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

dbalek 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 68222f2649 LPS: SignatureHelp implemented.
     new e71774364f Merge pull request #6476 from 
dbalek/dbalek/lsp-signature-help
68222f2649 is described below

commit 68222f26491c6b9ea886f18605e4f9443b45fc34
Author: Dusan Balek <dusan.ba...@oracle.com>
AuthorDate: Thu Sep 21 16:01:46 2023 +0200

    LPS: SignatureHelp implemented.
---
 ide/api.lsp/apichanges.xml                         |  15 ++
 ide/api.lsp/manifest.mf                            |   2 +-
 .../org/netbeans/api/lsp/SignatureInformation.java | 248 +++++++++++++++++++++
 .../modules/lsp/SignatureInformationAccessor.java  |  57 +++++
 .../spi/lsp/SignatureInformationCollector.java     | 132 +++++++++++
 .../netbeans/api/lsp/SignatureInformationTest.java | 128 +++++++++++
 java/java.completion/nbproject/project.properties  |   2 +-
 .../modules/java/completion/JavaTooltipTask.java   |  78 +++++--
 java/java.editor/nbproject/project.xml             |   4 +-
 .../java/JavaSignatureInformationCollector.java    |  66 ++++++
 .../editor/java/MethodParamsTipPaintComponent.java |   7 +
 .../modules/java/lsp/server/protocol/Server.java   |   5 +
 .../server/protocol/TextDocumentServiceImpl.java   |  48 +++-
 .../java/lsp/server/protocol/ServerTest.java       |  64 +++++-
 14 files changed, 826 insertions(+), 30 deletions(-)

diff --git a/ide/api.lsp/apichanges.xml b/ide/api.lsp/apichanges.xml
index b14de6dd7b..3cbeb75231 100644
--- a/ide/api.lsp/apichanges.xml
+++ b/ide/api.lsp/apichanges.xml
@@ -51,6 +51,21 @@
 <!-- ACTUAL CHANGES BEGIN HERE: -->
 
 <changes>
+    <change id="SignatureInformation">
+        <api name="LSP_API"/>
+        <summary>Added SignatureInformation and 
SignatureInformationCollector</summary>
+        <version major="1" minor="20"/>
+        <date day="21" month="9" year="2023"/>
+        <author login="dbalek"/>
+        <compatibility binary="compatible" source="compatible" addition="yes" 
deletion="no"/>
+        <description>
+            A <a 
href="@TOP@/org/netbeans/api/lsp/SignatureInformation.html">SignatureInformation</a>
 class
+            and <a 
href="@TOP@/org/netbeans/api/lsp/SignatureInformationCollecto.html">SignatureInformationCollector</a>
 interface
+            introduced that allows to compute and collect signature 
information.
+        </description>
+        <class package="org.netbeans.api.lsp" name="SignatureInformation"/>
+        <class package="org.netbeans.spi.lsp" 
name="SignatureInformationCollector"/>
+    </change>
     <change id="LazyCodeAction">
         <api name="LSP_API"/>
         <summary>Added CodeAction with lazy edit computation</summary>
diff --git a/ide/api.lsp/manifest.mf b/ide/api.lsp/manifest.mf
index dccfadd348..3bfd176abc 100644
--- a/ide/api.lsp/manifest.mf
+++ b/ide/api.lsp/manifest.mf
@@ -1,5 +1,5 @@
 Manifest-Version: 1.0
 OpenIDE-Module: org.netbeans.api.lsp/1
 OpenIDE-Module-Localizing-Bundle: org/netbeans/api/lsp/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.19
+OpenIDE-Module-Specification-Version: 1.20
 AutoUpdate-Show-In-Client: false
diff --git a/ide/api.lsp/src/org/netbeans/api/lsp/SignatureInformation.java 
b/ide/api.lsp/src/org/netbeans/api/lsp/SignatureInformation.java
new file mode 100644
index 0000000000..a5e384da01
--- /dev/null
+++ b/ide/api.lsp/src/org/netbeans/api/lsp/SignatureInformation.java
@@ -0,0 +1,248 @@
+/*
+ * 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.api.lsp;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import javax.swing.text.Document;
+import org.netbeans.api.annotations.common.CheckForNull;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.annotations.common.NullAllowed;
+import org.netbeans.api.editor.mimelookup.MimeLookup;
+import org.netbeans.api.editor.mimelookup.MimePath;
+import org.netbeans.lib.editor.util.swing.DocumentUtilities;
+import org.netbeans.modules.lsp.SignatureInformationAccessor;
+import org.netbeans.spi.lsp.SignatureInformationCollector;
+
+/**
+ * Represents the signature of something callable. A signature can have a 
label,
+ * like a function-name, a doc-comment, and a set of parameters.
+ *
+ * @author Dusan Balek
+ * @since 1.20
+ */
+public final class SignatureInformation {
+
+    static {
+        SignatureInformationAccessor.setDefault(new 
SignatureInformationAccessor() {
+            @Override
+            public SignatureInformation createSignatureInformation(String 
label, List<ParameterInformation> params, boolean isActive, String 
documentation) {
+                return new SignatureInformation(label, params, isActive, 
documentation);
+            }
+
+            @Override
+            public ParameterInformation createParameterInformation(String 
label, boolean isActive, String documentation) {
+                return new ParameterInformation(label, isActive, 
documentation);
+            }
+        });
+    }
+
+    private final String label;
+    private final List<ParameterInformation> params;
+    private final boolean isActive;
+    private final String documentation;
+
+    private SignatureInformation(String label, List<ParameterInformation> 
params, boolean isActive, String documentation) {
+        this.label = label;
+        this.params = params;
+        this.isActive = isActive;
+        this.documentation = documentation;
+    }
+
+    /**
+     * The label of this signature information.
+     *
+     * @since 1.20
+     */
+    @NonNull
+    public String getLabel() {
+        return label;
+    }
+
+    /**
+     * The parameters of this signature.
+     *
+     * @since 1.20
+     */
+    @NonNull
+    public List<ParameterInformation> getParameters() {
+        return Collections.unmodifiableList(params);
+    }
+
+    /**
+     * Returns true if the signature is active.
+     *
+     * @since 1.20
+     */
+    public boolean isActive() {
+        return isActive;
+    }
+
+    /**
+     * A human-readable string that represents a doc-comment. An HTML format is
+     * supported.
+     *
+     * @since 1.20
+     */
+    @CheckForNull
+    public String getDocumentation() {
+        return documentation;
+    }
+
+    /**
+     * Computes and collects signature information for a document at a given 
offset. Example
+     * usage can be illustrated by:
+     * {@snippet file="org/netbeans/api/lsp/SignatureInformationTest.java" 
region="testSignatureInformationCollect"}
+     *
+     * @param doc a text document
+     * @param offset an offset inside the text document
+     * @param context an optional signature help context
+     * @param consumer an operation accepting collected signature information
+     *
+     * @since 1.20
+     */
+    public static void collect(@NonNull Document doc, int offset, @NullAllowed 
Context context, @NonNull Consumer<SignatureInformation> consumer) {
+        MimePath mimePath = MimePath.parse(DocumentUtilities.getMimeType(doc));
+        for (SignatureInformationCollector collector : 
MimeLookup.getLookup(mimePath).lookupAll(SignatureInformationCollector.class)) {
+            collector.collectSignatureInformation(doc, offset, context, 
consumer);
+        }
+    }
+
+    /**
+     * Represents a parameter of a callable-signature. A parameter can
+     * have a label and a doc-comment.
+     *
+     * @since 1.20
+     */
+    public static final class ParameterInformation {
+
+        private final String label;
+        private final boolean isActive;
+        private final String documentation;
+
+        private ParameterInformation(String label, boolean isActive, String 
documentation) {
+            this.label = label;
+            this.isActive = isActive;
+            this.documentation = documentation;
+        }
+
+        /**
+        * The label of this parameter information.
+        * <p>
+        * <i>Note</i>: a label should be a substring of its containing
+        * signature label. Its intended use case is to highlight the parameter
+        * label part in the {@code SignatureInformation.label}.
+         *
+         * @since 1.20
+        */
+        @NonNull
+        public String getLabel() {
+            return label;
+        }
+
+        /**
+         * Returns true if the parameter is active.
+         *
+         * @since 1.20
+         */
+        public boolean isActive() {
+            return isActive;
+        }
+
+        /**
+         * A human-readable string that represents a doc-comment. An HTML 
format is
+         * supported.
+         *
+         * @since 1.20
+         */
+        @CheckForNull
+        public String getDocumentation() {
+            return documentation;
+        }
+    }
+
+    /**
+     * Additional information about the context in which a signature help 
request
+     * was triggered.
+     *
+     * @since 1.20
+     */
+    public static final class Context {
+
+        private final TriggerKind triggerKind;
+        private final Character triggerCharacter;
+
+        public Context(@NonNull TriggerKind triggerKind, @NullAllowed 
Character triggerCharacter) {
+            this.triggerKind = triggerKind;
+            this.triggerCharacter = triggerCharacter;
+        }
+
+        /**
+         * Action that caused signature help to be triggered.
+         *
+         * @since 1.20
+         */
+        @NonNull
+        public TriggerKind getTriggerKind() {
+            return triggerKind;
+        }
+
+        /**
+         * Character that caused signature help to be triggered.
+         * Is undefined if {@code triggerKind != TriggerKind.TriggerCharacter}.
+         *
+         * @since 1.20
+         */
+        @CheckForNull
+        public Character getTriggerCharacter() {
+            return triggerCharacter;
+        }
+    }
+
+    /**
+     * Specifies how a signature help was triggered.
+     *
+     * @since 1.20
+     */
+    public enum TriggerKind {
+
+        /**
+         * Signature help was invoked manually by the user or by a command.
+         *
+         * @since 1.20
+         */
+        Invoked,
+
+        /**
+         * Signature help was triggered by a trigger character.
+         *
+         * @since 1.20
+         */
+        TriggerCharacter,
+
+        /**
+         * Signature help was triggered by the cursor moving or by the document
+        * content changing.
+         *
+         * @since 1.20
+         */
+        ContentChange
+    }
+}
diff --git 
a/ide/api.lsp/src/org/netbeans/modules/lsp/SignatureInformationAccessor.java 
b/ide/api.lsp/src/org/netbeans/modules/lsp/SignatureInformationAccessor.java
new file mode 100644
index 0000000000..be0e9f87c9
--- /dev/null
+++ b/ide/api.lsp/src/org/netbeans/modules/lsp/SignatureInformationAccessor.java
@@ -0,0 +1,57 @@
+/*
+ * 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.lsp;
+
+import java.util.List;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.lsp.SignatureInformation;
+import org.openide.util.Exceptions;
+import org.openide.util.Parameters;
+
+
+public abstract class SignatureInformationAccessor {
+
+    private static volatile SignatureInformationAccessor DEFAULT;
+
+    public static synchronized SignatureInformationAccessor getDefault() {
+        SignatureInformationAccessor instance = DEFAULT;
+        if (instance == null) {
+            Class<?> c = SignatureInformation.class;
+            try {
+                Class.forName(c.getName(), true, c.getClassLoader());
+                instance = DEFAULT;
+                assert instance != null;
+            } catch (Exception ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return instance;
+    }
+
+    public static void setDefault(@NonNull final SignatureInformationAccessor 
accessor) {
+        Parameters.notNull("accessor", accessor);   //NOI18N
+        if (DEFAULT != null) {
+            throw new IllegalStateException("Accessor already initialized");
+        }
+        DEFAULT = accessor;
+    }
+
+    public abstract SignatureInformation createSignatureInformation(String 
label, List<SignatureInformation.ParameterInformation> params, boolean 
isActive, String documentation);
+    public abstract SignatureInformation.ParameterInformation 
createParameterInformation(String label, boolean isActive, String 
documentation);
+}
diff --git 
a/ide/api.lsp/src/org/netbeans/spi/lsp/SignatureInformationCollector.java 
b/ide/api.lsp/src/org/netbeans/spi/lsp/SignatureInformationCollector.java
new file mode 100644
index 0000000000..3205202425
--- /dev/null
+++ b/ide/api.lsp/src/org/netbeans/spi/lsp/SignatureInformationCollector.java
@@ -0,0 +1,132 @@
+/*
+ * 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.spi.lsp;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import javax.swing.text.Document;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.annotations.common.NullAllowed;
+import org.netbeans.api.lsp.SignatureInformation;
+import org.netbeans.modules.lsp.SignatureInformationAccessor;
+import org.netbeans.spi.editor.mimelookup.MimeLocation;
+
+/**
+ * Interface for computing and collecting signature information. Clients can 
use
+ * this interface to collect signature information and send them for 
presentation
+ * outside of NetBeans using the Language Server Protocol. Implementations of 
the
+ * interface should be registered in MimeLookup.
+ * {@snippet :
+ *
+ *  {@code @}MimeRegistration(mimeType = "text/foo", service = 
SignatureInformationCollector.class)
+ *  public class FooSignatureInformationCollector implements 
SignatureInformationCollector {
+ *     ...
+ *  }
+ * }
+ *
+ * @author Dusan Balek
+ * @since 1.20
+ */
+@MimeLocation(subfolderName = "SignatureHelpProviders")
+public interface SignatureInformationCollector {
+
+    /**
+     * Computes and collects signature information for a document at a given 
offset.
+     *
+     * @param doc a text document
+     * @param offset an offset inside the text document
+     * @param context an optional signature help context
+     * @param consumer an operation accepting collected signature information
+     *
+     *
+     * @since 1.0
+     */
+    public void collectSignatureInformation(@NonNull Document doc, int offset, 
@NullAllowed SignatureInformation.Context context, @NonNull 
Consumer<SignatureInformation> consumer);
+
+    /**
+     * Creates a builder for {@link SignatureInformation} instances.
+     *
+     * @param label the label of the signature information
+     * @param isActive true if the signature is active
+     * @return newly created builder
+     *
+     * @since 1.20
+     */
+    public static Builder newBuilder(@NonNull String label, boolean isActive) {
+        return new Builder(label, isActive);
+    }
+
+    /**
+     * Builder for {@link SignatureInformation} instances. Its usage can be 
illustrated by:
+     * {@snippet file="org/netbeans/api/lsp/SignatureInformationTest.java" 
region="builder"}
+     *
+     * @since 1.20
+     */
+    public static final class Builder {
+
+        private final String label;
+        private final List<SignatureInformation.ParameterInformation> params;
+        private final boolean isActive;
+        private String documentation;
+
+        private Builder(@NonNull String label, boolean isActive) {
+            this.label = label;
+            this.isActive = isActive;
+            this.params = new ArrayList<>();
+        }
+
+        /**
+         * A human-readable string that represents a doc-comment. An HTML 
format
+         * is supported.
+         *
+         * @since 1.20
+         */
+        @NonNull
+        public Builder documentation(@NonNull String documentation) {
+            this.documentation = documentation;
+            return this;
+        }
+
+        /**
+         * Adds parameter information to this signature.
+         *
+         * @param label label of the parameter information
+         * @param isActive true if the the parameter is active
+         * @param documentation an optional doc-comment of the parameter
+         *
+         * @since 1.20
+         */
+        @NonNull
+        public Builder addParameter(@NonNull String label, boolean isActive, 
@NullAllowed String documentation) {
+            
this.params.add(SignatureInformationAccessor.getDefault().createParameterInformation(label,
 isActive, documentation));
+            return this;
+        }
+
+        /**
+         * Builds signature information.
+         *
+         * @since 1.20
+         */
+        @NonNull
+        public SignatureInformation build() {
+            return 
SignatureInformationAccessor.getDefault().createSignatureInformation(label, 
params, isActive, documentation);
+        }
+    }
+}
diff --git 
a/ide/api.lsp/test/unit/src/org/netbeans/api/lsp/SignatureInformationTest.java 
b/ide/api.lsp/test/unit/src/org/netbeans/api/lsp/SignatureInformationTest.java
new file mode 100644
index 0000000000..21ebf4fe4e
--- /dev/null
+++ 
b/ide/api.lsp/test/unit/src/org/netbeans/api/lsp/SignatureInformationTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.api.lsp;
+
+import java.util.List;
+import java.util.function.Consumer;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DefaultStyledDocument;
+import javax.swing.text.Document;
+import org.netbeans.api.editor.mimelookup.MimePath;
+import org.netbeans.api.editor.mimelookup.test.MockMimeLookup;
+import org.netbeans.junit.NbTestCase;
+import org.netbeans.spi.lsp.SignatureInformationCollector;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+public class SignatureInformationTest extends NbTestCase {
+
+    public SignatureInformationTest(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setUp () throws Exception {
+        super.setUp();
+        clearWorkDir();
+        MockMimeLookup.setInstances (MimePath.get ("text/foo"), new 
FooSignatureInformationCollector());
+    }
+
+    public void testSignatureInformationCollect() {
+        Document doc = createDocument("text/foo", "");
+        int offset = 0;
+        // @start region="testSignatureInformationCollect"
+
+        // Compute and collect signature information for a document at a given 
offset
+        SignatureInformation.collect(doc, offset, null, signature -> {
+
+            // signature should never be 'null'
+            assertNotNull(signature);
+
+            // getting signature 'label'
+            String label = signature.getLabel();
+            assertEquals("label", label);
+
+            // check if the signature is active
+            assertTrue(signature.isActive());
+
+            // getting signature 'parameters'
+            List<SignatureInformation.ParameterInformation> params = 
signature.getParameters();
+            // check number of parameters
+            assertEquals(2, params.size());
+            for (int i = 0; i < params.size(); i++) {
+                SignatureInformation.ParameterInformation param = 
params.get(i);
+                // getting parameter 'label'
+                String paramLabel = param.getLabel();
+                assertEquals("param" + i, paramLabel);
+                // check if the parameter is active
+                if (i == 1) {
+                    assertTrue(param.isActive());
+                } else {
+                    assertFalse(param.isActive());
+                }
+                // getting optional parameter 'documentation'
+                String paramDocumentation = param.getDocumentation();
+                assertEquals("param" + i + " documentation", 
paramDocumentation);
+            }
+
+            // getting optional signature 'documentation'
+            String documentation = signature.getDocumentation();
+            assertEquals("documentation", documentation);
+        });
+
+        // @end region="testSignatureInformationCollect"
+    }
+
+    private Document createDocument(String mimeType, String contents) {
+        Document doc = new DefaultStyledDocument();
+        doc.putProperty("mimeType", mimeType);
+        try {
+            doc.insertString(0, contents, null);
+            return doc;
+        } catch (BadLocationException ble) {
+            throw new IllegalStateException(ble);
+        }
+    }
+
+    private static class FooSignatureInformationCollector implements 
SignatureInformationCollector {
+
+        @Override
+        public void collectSignatureInformation(Document doc, int offset, 
SignatureInformation.Context context, Consumer<SignatureInformation> consumer) {
+            // @start region="builder"
+
+            // Create a builder for creating 'SignatureInformation' instance 
providing its 'label' and 'isActive' flag
+            SignatureInformation si = 
SignatureInformationCollector.newBuilder("label", true)
+
+                    // add signature parameters
+                    .addParameter("param0", false, "param0 documentation")
+                    .addParameter("param1", true, "param1 documentation")
+
+                    // set signature documentation
+                    .documentation("documentation")
+
+                    // create a new 'SignatureInformation' instance
+                    .build();
+
+            // @end region="builder"
+            consumer.accept(si);
+        }
+    }
+}
diff --git a/java/java.completion/nbproject/project.properties 
b/java/java.completion/nbproject/project.properties
index a59952644c..4540e613f7 100644
--- a/java/java.completion/nbproject/project.properties
+++ b/java/java.completion/nbproject/project.properties
@@ -17,7 +17,7 @@
 is.autoload=true
 javac.source=1.8
 javac.compilerargs=-Xlint -Xlint:-serial
-spec.version.base=2.7.0
+spec.version.base=2.8.0
 #test configs
 
 test.config.jet-main.includes=\
diff --git 
a/java/java.completion/src/org/netbeans/modules/java/completion/JavaTooltipTask.java
 
b/java/java.completion/src/org/netbeans/modules/java/completion/JavaTooltipTask.java
index 10149c3d09..0ee74aa066 100644
--- 
a/java/java.completion/src/org/netbeans/modules/java/completion/JavaTooltipTask.java
+++ 
b/java/java.completion/src/org/netbeans/modules/java/completion/JavaTooltipTask.java
@@ -21,7 +21,6 @@ package org.netbeans.modules.java.completion;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.Callable;
@@ -42,7 +41,6 @@ import com.sun.source.util.Trees;
 
 import org.netbeans.api.annotations.common.NullAllowed;
 import org.netbeans.api.java.source.*;
-import org.openide.util.NbBundle;
 
 /**
  *
@@ -60,7 +58,9 @@ public final class JavaTooltipTask extends BaseTask {
 
     private int anchorOffset;
     private List<List<String>> toolTipData;
+    private List<String> toolTipSignatures;
     private int toolTipIndex;
+    private int activeSignatureIndex;
     private int toolTipOffset;
 
     private JavaTooltipTask(final int caretOffset, final Callable<Boolean> 
cancel) {
@@ -71,10 +71,18 @@ public final class JavaTooltipTask extends BaseTask {
         return toolTipData;
     }
 
+    public List<String> getTooltipSignatures() {
+        return toolTipSignatures;
+    }
+
     public int getTooltipIndex() {
         return toolTipIndex;
     }
 
+    public int getActiveSignatureIndex() {
+        return activeSignatureIndex;
+    }
+
     public int getAnchorOffset() {
         return anchorOffset;
     }
@@ -85,7 +93,7 @@ public final class JavaTooltipTask extends BaseTask {
 
     @Override
     protected void resolve(CompilationController controller) throws 
IOException {
-        Env env = getCompletionEnvironment(controller, true);
+        Env env = getCompletionEnvironment(controller, false);
         if (env == null) {
             return;
         }
@@ -102,18 +110,19 @@ public final class JavaTooltipTask extends BaseTask {
                 List<Tree> argTypes = getArgumentsUpToPos(env, 
mi.getArguments(), (int) sourcePositions.getEndPosition(root, 
mi.getMethodSelect()), startPos, false);
                 if (argTypes != null) {
                     controller.toPhase(JavaSource.Phase.RESOLVED);
+                    final Trees trees = controller.getTrees();
                     TypeMirror[] types = new TypeMirror[argTypes.size()];
                     int j = 0;
                     for (Tree t : argTypes) {
-                        types[j++] = controller.getTrees().getTypeMirror(new 
TreePath(path, t));
+                        types[j++] = trees.getTypeMirror(new TreePath(path, 
t));
                     }
-                    Tree mid = mi.getMethodSelect();
+                    final Tree mid = mi.getMethodSelect();
+                    final Element activeElement = trees.getElement(path);
                     path = new TreePath(path, mid);
                     switch (mid.getKind()) {
                         case MEMBER_SELECT: {
                             ExpressionTree exp = ((MemberSelectTree) 
mid).getExpression();
                             path = new TreePath(path, exp);
-                            final Trees trees = controller.getTrees();
                             final TypeMirror type = trees.getTypeMirror(path);
                             final Element element = trees.getElement(path);
                             final boolean isStatic = element != null && 
(element.getKind().isClass() || element.getKind().isInterface() || 
element.getKind() == TYPE_PARAMETER);
@@ -127,13 +136,12 @@ public final class JavaTooltipTask extends BaseTask {
                                     return (!isStatic || 
e.getModifiers().contains(STATIC) || e.getKind() == CONSTRUCTOR) && 
(t.getKind() != TypeKind.DECLARED || trees.isAccessible(scope, e, 
(DeclaredType) (isSuperCall && enclType != null ? enclType : t)));
                                 }
                             };
-                            toolTipData = getMatchingParams(controller, type, 
controller.getElementUtilities().getMembers(type, acceptor), 
((MemberSelectTree) mid).getIdentifier().toString(), types, 
controller.getTypes());
+                            handleMatchingParams(controller, type, 
activeElement, controller.getElementUtilities().getMembers(type, acceptor), 
((MemberSelectTree) mid).getIdentifier().toString(), types);
                             break;
                         }
                         case IDENTIFIER: {
                             final Scope scope = env.getScope();
                             final TreeUtilities tu = 
controller.getTreeUtilities();
-                            final Trees trees = controller.getTrees();
                             final TypeElement enclClass = 
scope.getEnclosingClass();
                             final boolean isStatic = enclClass != null ? 
(tu.isStaticContext(scope) || (env.getPath().getLeaf().getKind() == 
Tree.Kind.BLOCK && ((BlockTree) env.getPath().getLeaf()).isStatic())) : false;
                             ElementUtilities.ElementAcceptor acceptor = new 
ElementUtilities.ElementAcceptor() {
@@ -152,12 +160,12 @@ public final class JavaTooltipTask extends BaseTask {
                             String name = ((IdentifierTree) 
mid).getName().toString();
                             if (SUPER_KEYWORD.equals(name) && enclClass != 
null) {
                                 TypeMirror superclass = 
enclClass.getSuperclass();
-                                toolTipData = getMatchingParams(controller, 
superclass, controller.getElementUtilities().getMembers(superclass, acceptor), 
INIT, types, controller.getTypes());
+                                handleMatchingParams(controller, superclass, 
activeElement, controller.getElementUtilities().getMembers(superclass, 
acceptor), INIT, types);
                             } else if (THIS_KEYWORD.equals(name) && enclClass 
!= null) {
                                 TypeMirror thisclass = enclClass.asType();
-                                toolTipData = getMatchingParams(controller, 
thisclass, controller.getElementUtilities().getMembers(thisclass, acceptor), 
INIT, types, controller.getTypes());
+                                handleMatchingParams(controller, thisclass, 
activeElement, controller.getElementUtilities().getMembers(thisclass, 
acceptor), INIT, types);
                             } else {
-                                toolTipData = getMatchingParams(controller, 
enclClass != null ? enclClass.asType() : null, 
controller.getElementUtilities().getLocalMembersAndVars(scope, acceptor), name, 
types, controller.getTypes());
+                                handleMatchingParams(controller, enclClass != 
null ? enclClass.asType() : null, activeElement, 
controller.getElementUtilities().getLocalMembersAndVars(scope, acceptor), name, 
types);
                             }
                             break;
                         }
@@ -183,13 +191,14 @@ public final class JavaTooltipTask extends BaseTask {
                 List<Tree> argTypes = getArgumentsUpToPos(env, 
nc.getArguments(), pos, startPos, false);
                 if (argTypes != null) {
                     controller.toPhase(JavaSource.Phase.RESOLVED);
+                    final Trees trees = controller.getTrees();
                     TypeMirror[] types = new TypeMirror[argTypes.size()];
                     int j = 0;
                     for (Tree t : argTypes) {
-                        types[j++] = controller.getTrees().getTypeMirror(new 
TreePath(path, t));
+                        types[j++] = trees.getTypeMirror(new TreePath(path, 
t));
                     }
+                    final Element activeElement = trees.getElement(path);
                     path = new TreePath(path, nc.getIdentifier());
-                    final Trees trees = controller.getTrees();
                     TypeMirror type = trees.getTypeMirror(path);
                     if (type != null && type.getKind() == TypeKind.ERROR && 
path.getLeaf().getKind() == Tree.Kind.PARAMETERIZED_TYPE) {
                         path = new TreePath(path, ((ParameterizedTypeTree) 
path.getLeaf()).getType());
@@ -204,7 +213,7 @@ public final class JavaTooltipTask extends BaseTask {
                             return e.getKind() == CONSTRUCTOR && 
(trees.isAccessible(scope, e, (DeclaredType) t) || isAnonymous && 
e.getModifiers().contains(PROTECTED));
                         }
                     };
-                    toolTipData = getMatchingParams(controller, type, 
controller.getElementUtilities().getMembers(type, acceptor), INIT, types, 
controller.getTypes());
+                    handleMatchingParams(controller, type, activeElement, 
controller.getElementUtilities().getMembers(type, acceptor), INIT, types);
                     toolTipIndex = types.length;
                     if (pos < 0) {
                         path = path.getParentPath();
@@ -226,9 +235,12 @@ public final class JavaTooltipTask extends BaseTask {
         }
     }
 
-    private List<List<String>> getMatchingParams(CompilationInfo info, 
TypeMirror type, Iterable<? extends Element> elements, String name, 
TypeMirror[] argTypes, Types types) {
-        List<List<String>> ret = new ArrayList<>();
+    private void handleMatchingParams(CompilationInfo info, TypeMirror type, 
Element activeElement, Iterable<? extends Element> elements, String name, 
TypeMirror[] argTypes) {
+        List<List<String>> data = new ArrayList<>();
+        List<String> signatures = new ArrayList<>();
+        Types types = info.getTypes();
         TypeUtilities tu = info.getTypeUtilities();
+        activeSignatureIndex = 0;
         for (Element e : elements) {
             if ((e.getKind() == CONSTRUCTOR || e.getKind() == METHOD) && 
name.contentEquals(e.getSimpleName())) {
                 List<? extends VariableElement> params = ((ExecutableElement) 
e).getParameters();
@@ -237,10 +249,19 @@ public final class JavaTooltipTask extends BaseTask {
                 if (!varArgs && (parSize < argTypes.length)) {
                     continue;
                 }
+                if (e == activeElement) {
+                    activeSignatureIndex = signatures.size();
+                }
+                ExecutableType eType = (ExecutableType) asMemberOf(e, type, 
types);
+                StringBuilder sig = new StringBuilder(INIT.equals(name) && 
type != null && type.getKind() == TypeKind.DECLARED ? ((DeclaredType) 
type).asElement().getSimpleName() : name).append('(');
                 if (parSize == 0) {
-                    
ret.add(Collections.<String>singletonList(NbBundle.getMessage(JavaCompletionTask.class,
 "JCP-no-parameters")));
+                    data.add(new ArrayList<>());
+                    sig.append(')');
+                    if (e.getKind() == METHOD) {
+                        sig.append(" : 
").append(tu.getTypeName(eType.getReturnType()));
+                    }
+                    signatures.add(sig.toString());
                 } else {
-                    ExecutableType eType = (ExecutableType) asMemberOf(e, 
type, types);
                     Iterator<? extends TypeMirror> parIt = 
eType.getParameterTypes().iterator();
                     TypeMirror param = null;
                     for (int i = 0; i <= argTypes.length; i++) {
@@ -258,21 +279,29 @@ public final class JavaTooltipTask extends BaseTask {
                             for (Iterator<? extends VariableElement> it = 
params.iterator(); it.hasNext();) {
                                 VariableElement ve = it.next();
                                 StringBuilder sb = new StringBuilder();
-                                sb.append(tu.getTypeName(tIt.next()));
+                                CharSequence typeName = 
tu.getTypeName(tIt.next());
+                                sb.append(typeName);
+                                sig.append(typeName);
                                 if (varArgs && !tIt.hasNext()) {
                                     sb.delete(sb.length() - 2, 
sb.length()).append("..."); //NOI18N
+                                    sig.delete(sig.length() - 2, 
sig.length()).append("..."); //NOI18N
                                 }
                                 CharSequence veName = ve.getSimpleName();
                                 if (veName != null && veName.length() > 0) {
-                                    sb.append(" "); // NOI18N
-                                    sb.append(veName);
+                                    sb.append(" ").append(veName); // NOI18N
+                                    sig.append(" ").append(veName); // NOI18N
                                 }
                                 if (it.hasNext()) {
-                                    sb.append(", "); // NOI18N
+                                    sig.append(", "); // NOI18N
                                 }
                                 paramStrings.add(sb.toString());
                             }
-                            ret.add(paramStrings);
+                            data.add(paramStrings);
+                            sig.append(')');
+                            if (e.getKind() == METHOD) {
+                                sig.append(" : 
").append(tu.getTypeName(eType.getReturnType()));
+                            }
+                            signatures.add(sig.toString());
                             break;
                         }
                         if (argTypes[i] == null || argTypes[i].getKind() != 
TypeKind.ERROR && !isAssignable(types, argTypes[i], param)) {
@@ -282,7 +311,8 @@ public final class JavaTooltipTask extends BaseTask {
                 }
             }
         }
-        return ret.isEmpty() ? null : ret;
+        toolTipData = data.isEmpty() ? null : data;
+        toolTipSignatures = signatures.isEmpty() ? null : signatures;
     }
 
     private static boolean isAssignable(Types types, TypeMirror arg, 
TypeMirror parameter) {
diff --git a/java/java.editor/nbproject/project.xml 
b/java/java.editor/nbproject/project.xml
index 132d3c9ffe..e3b37e4895 100644
--- a/java/java.editor/nbproject/project.xml
+++ b/java/java.editor/nbproject/project.xml
@@ -58,7 +58,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.17</specification-version>
+                        <specification-version>1.20</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
@@ -207,7 +207,7 @@
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>1.8</specification-version>
+                        <specification-version>2.8</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git 
a/java/java.editor/src/org/netbeans/modules/editor/java/JavaSignatureInformationCollector.java
 
b/java/java.editor/src/org/netbeans/modules/editor/java/JavaSignatureInformationCollector.java
new file mode 100644
index 0000000000..943b14f0eb
--- /dev/null
+++ 
b/java/java.editor/src/org/netbeans/modules/editor/java/JavaSignatureInformationCollector.java
@@ -0,0 +1,66 @@
+/*
+ * 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.editor.java;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Consumer;
+import javax.swing.text.Document;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.api.lsp.SignatureInformation;
+import org.netbeans.modules.java.completion.JavaTooltipTask;
+import org.netbeans.modules.parsing.api.ParserManager;
+import org.netbeans.modules.parsing.api.Source;
+import org.netbeans.modules.parsing.spi.ParseException;
+import org.netbeans.spi.lsp.SignatureInformationCollector;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@MimeRegistration(mimeType = "text/x-java", service = 
SignatureInformationCollector.class)
+public final class JavaSignatureInformationCollector implements 
SignatureInformationCollector {
+
+    @Override
+    public void collectSignatureInformation(Document doc, int offset, 
SignatureInformation.Context context, Consumer<SignatureInformation> consumer) {
+        if (context == null || context.getTriggerKind() != 
SignatureInformation.TriggerKind.TriggerCharacter || 
context.getTriggerCharacter() == '(') {
+            try {
+                JavaTooltipTask task = JavaTooltipTask.create(offset, () -> 
false);
+                
ParserManager.parse(Collections.singletonList(Source.create(doc)), task);
+                if (task.getTooltipData() != null && 
task.getTooltipSignatures() != null) {
+                    Iterator<List<String>> it = 
task.getTooltipData().iterator();
+                    for (int i = 0; i < task.getTooltipSignatures().size() && 
it.hasNext(); i++) {
+                        List<String> params = it.next();
+                        String signature = task.getTooltipSignatures().get(i);
+                        Builder builder = 
SignatureInformationCollector.newBuilder(signature, i == 
task.getActiveSignatureIndex());
+                        for (int j = 0; j < params.size(); j++) {
+                            String param = params.get(j);
+                            builder.addParameter(param, j == 
task.getTooltipIndex(), null);
+                        }
+                        consumer.accept(builder.build());
+                    }
+                }
+            } catch (ParseException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+    }
+}
diff --git 
a/java/java.editor/src/org/netbeans/modules/editor/java/MethodParamsTipPaintComponent.java
 
b/java/java.editor/src/org/netbeans/modules/editor/java/MethodParamsTipPaintComponent.java
index 565375de5d..2db9c3338c 100644
--- 
a/java/java.editor/src/org/netbeans/modules/editor/java/MethodParamsTipPaintComponent.java
+++ 
b/java/java.editor/src/org/netbeans/modules/editor/java/MethodParamsTipPaintComponent.java
@@ -24,6 +24,7 @@ import java.util.List;
 import javax.swing.*;
 import javax.swing.text.JTextComponent;
 import org.openide.awt.GraphicsUtils;
+import org.openide.util.NbBundle;
 
 /**
  *
@@ -91,8 +92,14 @@ public class MethodParamsTipPaintComponent extends JToolTip {
         if (params != null) {
             for (List<String> p : params) {
                 int i = 0;
+                if (p.isEmpty()) {
+                    
p.add(NbBundle.getMessage(MethodParamsTipPaintComponent.class, 
"JCP-no-parameters"));
+                }
                 int plen = p.size() - 1;
                 for (String s : p) {
+                    if (i < plen) {
+                        s += ", "; //NOI18N
+                    }
                     if (getWidth(s, i == idx || i == plen && idx > plen ? 
getDrawFont().deriveFont(Font.BOLD) : getDrawFont()) + drawX > screenWidth) {
                         drawY += fontHeight;
                         drawX = startX + getWidth("        ", drawFont); 
//NOI18N
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 ba01d4f530..f846fdb952 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
@@ -71,6 +71,7 @@ import org.eclipse.lsp4j.SemanticTokensParams;
 import org.eclipse.lsp4j.ServerCapabilities;
 import org.eclipse.lsp4j.SetTraceParams;
 import org.eclipse.lsp4j.ShowMessageRequestParams;
+import org.eclipse.lsp4j.SignatureHelpOptions;
 import org.eclipse.lsp4j.TextDocumentIdentifier;
 import org.eclipse.lsp4j.TextDocumentSyncKind;
 import org.eclipse.lsp4j.TextDocumentSyncOptions;
@@ -783,6 +784,10 @@ public final class Server {
                 completionOptions.setResolveProvider(true);
                 completionOptions.setTriggerCharacters(Arrays.asList(".", "#", 
"@", "*"));
                 capabilities.setCompletionProvider(completionOptions);
+                SignatureHelpOptions signatureHelpOptions = new 
SignatureHelpOptions();
+                signatureHelpOptions.setTriggerCharacters(Arrays.asList("("));
+                
signatureHelpOptions.setRetriggerCharacters(Arrays.asList(","));
+                capabilities.setSignatureHelpProvider(signatureHelpOptions);
                 capabilities.setHoverProvider(true);
                 CodeActionOptions codeActionOptions = new 
CodeActionOptions(Arrays.asList(CodeActionKind.QuickFix, CodeActionKind.Source, 
CodeActionKind.SourceOrganizeImports, CodeActionKind.Refactor));
                 codeActionOptions.setResolveProvider(true);
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
index 7401da39a2..b6c4a4b9f2 100644
--- 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
@@ -126,6 +126,7 @@ import org.eclipse.lsp4j.LocationLink;
 import org.eclipse.lsp4j.MarkupContent;
 import org.eclipse.lsp4j.MessageParams;
 import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.ParameterInformation;
 import org.eclipse.lsp4j.Position;
 import org.eclipse.lsp4j.PrepareRenameParams;
 import org.eclipse.lsp4j.PrepareRenameResult;
@@ -144,6 +145,7 @@ import 
org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
 import org.eclipse.lsp4j.ServerCapabilities;
 import org.eclipse.lsp4j.SignatureHelp;
 import org.eclipse.lsp4j.SignatureHelpParams;
+import org.eclipse.lsp4j.SignatureInformation;
 import org.eclipse.lsp4j.SymbolInformation;
 import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
 import org.eclipse.lsp4j.TextDocumentEdit;
@@ -580,7 +582,51 @@ public class TextDocumentServiceImpl implements 
TextDocumentService, LanguageCli
 
     @Override
     public CompletableFuture<SignatureHelp> signatureHelp(SignatureHelpParams 
params) {
-        throw new UnsupportedOperationException("Not supported yet.");
+        // shortcut: if the projects are not yet initialized, return empty:
+        if (server.openedProjects().getNow(null) == null) {
+            return CompletableFuture.completedFuture(null);
+        }
+        String uri = params.getTextDocument().getUri();
+        FileObject file = fromURI(uri);
+        Document rawDoc = server.getOpenedDocuments().getDocument(uri);
+        if (file == null || !(rawDoc instanceof StyledDocument)) {
+            return CompletableFuture.completedFuture(null);
+        }
+        StyledDocument doc = (StyledDocument) rawDoc;
+        List<SignatureInformation> signatures = new ArrayList<>();
+        AtomicInteger activeSignature = new AtomicInteger(-1);
+        AtomicInteger activeParameter = new AtomicInteger(-1);
+        org.netbeans.api.lsp.SignatureInformation.collect(doc, 
Utils.getOffset(doc, params.getPosition()), null, signature -> {
+            SignatureInformation signatureInformation = new 
SignatureInformation(signature.getLabel());
+            List<ParameterInformation> parameters = new 
ArrayList<>(signature.getParameters().size());
+            for (int i = 0; i < signature.getParameters().size(); i++) {
+                org.netbeans.api.lsp.SignatureInformation.ParameterInformation 
parameter = signature.getParameters().get(i);
+                ParameterInformation parameterInformation = new 
ParameterInformation(parameter.getLabel());
+                if (parameter.getDocumentation() != null) {
+                    MarkupContent markup = new MarkupContent();
+                    markup.setKind("markdown");
+                    markup.setValue(html2MD(parameter.getDocumentation()));
+                    parameterInformation.setDocumentation(markup);
+                }
+                parameters.add(parameterInformation);
+                if (signatureInformation.getActiveParameter() == null && 
parameter.isActive()) {
+                    signatureInformation.setActiveParameter(i);
+                }
+            }
+            if (signature.getDocumentation() != null) {
+                MarkupContent markup = new MarkupContent();
+                markup.setKind("markdown");
+                markup.setValue(html2MD(signature.getDocumentation()));
+                signatureInformation.setDocumentation(markup);
+            }
+            signatureInformation.setParameters(parameters);
+            if (activeSignature.get() < 0 && signature.isActive()) {
+                activeSignature.set(signatures.size());
+                activeParameter.set(signatureInformation.getActiveParameter());
+            }
+            signatures.add(signatureInformation);
+        });
+        return CompletableFuture.completedFuture(signatures.isEmpty() ? null : 
new SignatureHelp(signatures, activeSignature.get(), activeParameter.get()));
     }
 
     @Override
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 84867cfa97..8f657a9b2b 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
@@ -53,7 +53,6 @@ import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -109,6 +108,7 @@ import org.eclipse.lsp4j.Location;
 import org.eclipse.lsp4j.MarkupContent;
 import org.eclipse.lsp4j.MessageActionItem;
 import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.ParameterInformation;
 import org.eclipse.lsp4j.Position;
 import org.eclipse.lsp4j.ProgressParams;
 import org.eclipse.lsp4j.PublishDiagnosticsParams;
@@ -126,6 +126,9 @@ import org.eclipse.lsp4j.ResourceOperationKind;
 import org.eclipse.lsp4j.ShowDocumentParams;
 import org.eclipse.lsp4j.ShowDocumentResult;
 import org.eclipse.lsp4j.ShowMessageRequestParams;
+import org.eclipse.lsp4j.SignatureHelp;
+import org.eclipse.lsp4j.SignatureHelpParams;
+import org.eclipse.lsp4j.SignatureInformation;
 import org.eclipse.lsp4j.SymbolInformation;
 import org.eclipse.lsp4j.TextDocumentClientCapabilities;
 import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
@@ -1391,6 +1394,65 @@ public class ServerTest extends NbTestCase {
                 "\n");
     }
 
+    public void testSignatureHelp() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "/**\n" +
+                      " * This is a test class with Javadoc.\n" +
+                      " */\n" +
+                      "public class Test {\n" +
+                      "    public static void main(String[] args) {\n" +
+                      "        System.out.println(\"len: \" + args.length);\n" 
+
+                      "    }\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        FileUtil.refreshFor(getWorkDir());
+        Launcher<LanguageServer> serverLauncher = 
createClientLauncherWithLogging(new LspClient() {
+            @Override
+            public void telemetryEvent(Object arg0) {
+            }
+
+            @Override
+            public void publishDiagnostics(PublishDiagnosticsParams params) {
+            }
+
+            @Override
+            public void showMessage(MessageParams arg0) {
+            }
+
+            @Override
+            public CompletableFuture<MessageActionItem> 
showMessageRequest(ShowMessageRequestParams arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void logMessage(MessageParams arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        InitializeResult result = server.initialize(new 
InitializeParams()).get();
+        assertNotNull(result.getCapabilities().getSignatureHelpProvider());
+        server.getTextDocumentService().didOpen(new 
DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
+        SignatureHelp help = server.getTextDocumentService().signatureHelp(new 
SignatureHelpParams(new TextDocumentIdentifier(toURI(src)), new Position(5, 
30))).get();
+        assertNotNull(help);
+        List<SignatureInformation> signatures = help.getSignatures();
+        assertNotNull(signatures);
+        SignatureInformation sInfo = signatures.stream().filter(si -> 
"println(String x) : void".equals(si.getLabel())).findFirst().get();
+        assertNotNull(sInfo);
+        assertEquals(signatures.indexOf(sInfo), 
help.getActiveSignature().intValue());
+        assertEquals(0, help.getActiveParameter().intValue());
+        List<ParameterInformation> params = sInfo.getParameters();
+        assertNotNull(params);
+        assertEquals(1, params.size());
+        assertTrue(params.get(0).getLabel().isLeft());
+        assertEquals("String x", params.get(0).getLabel().getLeft());
+        assertEquals(0, sInfo.getActiveParameter().intValue());
+    }
+
     public void testAdvancedCompletion1() throws Exception {
         String javaVersion = System.getProperty("java.specification.version");
         File src = new File(getWorkDir(), "Test.java");


---------------------------------------------------------------------
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