This is an automated email from the ASF dual-hosted git repository.

entl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git


The following commit(s) were added to refs/heads/master by this push:
     new c47c7f2  Debugger attach in VSCode extension. (#5346)
c47c7f2 is described below

commit c47c7f22bb7adc97af6c428100487ab2f82688e3
Author: Martin Entlicher <martin.entlic...@oracle.com>
AuthorDate: Thu May 27 18:16:49 2021 +0200

    Debugger attach in VSCode extension. (#5346)
---
 .../lsp/server/debugging/NbProtocolServer.java     |  14 +-
 .../debugging/attach/AttachConfigurations.java     | 222 +++++++++++++++++++++
 .../debugging/attach/NbAttachRequestHandler.java   | 222 +++++++++++++++++++++
 .../server/debugging/launch/NbDebugSession.java    |   2 +-
 .../java/lsp/server/protocol/DebugConnector.java   | 151 ++++++++++++++
 .../modules/java/lsp/server/protocol/Server.java   |  27 ++-
 .../lsp/server/protocol/WorkspaceServiceImpl.java  |  15 +-
 .../java/lsp/server/protocol/ServerTest.java       |  42 ++++
 java/java.lsp.server/vscode/package.json           |  39 +++-
 java/java.lsp.server/vscode/src/extension.ts       |  61 +++---
 java/java.lsp.server/vscode/src/protocol.ts        |   7 +
 11 files changed, 758 insertions(+), 44 deletions(-)

diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java
index 56f20df..6ae1497 100644
--- 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java
@@ -19,19 +19,15 @@
 package org.netbeans.modules.java.lsp.server.debugging;
 
 import java.io.File;
-import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.net.URL;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.commons.lang3.StringUtils;
 import org.eclipse.lsp4j.debug.Capabilities;
@@ -41,7 +37,6 @@ import org.eclipse.lsp4j.debug.ContinueResponse;
 import org.eclipse.lsp4j.debug.DisconnectArguments;
 import org.eclipse.lsp4j.debug.EvaluateArguments;
 import org.eclipse.lsp4j.debug.EvaluateResponse;
-import org.eclipse.lsp4j.debug.ExceptionBreakMode;
 import org.eclipse.lsp4j.debug.ExceptionBreakpointsFilter;
 import org.eclipse.lsp4j.debug.ExceptionInfoArguments;
 import org.eclipse.lsp4j.debug.ExceptionInfoResponse;
@@ -79,10 +74,11 @@ import org.netbeans.api.debugger.jpda.ObjectVariable;
 import org.netbeans.api.debugger.jpda.Variable;
 import org.netbeans.modules.debugger.jpda.truffle.vars.TruffleVariable;
 import org.netbeans.modules.java.lsp.server.LspSession;
+import 
org.netbeans.modules.java.lsp.server.debugging.breakpoints.NbBreakpointsRequestHandler;
+import 
org.netbeans.modules.java.lsp.server.debugging.attach.NbAttachRequestHandler;
 import org.netbeans.modules.java.lsp.server.debugging.launch.NbDebugSession;
 import 
org.netbeans.modules.java.lsp.server.debugging.launch.NbDisconnectRequestHandler;
 import 
org.netbeans.modules.java.lsp.server.debugging.launch.NbLaunchRequestHandler;
-import 
org.netbeans.modules.java.lsp.server.debugging.breakpoints.NbBreakpointsRequestHandler;
 import 
org.netbeans.modules.java.lsp.server.debugging.variables.NbVariablesRequestHandler;
 import org.netbeans.modules.java.lsp.server.debugging.utils.ErrorUtilities;
 import org.netbeans.modules.nativeimage.api.debug.EvaluateException;
@@ -99,6 +95,7 @@ public final class NbProtocolServer implements 
IDebugProtocolServer, LspSession.
 
     private final DebugAdapterContext context;
     private final NbLaunchRequestHandler launchRequestHandler = new 
NbLaunchRequestHandler();
+    private final NbAttachRequestHandler attachRequestHandler = new 
NbAttachRequestHandler();
     private final NbDisconnectRequestHandler disconnectRequestHandler = new 
NbDisconnectRequestHandler();
     private final NbBreakpointsRequestHandler breakpointsRequestHandler = new 
NbBreakpointsRequestHandler();
     private final NbVariablesRequestHandler variablesRequestHandler = new 
NbVariablesRequestHandler();
@@ -172,6 +169,11 @@ public final class NbProtocolServer implements 
IDebugProtocolServer, LspSession.
     }
 
     @Override
+    public CompletableFuture<Void> attach(Map<String, Object> args) {
+        return attachRequestHandler.attach(args, context);
+    }
+
+    @Override
     public CompletableFuture<Void> disconnect(DisconnectArguments args) {
         return disconnectRequestHandler.disconnect(args, context);
     }
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/attach/AttachConfigurations.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/attach/AttachConfigurations.java
new file mode 100644
index 0000000..5a99511
--- /dev/null
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/attach/AttachConfigurations.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.debugging.attach;
+
+import com.sun.jdi.Bootstrap;
+import com.sun.jdi.VirtualMachineManager;
+import com.sun.jdi.connect.AttachingConnector;
+import com.sun.jdi.connect.Connector;
+import com.sun.tools.attach.AttachNotSupportedException;
+import com.sun.tools.attach.VirtualMachine;
+import com.sun.tools.attach.VirtualMachineDescriptor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
+
+import org.netbeans.modules.java.lsp.server.debugging.utils.ErrorUtilities;
+import org.netbeans.modules.java.lsp.server.protocol.DebugConnector;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.modules.java.lsp.server.protocol.QuickPickItem;
+import org.netbeans.modules.java.lsp.server.protocol.Server;
+import org.netbeans.modules.java.lsp.server.protocol.ShowQuickPickParams;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.RequestProcessor;
+
+/**
+ * Debugger attach configurations provider.
+ *
+ * @author Martin Entlicher
+ */
+public final class AttachConfigurations {
+
+    static final String NAME_ATTACH_PROCESS = "Attach to Process";          // 
NOI18N
+    static final String NAME_ATTACH_SOCKET = "Attach to Port";              // 
NOI18N
+    static final String NAME_ATTACH_SHMEM = "Attach to Shared Memory";      // 
NOI18N
+    static final String NAME_ATTACH_BY = "Attach by ";                      // 
NOI18N
+
+    static final String CONNECTOR_PROCESS = "com.sun.jdi.ProcessAttach";    // 
NOI18N
+    static final String CONNECTOR_SOCKET = "com.sun.jdi.SocketAttach";      // 
NOI18N
+    static final String CONNECTOR_SHMEM = "com.sun.jdi.SharedMemoryAttach"; // 
NOI18N
+
+    static final String PROCESS_ARG_PID = "processId";          // NOI18N
+    static final String SOCKET_ARG_HOST = "hostName";           // NOI18N
+    static final String SOCKET_ARG_PORT = "port";               // NOI18N
+    static final String SHMEM_ARG_NAME = "sharedMemoryName";    // NOI18N
+
+    private static final RequestProcessor RP = new 
RequestProcessor(AttachConfigurations.class);
+
+    private AttachConfigurations() {}
+
+    public static CompletableFuture<Object> findConnectors() {
+        return CompletableFuture.supplyAsync(() -> {
+            return listAttachingConnectors();
+        }, RP);
+    }
+
+    @Messages({"LBL_ConnectorPort=port of the debuggee JVM", 
"LBL_ConnectorShmemName=shared memory name"})
+    private static List<DebugConnector> listAttachingConnectors() {
+        VirtualMachineManager vmm = Bootstrap.virtualMachineManager ();
+        List<AttachingConnector> attachingConnectors = 
vmm.attachingConnectors();
+        List<DebugConnector> connectors = new ArrayList<>(5);
+        String type = "java8+";             // NOI18N
+        for (AttachingConnector ac : attachingConnectors) {
+            String connectorName = ac.name();
+            Map<String, Connector.Argument> defaultArguments = 
ac.defaultArguments();
+            DebugConnector connector;
+            switch (connectorName) {
+                case CONNECTOR_PROCESS:
+                    connector = new DebugConnector(NAME_ATTACH_PROCESS, type,
+                            Collections.singletonList(PROCESS_ARG_PID),
+                            Collections.singletonList("${command:" + 
Server.JAVA_FIND_DEBUG_PROCESS_TO_ATTACH + "}"));    // NOI18N
+                    break;
+                case CONNECTOR_SOCKET: {
+                    String hostName = 
getArgumentOrDefault(defaultArguments.get("hostname"), "localhost");          
// NOI18N
+                    String port = 
getArgumentOrDefault(defaultArguments.get("port"), 
"<"+Bundle.LBL_ConnectorPort()+">"); // NOI18N
+                    connector = new DebugConnector(NAME_ATTACH_SOCKET, type,
+                            Arrays.asList(SOCKET_ARG_HOST, SOCKET_ARG_PORT),
+                            Arrays.asList(hostName, port));
+                    break;
+                }
+                case CONNECTOR_SHMEM: {
+                    String name = 
getArgumentOrDefault(defaultArguments.get("name"), 
"<"+Bundle.LBL_ConnectorShmemName()+">");       // NOI18N
+                    connector = new DebugConnector(NAME_ATTACH_SHMEM, type,
+                            Collections.singletonList(SHMEM_ARG_NAME),
+                            Collections.singletonList(name));
+                    break;
+                }
+                default: {
+                    List<String> names = new ArrayList<>();
+                    List<String> values = new ArrayList<>();
+                    for (Connector.Argument arg : defaultArguments.values()) {
+                        if (arg.mustSpecify()) {
+                            names.add(arg.name());
+                            String value = arg.value();
+                            if (value.isEmpty()) {
+                                value = "<" + arg.description()+ ">";   // 
NOI18N
+                            }
+                            values.add(value);
+                        }
+                    }
+                    connector = new DebugConnector(NAME_ATTACH_BY + 
connectorName, type,
+                            names, values);
+                }
+            }
+            connectors.add(connector);
+        }
+        connectors.sort((c1, c2) -> 
c1.getName().compareToIgnoreCase(c2.getName()));
+        return connectors;
+    }
+
+    private static String getArgumentOrDefault(Connector.Argument arg, String 
def) {
+        if (arg != null) {
+            String value = arg.value();
+            if (!value.isEmpty()) {
+                return value;
+            }
+        }
+        return def;
+    }
+
+    public static CompletableFuture<Object> 
findProcessAttachTo(NbCodeLanguageClient client) {
+        return CompletableFuture.supplyAsync(() -> {
+            return listProcessesToAttachTo(client);
+        }, RP).thenCompose(params -> 
client.showQuickPick(params)).thenApply(itemsList -> {
+            if (itemsList == null || itemsList.isEmpty()) {
+                return null;
+            } else {
+                return itemsList.get(0).getUserData();
+            }
+        });
+    }
+
+    @Messages("MSG_NoDebuggableProcess=No debuggable JVM process found. Please 
be sure to use `-agentlib:jdwp=transport=dt_socket,server=y` option.")
+    private static void notifyNoProcessesError(NbCodeLanguageClient client) {
+        MessageParams params = new MessageParams();
+        params.setMessage(Bundle.MSG_NoDebuggableProcess());
+        params.setType(MessageType.Error);
+        client.showMessage(params);
+    }
+
+    @Messages("LBL_PickProcessAttach=Pick JVM process to attach to:")
+    private static ShowQuickPickParams 
listProcessesToAttachTo(NbCodeLanguageClient client) {
+        List<QuickPickItem> attachables = new ArrayList<>();
+        List<VirtualMachineDescriptor> descriptors = VirtualMachine.list();
+        for (VirtualMachineDescriptor descriptor : descriptors) {
+            try {
+                VirtualMachine vm = VirtualMachine.attach(descriptor);
+                Properties agentProperties = vm.getAgentProperties();
+                boolean hasJDWP = false;
+                for (Object key : agentProperties.keySet()) {
+                    if (key instanceof String && ((String) 
key).contains("jdwp")) { // NOI18N
+                        hasJDWP = true;
+                        break;
+                    }
+                }
+                if (hasJDWP) {
+                    attachables.add(createQuickPickItem(descriptor, vm));
+                }
+            } catch (AttachNotSupportedException | IOException ex) {
+                continue;
+            }
+        }
+        if (attachables.isEmpty()) {
+            notifyNoProcessesError(client);
+            throw ErrorUtilities.createResponseErrorException("No debuggable 
JVM process found.", ResponseErrorCode.RequestCancelled);  // NOI18N
+        }
+        return new ShowQuickPickParams(Bundle.LBL_PickProcessAttach(), 
attachables);
+    }
+
+    private static QuickPickItem createQuickPickItem(VirtualMachineDescriptor 
descriptor, VirtualMachine vm) {
+        String label = descriptor.id();
+        String detail = descriptor.displayName();
+        if ("Unknown".equals(detail)) {     // NOI18N
+            String command = null;
+            try {
+                command = 
vm.getSystemProperties().getProperty("sun.java.command");     // NOI18N
+            } catch (IOException ex) {}
+            if (command == null) {
+                try {
+                    command = 
vm.getAgentProperties().getProperty("sun.java.command");  // NOI18N
+                } catch (IOException ex) {}
+            }
+            if (command != null) {
+                detail = command;
+            }
+        }
+        int index = detail.indexOf(' ');
+        String description = index > 0 ? detail.substring(0, index) : detail;
+        if (index <= 0) {
+            // Do not duplicate the description.
+            detail = null;
+        }
+        Object userData = descriptor.id(); // Process Id
+        return new QuickPickItem(label, description, detail, false, userData);
+    }
+
+}
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/attach/NbAttachRequestHandler.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/attach/NbAttachRequestHandler.java
new file mode 100644
index 0000000..8b403f8
--- /dev/null
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/attach/NbAttachRequestHandler.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.debugging.attach;
+
+import com.sun.jdi.Bootstrap;
+import com.sun.jdi.VirtualMachineManager;
+import com.sun.jdi.connect.AttachingConnector;
+import com.sun.jdi.connect.Connector.Argument;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.debug.TerminatedEventArguments;
+import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
+
+import org.netbeans.api.debugger.DebuggerEngine;
+import org.netbeans.api.debugger.DebuggerInfo;
+import org.netbeans.api.debugger.DebuggerManager;
+import org.netbeans.api.debugger.Session;
+import org.netbeans.api.debugger.jpda.AttachingDICookie;
+import org.netbeans.api.debugger.jpda.DebuggerStartException;
+import org.netbeans.api.debugger.jpda.JPDADebugger;
+import org.netbeans.modules.java.lsp.server.debugging.DebugAdapterContext;
+import org.netbeans.modules.java.lsp.server.debugging.launch.NbDebugSession;
+import org.netbeans.modules.java.lsp.server.debugging.utils.ErrorUtilities;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.RequestProcessor;
+
+/**
+ *
+ * @author Martin Entlicher
+ */
+public final class NbAttachRequestHandler {
+
+    private static final String CONNECTOR_ARG_PID = "pid";          // NOI18N
+    private static final String CONNECTOR_ARG_HOST = "hostname";    // NOI18N
+    private static final String CONNECTOR_ARG_PORT = "port";        // NOI18N
+    private static final String CONNECTOR_ARG_NAME = "name";        // NOI18N
+    // The default attributes of DebugConfiguration
+    private static final Set<String> CONFIG_ATTRIBUTES = new 
HashSet<>(Arrays.asList("type", "name", "request", "classPaths", "console"));   
// NOI18N
+
+    private static final RequestProcessor RP = new 
RequestProcessor(AttachConfigurations.class);
+
+    public CompletableFuture<Void> attach(Map<String, Object> attachArguments, 
DebugAdapterContext context) {
+        boolean isNative = "nativeimage".equals(attachArguments.get("type"));  
 // NOI18N
+        if (isNative) {
+            return attachToNative(attachArguments, context);
+        } else {
+            return attachToJVM(attachArguments, context);
+        }
+    }
+
+    private CompletableFuture<Void> attachToNative(Map<String, Object> 
attachArguments, DebugAdapterContext context) {
+        CompletableFuture<Void> resultFuture = new CompletableFuture<>();
+        // TODO
+        ErrorUtilities.completeExceptionally(resultFuture,
+                "Attach to native image is not implemented yet", 
ResponseErrorCode.serverErrorStart);
+        return resultFuture;
+    }
+
+    @Messages({"# {0} - connector name", "MSG_InvalidConnector=Invalid 
connector name: {0}"})
+    private CompletableFuture<Void> attachToJVM(Map<String, Object> 
attachArguments, DebugAdapterContext context) {
+        String name = (String) attachArguments.get("name");     // NOI18N
+        AttachingDICookie attachingCookie;
+        String connectorName;
+        Map<String, String> translatedArguments = new HashMap<>();
+        CompletableFuture<Void> resultFuture = new CompletableFuture<>();
+        switch (name) {
+            case AttachConfigurations.NAME_ATTACH_PROCESS:
+                Object pid = 
attachArguments.get(AttachConfigurations.PROCESS_ARG_PID);
+                connectorName = AttachConfigurations.CONNECTOR_PROCESS;
+                translatedArguments.put(AttachConfigurations.PROCESS_ARG_PID, 
CONNECTOR_ARG_PID);
+                break;
+            case AttachConfigurations.NAME_ATTACH_SOCKET:
+                connectorName = AttachConfigurations.CONNECTOR_SOCKET;
+                translatedArguments.put(AttachConfigurations.SOCKET_ARG_HOST, 
CONNECTOR_ARG_HOST);
+                translatedArguments.put(AttachConfigurations.SOCKET_ARG_PORT, 
CONNECTOR_ARG_PORT);
+                break;
+            case AttachConfigurations.NAME_ATTACH_SHMEM:
+                connectorName = AttachConfigurations.CONNECTOR_SHMEM;
+                translatedArguments.put(AttachConfigurations.SHMEM_ARG_NAME, 
CONNECTOR_ARG_NAME);
+                break;
+            default:
+                if (name.startsWith(AttachConfigurations.NAME_ATTACH_BY)) {
+                    connectorName = 
name.substring(AttachConfigurations.NAME_ATTACH_BY.length());
+                } else {
+                    ErrorUtilities.completeExceptionally(resultFuture,
+                            Bundle.MSG_InvalidConnector(name),
+                            ResponseErrorCode.serverErrorStart);
+                    connectorName = null;
+                }
+        }
+        if (connectorName != null) {
+            context.setDebugMode(true);
+            RP.post(() -> attachTo(connectorName, attachArguments, 
translatedArguments, context, resultFuture));
+        } else {
+            assert resultFuture.isCompletedExceptionally();
+        }
+        return resultFuture;
+    }
+
+    @Messages({"# {0} - connector name", "# {1} - argument name", 
"MSG_ConnectorArgumentNotFound=Argument {0} of {1} was not found.",
+               "# {0} - argument name", "# {1} - value", 
"MSG_ConnectorInvalidValue=Invalid value of {0}: {1}",
+               "# {0} - connector name", "MSG_ConnectorNotFound=Connector {0} 
was not found."})
+    private void attachTo(String connectorName, Map<String, Object> arguments, 
Map<String, String> translatedArguments, DebugAdapterContext context, 
CompletableFuture<Void> resultFuture) {
+        VirtualMachineManager vmm = Bootstrap.virtualMachineManager ();
+        List<AttachingConnector> attachingConnectors = 
vmm.attachingConnectors();
+        for (AttachingConnector connector : attachingConnectors) {
+            if (connector.name().equals(connectorName)) {
+                Map<String, Argument> args = connector.defaultArguments();
+                for (String argName : arguments.keySet()) {
+                    if (CONFIG_ATTRIBUTES.contains(argName) || 
argName.startsWith("__")) {
+                        continue;
+                    }
+                    String argNameTranslated = 
translatedArguments.getOrDefault(argName, argName);
+                    Argument arg = args.get(argNameTranslated);
+                    if (arg == null) {
+                        ErrorUtilities.completeExceptionally(resultFuture,
+                            
Bundle.MSG_ConnectorArgumentNotFound(connectorName, argNameTranslated),
+                            ResponseErrorCode.serverErrorStart);
+                        return ;
+                    }
+                    String value = arguments.get(argName).toString();
+                    if (!arg.isValid(value)) {
+                        ErrorUtilities.completeExceptionally(resultFuture,
+                            Bundle.MSG_ConnectorInvalidValue(argName, value),
+                            ResponseErrorCode.serverErrorStart);
+                        return ;
+                    }
+                    arg.setValue(value);
+                }
+                AttachingDICookie attachingCookie = 
AttachingDICookie.create(connector, args);
+                resultFuture.complete(null);
+                startAttaching(attachingCookie, context);
+                return ;
+            }
+        }
+        ErrorUtilities.completeExceptionally(resultFuture,
+                Bundle.MSG_ConnectorNotFound(connectorName),
+                ResponseErrorCode.serverErrorStart);
+    }
+
+    @Messages("MSG_FailedToAttach=Failed to attach.")
+    private void startAttaching(AttachingDICookie attachingCookie, 
DebugAdapterContext context) {
+        DebuggerEngine[] es = DebuggerManager.getDebuggerManager 
().startDebugging(
+            DebuggerInfo.create(AttachingDICookie.ID, new Object [] { 
attachingCookie })
+        );
+        if (es.length > 0) {
+            JPDADebugger debugger = es[0].lookupFirst(null, 
JPDADebugger.class);
+            if (debugger != null) {
+                Session session = es[0].lookupFirst(null, Session.class);
+                NbDebugSession debugSession = new NbDebugSession(session);
+                context.setDebugSession(debugSession);
+                AtomicBoolean finished = new AtomicBoolean(false);
+                debugger.addPropertyChangeListener(JPDADebugger.PROP_STATE, 
new PropertyChangeListener() {
+                    @Override
+                    public void propertyChange(PropertyChangeEvent evt) {
+                        int newState = (int) evt.getNewValue();
+                        if (newState == JPDADebugger.STATE_DISCONNECTED) {
+                            if (!finished.getAndSet(true)) {
+                                notifyTerminated(context);
+                            }
+                        }
+                    }
+                });
+                boolean success = false;
+                try {
+                    debugger.waitRunning();
+                    success = debugger.getState() != 
JPDADebugger.STATE_DISCONNECTED;
+                } catch (DebuggerStartException ex) {
+                    notifyErrorMessage(context, ex.getLocalizedMessage());
+                }
+                if (!success) {
+                    if (!finished.getAndSet(true)) {
+                        notifyTerminated(context);
+                    }
+                }
+                return ;
+            }
+        }
+        notifyErrorMessage(context, Bundle.MSG_FailedToAttach());
+        notifyTerminated(context);
+    }
+
+    private void notifyErrorMessage(DebugAdapterContext context, String 
message) {
+        MessageParams params = new MessageParams();
+        params.setMessage(message);
+        params.setType(MessageType.Error);
+        
context.getLspSession().getLookup().lookup(NbCodeLanguageClient.class).showMessage(params);
+    }
+
+    private void notifyTerminated(DebugAdapterContext context) {
+        context.getClient().terminated(new TerminatedEventArguments());
+    }
+}
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDebugSession.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDebugSession.java
index e18052c..3c110a6 100644
--- 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDebugSession.java
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDebugSession.java
@@ -31,7 +31,7 @@ public final class NbDebugSession {
     private final Session session;
     private volatile NIDebugger niDebugger;
 
-    NbDebugSession(Session session) {
+    public NbDebugSession(Session session) {
         this.session = session;
     }
 
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DebugConnector.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DebugConnector.java
new file mode 100644
index 0000000..e8b3a20
--- /dev/null
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DebugConnector.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import java.util.List;
+import java.util.Objects;
+import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ * Debug connector arguments.
+ *
+ * @author Martin Entlicher
+ */
+public final class DebugConnector {
+
+    /**
+     * The display name identifier of the connector.
+     */
+    @NonNull
+    private String name;
+
+    /**
+     * The type of the connector.
+     */
+    @NonNull
+    private String type;
+
+    /**
+     * The debug connector arguments.
+     */
+    @NonNull
+    private List<String> arguments;
+
+    /**
+     * Default values of debug connector arguments.
+     */
+    @NonNull
+    private List<String> defaultValues;
+
+    public DebugConnector(String name, String type, List<String> arguments, 
List<String> defaultValues) {
+        this.name = name;
+        this.type = type;
+        this.arguments = arguments;
+        this.defaultValues = defaultValues;
+    }
+
+    @Pure
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Pure
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    @Pure
+    public List<String> getArguments() {
+        return arguments;
+    }
+
+    public void setArguments(List<String> arguments) {
+        this.arguments = arguments;
+    }
+
+    @Pure
+    public List<String> getDefaultValues() {
+        return defaultValues;
+    }
+
+    public void setDefaultValues(List<String> defaultValues) {
+        this.defaultValues = defaultValues;
+    }
+
+    @Pure
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 37 * hash + Objects.hashCode(this.name);
+        hash = 37 * hash + Objects.hashCode(this.type);
+        hash = 37 * hash + Objects.hashCode(this.arguments);
+        hash = 37 * hash + Objects.hashCode(this.defaultValues);
+        return hash;
+    }
+
+    @Pure
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final DebugConnector other = (DebugConnector) obj;
+        if (!Objects.equals(this.name, other.name)) {
+            return false;
+        }
+        if (!Objects.equals(this.type, other.type)) {
+            return false;
+        }
+        if (!Objects.equals(this.arguments, other.arguments)) {
+            return false;
+        }
+        if (!Objects.equals(this.defaultValues, other.defaultValues)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Pure
+    @Override
+    public String toString() {
+        ToStringBuilder b = new ToStringBuilder(this);
+        b.add("name", name);
+        b.add("type", type);
+        b.add("arguments", arguments.toString());
+        b.add("defaultValues", defaultValues.toString());
+        return b.toString();
+    }
+
+}
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
index cb184b2..3a19dea 100644
--- 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
@@ -606,9 +606,18 @@ public final class Server {
                 capabilities.setImplementationProvider(true);
                 capabilities.setDocumentHighlightProvider(true);
                 capabilities.setReferencesProvider(true);
-                List<String> commands = new 
ArrayList<>(Arrays.asList(JAVA_NEW_FROM_TEMPLATE, JAVA_BUILD_WORKSPACE, 
JAVA_GET_PROJECT_SOURCE_ROOTS,
-                        JAVA_GET_PROJECT_CLASSPATH, 
JAVA_GET_PROJECT_PACKAGES,JAVA_LOAD_WORKSPACE_TESTS, GRAALVM_PAUSE_SCRIPT, 
JAVA_SUPER_IMPLEMENTATION,
-                        JAVA_FIND_CONFIGURATIONS));
+                List<String> commands = new ArrayList<>(Arrays.asList(
+                        GRAALVM_PAUSE_SCRIPT,
+                        JAVA_BUILD_WORKSPACE,
+                        JAVA_FIND_DEBUG_ATTACH_CONFIGURATIONS,
+                        JAVA_FIND_DEBUG_PROCESS_TO_ATTACH,
+                        JAVA_FIND_PROJECT_CONFIGURATIONS,
+                        JAVA_GET_PROJECT_CLASSPATH,
+                        JAVA_GET_PROJECT_PACKAGES,
+                        JAVA_GET_PROJECT_SOURCE_ROOTS,
+                        JAVA_LOAD_WORKSPACE_TESTS,
+                        JAVA_NEW_FROM_TEMPLATE,
+                        JAVA_SUPER_IMPLEMENTATION));
                 for (CodeGenerator codeGenerator : 
Lookup.getDefault().lookupAll(CodeGenerator.class)) {
                     commands.addAll(codeGenerator.getCommands());
                 }
@@ -731,8 +740,16 @@ public final class Server {
     /**
      * Enumerates project configurations.
      */
-    public static final String JAVA_FIND_CONFIGURATIONS = 
"java.project.configurations";
-            
+    public static final String JAVA_FIND_PROJECT_CONFIGURATIONS = 
"java.project.configurations";
+    /**
+     * Enumerates attach debugger configurations.
+     */
+    public static final String JAVA_FIND_DEBUG_ATTACH_CONFIGURATIONS = 
"java.attachDebugger.configurations";
+    /**
+     * Enumerates JVM processes eligible for debugger attach.
+     */
+    public static final String JAVA_FIND_DEBUG_PROCESS_TO_ATTACH = 
"java.attachDebugger.pickProcess";
+
     static final String INDEXING_COMPLETED = "Indexing completed.";
     static final String NO_JAVA_SUPPORT = "Cannot initialize Java support on 
JDK ";
 
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
index 8fe666d..7f970ad 100644
--- 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
@@ -78,6 +78,7 @@ import org.netbeans.api.project.ui.OpenProjects;
 import org.netbeans.modules.gsf.testrunner.ui.api.TestMethodController;
 import org.netbeans.modules.java.lsp.server.LspServerState;
 import org.netbeans.modules.java.lsp.server.Utils;
+import 
org.netbeans.modules.java.lsp.server.debugging.attach.AttachConfigurations;
 import org.netbeans.modules.java.source.ui.JavaSymbolProvider;
 import org.netbeans.modules.java.source.ui.JavaTypeProvider;
 import org.netbeans.modules.java.source.usages.ClassIndexImpl;
@@ -219,7 +220,7 @@ public final class WorkspaceServiceImpl implements 
WorkspaceService, LanguageCli
                 Position pos = 
gson.fromJson(gson.toJson(params.getArguments().get(1)), Position.class);
                 return 
(CompletableFuture)((TextDocumentServiceImpl)server.getTextDocumentService()).superImplementations(uri,
 pos);
                 
-            case Server.JAVA_FIND_CONFIGURATIONS: {
+            case Server.JAVA_FIND_PROJECT_CONFIGURATIONS: {
                 String fileUri = ((JsonPrimitive) 
params.getArguments().get(0)).getAsString();
                 
                 FileObject file;
@@ -232,6 +233,12 @@ public final class WorkspaceServiceImpl implements 
WorkspaceService, LanguageCli
 
                 return findProjectConfigurations(file);
             }
+            case Server.JAVA_FIND_DEBUG_ATTACH_CONFIGURATIONS: {
+                return AttachConfigurations.findConnectors();
+            }
+            case Server.JAVA_FIND_DEBUG_PROCESS_TO_ATTACH: {
+                return AttachConfigurations.findProcessAttachTo(client);
+            }
             default:
                 for (CodeGenerator codeGenerator : 
Lookup.getDefault().lookupAll(CodeGenerator.class)) {
                     if (codeGenerator.getCommands().contains(command)) {
@@ -249,8 +256,10 @@ public final class WorkspaceServiceImpl implements 
WorkspaceService, LanguageCli
             }
             ProjectConfigurationProvider<ProjectConfiguration> provider = 
p.getLookup().lookup(ProjectConfigurationProvider.class);
             List<String> configDispNames = new ArrayList<>();
-            for (ProjectConfiguration c : provider.getConfigurations()) {
-                configDispNames.add(c.getDisplayName());
+            if (provider != null) {
+                for (ProjectConfiguration c : provider.getConfigurations()) {
+                    configDispNames.add(c.getDisplayName());
+                }
             }
             return configDispNames;
         });
diff --git 
a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
 
b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
index 075782b..51a4bcd 100644
--- 
a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
+++ 
b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
@@ -1076,6 +1076,48 @@ public class ServerTest extends NbTestCase {
         assertEquals(13, loc.getRange().getEnd().getCharacter());
     }
 
+    public void testFindDebugAttachConfigurations() throws Exception {
+        Launcher<LanguageServer> serverLauncher = 
LSPLauncher.createClientLauncher(new LspClient() {
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        Object ret = server.getWorkspaceService().executeCommand(new 
ExecuteCommandParams(Server.JAVA_FIND_DEBUG_ATTACH_CONFIGURATIONS, 
Collections.emptyList())).get();
+        assertNotNull(ret);
+        DebugConnector[] connectors = 
gson.fromJson(gson.toJsonTree(ret).getAsJsonArray(), DebugConnector[].class);
+        assertTrue(connectors.length > 0);
+        boolean haveAttachToPort = false;
+        boolean haveAttachToProcess = false;
+        for (DebugConnector c : connectors) {
+            if ("Attach to Port".equals(c.getName())) {
+                haveAttachToPort = true;
+                checkAttachToPort(c);
+            }
+            if ("Attach to Process".equals(c.getName())) {
+                haveAttachToProcess = true;
+                checkAttachToProcess(c);
+            }
+        }
+        assertTrue(Arrays.toString(connectors), haveAttachToPort && 
haveAttachToProcess);
+    }
+
+    private void checkAttachToPort(DebugConnector c) {
+        assertEquals("java8+", c.getType());
+        List<String> arguments = c.getArguments();
+        assertEquals(2, arguments.size());
+        assertEquals("hostName", arguments.get(0));
+        assertEquals("port", arguments.get(1));
+        assertEquals(2, c.getDefaultValues().size());
+    }
+
+    private void checkAttachToProcess(DebugConnector c) {
+        assertEquals("java8+", c.getType());
+        List<String> arguments = c.getArguments();
+        assertEquals(1, arguments.size());
+        assertEquals("processId", arguments.get(0));
+        assertEquals(1, c.getDefaultValues().size());
+    }
+
     public void testOpenProjectOpenJDK() throws Exception {
         getWorkDir().mkdirs();
 
diff --git a/java/java.lsp.server/vscode/package.json 
b/java/java.lsp.server/vscode/package.json
index 8e871d6..dbf3a5f 100644
--- a/java/java.lsp.server/vscode/package.json
+++ b/java/java.lsp.server/vscode/package.json
@@ -28,7 +28,8 @@
                "workspaceContains:**/*.java",
                "workspaceContains:pom.xml",
                "workspaceContains:build.gradle",
-               "onDebug"
+               "onDebug",
+               "onDebugDynamicConfigurations"
        ],
        "main": "./out/extension.js",
        "contributes": {
@@ -125,7 +126,7 @@
                                                                "description": 
"Arguments for the Java VM",
                                                                "default": null
                                                        },
-                                                       "launchConfiguration" : 
{
+                                                       "launchConfiguration": {
                                                                "type": [
                                                                        
"string",
                                                                        "null"
@@ -133,6 +134,34 @@
                                                                "description": 
"Mode and default behaviour for launch"
                                                        }
                                                }
+                                       },
+                                       "attach": {
+                                               "properties": {
+                                                       "hostName": {
+                                                               "type": 
"string",
+                                                               "default": 
"localhost",
+                                                               "description": 
"Host name or IP address to which to attach"
+                                                       },
+                                                       "port": {
+                                                               "type": 
"string",
+                                                               "default": 
"8000",
+                                                               "description": 
"Port number to which to attach"
+                                                       },
+                                                       "sharedMemoryName": {
+                                                               "type": 
"string",
+                                                               "description": 
"Shared memory name of the debuggee"
+                                                       },
+                                                       "processId": {
+                                                               "type": 
"string",
+                                                               "default": 
"${command:java.attachDebugger.pickProcess}",
+                                                               "description": 
"Process Id of the debuggee"
+                                                       },
+                                                       "timeout": {
+                                                               "type": 
"string",
+                                                               "default": 
"30000",
+                                                               "description": 
"Timeout while waiting to attach"
+                                                       }
+                                               }
                                        }
                                },
                                "initialConfigurations": [
@@ -297,16 +326,16 @@
                "nbjavac": "node ./out/nbcode.js -J-Dnetbeans.close=true 
--modules --install .*nbjavac.*"
        },
        "devDependencies": {
-               "@types/vscode": "^1.49.0",
                "@types/glob": "^7.1.1",
                "@types/mocha": "^7.0.2",
                "@types/node": "^13.11.0",
                "@types/ps-node": "^0.1.0",
+               "@types/vscode": "^1.49.0",
                "glob": "^7.1.6",
                "mocha": "^7.1.2",
+               "ps-node": "^0.1.6",
                "typescript": "^3.8.3",
-               "vscode-test": "^1.3.0",
-               "ps-node": "^0.1.6"
+               "vscode-test": "^1.3.0"
        },
        "dependencies": {
                "vscode-debugadapter": "1.42.1",
diff --git a/java/java.lsp.server/vscode/src/extension.ts 
b/java/java.lsp.server/vscode/src/extension.ts
index ada41c6..7027ff6 100644
--- a/java/java.lsp.server/vscode/src/extension.ts
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -41,7 +41,7 @@ import { testExplorerExtensionId, TestHub } from 
'vscode-test-adapter-api';
 import { TestAdapterRegistrar } from 'vscode-test-adapter-util';
 import * as launcher from './nbcode';
 import {NbTestAdapter} from './testAdapter';
-import { StatusMessageRequest, ShowStatusMessageParams, QuickPickRequest, 
InputBoxRequest, TestProgressNotification } from './protocol';
+import { StatusMessageRequest, ShowStatusMessageParams, QuickPickRequest, 
InputBoxRequest, TestProgressNotification, DebugConnector } from './protocol';
 
 const API_VERSION : string = "1.0";
 let client: Promise<LanguageClient>;
@@ -173,7 +173,7 @@ export function activate(context: ExtensionContext): 
VSNetBeansAPI {
 
     //register debugger:
     let configProvider = new NetBeansConfigurationProvider();
-    
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('java8+',
 configProvider));
+    
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('java8+',
 configProvider, vscode.DebugConfigurationProviderTriggerKind.Dynamic));
     let configNativeProvider = new NetBeansConfigurationNativeProvider();
     
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('nativeimage',
 configNativeProvider));
 
@@ -672,30 +672,43 @@ class NetBeansConfigurationProvider implements 
vscode.DebugConfigurationProvider
         } else {
             u = vscode.window.activeTextEditor?.document?.uri
         }
+        let result : vscode.DebugConfiguration[] = [];
         const configNames : string[] | null | undefined = await 
vscode.commands.executeCommand('java.project.configurations', u?.toString());
-        if (!configNames) {
-            return [];
+        if (configNames) {
+            let first : boolean = true;
+            for (let cn of configNames) {
+                let cname : string;
+
+                if (first) {
+                    // ignore the default config, comes first.
+                    first = false;
+                    continue;
+                } else {
+                    cname = "Launch Java: " + cn;
+                }
+                const debugConfig : vscode.DebugConfiguration = {
+                    name: cname,
+                    type: "java8+",
+                    request: "launch",
+                    mainClass: '${file}',
+                    launchConfiguration: cn,
+                };
+                result.push(debugConfig);
+            }
         }
-        let result : vscode.DebugConfiguration[] = [];
-        let first : boolean = true;
-        for (let cn of configNames) {
-            let cname : string;
-
-            if (first) {
-                // ignore the default config, comes first.
-                first = false;
-                continue;
-            } else {
-                cname = "Launch Java: " + cn;
+        const attachConnectors : DebugConnector[] | null | undefined = await 
vscode.commands.executeCommand('java.attachDebugger.configurations');
+        if (attachConnectors) {
+            for (let ac of attachConnectors) {
+                const debugConfig : vscode.DebugConfiguration = {
+                    name: ac.name,
+                    type: ac.type,
+                    request: "attach",
+                };
+                for (let i = 0; i < ac.arguments.length; i++) {
+                    debugConfig[ac.arguments[i]] = ac.defaultValues[i];
+                }
+                result.push(debugConfig)
             }
-            const debugConfig : vscode.DebugConfiguration = {
-                name: cname,
-                type: "java8+",
-                request: "launch",
-                mainClass: '${file}',
-                launchConfiguration: cn,
-            };
-            result.push(debugConfig);
         }
         return result;
     }
@@ -707,7 +720,7 @@ class NetBeansConfigurationProvider implements 
vscode.DebugConfigurationProvider
         if (!config.request) {
             config.request = 'launch';
         }
-        if (!config.mainClass) {
+        if ('launch' == config.request && !config.mainClass) {
             config.mainClass = '${file}';
         }
         if (!config.classPaths) {
diff --git a/java/java.lsp.server/vscode/src/protocol.ts 
b/java/java.lsp.server/vscode/src/protocol.ts
index 8a4d08f..5b48c2e 100644
--- a/java/java.lsp.server/vscode/src/protocol.ts
+++ b/java/java.lsp.server/vscode/src/protocol.ts
@@ -96,3 +96,10 @@ export interface TestCase {
 export namespace TestProgressNotification {
     export const type = new NotificationType<TestProgressParams, 
void>('window/notifyTestProgress');
 };
+
+export interface DebugConnector {
+    name: string;
+    type: string;
+    arguments: string[];
+    defaultValues: string[];
+}

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org
For additional commands, e-mail: commits-h...@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists

Reply via email to