You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by ge...@apache.org on 2019/09/06 08:05:55 UTC

[netbeans] branch master updated: LSP Server for Java (#926)

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

geertjan 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 f740b00  LSP Server for Java (#926)
f740b00 is described below

commit f740b000ddbd8c69fca7b3a5957a69d9c6211bae
Author: Jan Lahoda <jl...@netbeans.org>
AuthorDate: Fri Sep 6 10:05:43 2019 +0200

    LSP Server for Java (#926)
    
    * A sketch of Java LSP server.
    
    * Moving the java.lsp.server module to a correct place.
    
    * Moving the java.lsp.server module to a new location.
    
    * Support for JDK 9+.
    
    * Upgrading to LSP4J 5.0
    
    * Adding support for code actions.
    
    * Fixing support for keywords in code completion.
    
    * Adding outline.
    
    * Client sends document content on didOpen.
    
    * Cleanup - providing a utility to get the appropriate JavaSource.
    
    * Adding support for go to declaration to the Java LSP server.
    
    * Adding VS Code extension.
    
    * Adjusting dependencies.
    
    * Starting to eliminate remaining usages of old javadoc API and implementation.
    
    * Improving the Java server for the LSP.
    
    * Ensure InstalledFileLocator works by setting cluster path.
    
    * Fixing java/javadoc's Analyzer after drop of usage of the old javadoc API.
    
    * Fixing run with OpenJDK project.
    
    * Resolving forgotten conflicts.
    
    * Automatically add requires.nb.javac when module has a dependency on javacapi.
    
    * Not exporting old javadoc API
    
    * Removing usage of old Javadoc API.
    
    * Adding MarkOccurrences.
    
    * [NETBEANS-2207]: Removing old javadoc api(com.sun.javadoc.**) fron nb-javac
    
    * Re-enabling the nb-javac classpath extensions, as the old javadoc API has been stripped from the nb-javac.
    
    * Fixing dependencies - the language client is a real dependency.
    
    * Ensure nb-javac is not used.
    
    * realpath seems to be unavailable on some systems, using readlink instead.
    
    * Querying source groups sets external source roots, ensuring it is called.
    
    * Using DiagnosticSeverity.Warning for Severity.VERIFIER.
    
    * Produce correct classpath for langtools tests in the current layout.
    
    * Running the Java language server inside module system.
    
    * Attempting to fix verify-libs-and-licenses.
    
    * Cleanup - undoing unnecessary changes; adding license headers.
    
    * More undoing of unnecessary changes
    
    * Correcting clusters list and configuration.
    
    * Adding license info about clusters configuration file (which cannot hold license by itself)
    
    * Avoiding a StackOverflowError in Tree copier when running on JDK 14 by calling the correct superclass method instead of the synthetic bridge.
    
    * Check suitable JDK is available, but show an error to the user if not.
    
    * Adding a rudimentary README.md
    
    * Adding a license header.
---
 .../netbeans/modules/editor/java/GoToSupport.java  | 106 ++-
 .../hints/infrastructure/ErrorHintsProvider.java   |   4 +-
 java/java.lsp.server/build.xml                     |  54 ++
 java/java.lsp.server/external/binaries-list        |  23 +
 .../external/lsp4j-0.5.0-license.txt               |  73 ++
 .../external/xtend-2.14.0-license.txt              |  73 ++
 java/java.lsp.server/licenseinfo.xml               |  35 +
 java/java.lsp.server/manifest.mf                   |   6 +
 java/java.lsp.server/nbproject/project.properties  |  30 +
 java/java.lsp.server/nbproject/project.xml         | 374 +++++++++
 java/java.lsp.server/script/bin/nb-java-lsp-server | 146 ++++
 .../script/etc/nb-java-lsp-server.clusters         |   4 +
 .../script/etc/nb-java-lsp-server.conf             |  68 ++
 .../modules/java/lsp/server/Bundle.properties      |  18 +
 .../netbeans/modules/java/lsp/server/Server.java   | 227 +++++
 .../lsp/server/text/TextDocumentServiceImpl.java   | 913 +++++++++++++++++++++
 .../lsp/server/workspace/WorkspaceServiceImpl.java |  50 ++
 .../modules/java/lsp/server/ServerTest.java        | 560 +++++++++++++
 java/java.lsp.server/vscode/.gitignore             |   3 +
 java/java.lsp.server/vscode/README.md              |  39 +
 java/java.lsp.server/vscode/package-lock.json      |  26 +
 java/java.lsp.server/vscode/package.json           |  51 ++
 java/java.lsp.server/vscode/src/VerifyJDK11.java   |  24 +
 java/java.lsp.server/vscode/src/extension.ts       |  79 ++
 java/java.lsp.server/vscode/tsconfig.json          |  20 +
 .../java/openjdk/jtreg/ClassPathProviderImpl.java  |   5 +-
 .../modules/java/openjdk/jtreg/Utilities.java      |  11 +-
 .../netbeans/api/java/source/ui/ElementOpen.java   |  12 +-
 .../java/source/ui/ElementOpenAccessor.java        |  52 ++
 .../lib/nbjavac/services/NBJavacTrees.java         |  14 +-
 .../org/netbeans/nbbuild/extlibs/ignored-overlaps  |   5 +
 nbbuild/cluster.properties                         |   1 +
 nbbuild/licenses/MIT-vscode-ext                    |  19 +
 nbbuild/licenses/names.properties                  |   1 +
 34 files changed, 3076 insertions(+), 50 deletions(-)

diff --git a/java/java.editor/src/org/netbeans/modules/editor/java/GoToSupport.java b/java/java.editor/src/org/netbeans/modules/editor/java/GoToSupport.java
index 1a68977..903420f 100644
--- a/java/java.editor/src/org/netbeans/modules/editor/java/GoToSupport.java
+++ b/java/java.editor/src/org/netbeans/modules/editor/java/GoToSupport.java
@@ -180,12 +180,8 @@ public class GoToSupport {
                 return ;
             }
             
-            final int[] offsetToOpen = new int[] {-1};
-            final ElementHandle[] elementToOpen = new ElementHandle[1];
-            final String[] displayNameForError = new String[1];
-            final boolean[] tryToOpen = new boolean[1];
-            final ClasspathInfo[] cpInfo = new ClasspathInfo[1];
-            
+            GoToTarget[] target = new GoToTarget[1];
+
             ParserManager.parse(Collections.singleton (Source.create(doc)), new UserTask() {
                 @Override
                 public void run(ResultIterator resultIterator) throws Exception {
@@ -197,12 +193,11 @@ public class GoToSupport {
                     if (controller == null || controller.toPhase(Phase.RESOLVED).compareTo(Phase.RESOLVED) < 0) {
                         return;
                     }
-                    cpInfo[0] = controller.getClasspathInfo();
 
                     Context resolved = resolveContext(controller, doc, offset, goToSource, false);
 
                     if (resolved == null) {
-                        CALLER.beep(goToSource, javadoc);
+                        target[0] = new GoToTarget(-1, null, null, null, false);
                         return;
                     }
                     
@@ -211,53 +206,34 @@ public class GoToSupport {
                         if (url != null) {
                             HtmlBrowser.URLDisplayer.getDefault().showURL(url);
                         } else {
-                            CALLER.beep(goToSource, javadoc);
+                            target[0] = new GoToTarget(-1, null, null, null, false);
                         }
                     } else {
-                        TreePath elpath = getPath(controller, resolved.resolved);
-                        
-                        if (elpath != null) {
-                            Tree tree = elpath.getLeaf();
-                            long startPos = controller.getTrees().getSourcePositions().getStartPosition(controller.getCompilationUnit(), tree);
-                            
-                            if (startPos != (-1)) {
-                                //check if the caret is inside the declaration itself, as jump in this case is not very usefull:
-                                if (isCaretInsideDeclarationName(controller, tree, elpath, offset)) {
-                                    CALLER.beep(goToSource, javadoc);
-                                } else {
-                                    //#71272: it is necessary to translate the offset:
-                                    offsetToOpen[0] = controller.getSnapshot().getOriginalOffset((int) startPos);
-                                    displayNameForError[0] = controller.getElementUtilities().getElementName(resolved.resolved, false).toString();
-                                    tryToOpen[0] = true;
-                                }
-                            } else {
-                                CALLER.beep(goToSource, javadoc);
-                            }
-                        } else {
-                            elementToOpen[0] = ElementHandle.create(resolved.resolved);
-                            displayNameForError[0] = controller.getElementUtilities().getElementName(resolved.resolved, false).toString();
-                            tryToOpen[0] = true;
-                        }
+                        target[0] = computeGoToTarget(controller, resolved, offset);
                     }
                 }
             });
             
-            if (tryToOpen[0]) {
+            if (target[0] != null) {
                 boolean openSucceeded = false;
 
                 if (cancel.get()) {
                     return ;
                 }
 
-                if (offsetToOpen[0] >= 0) {
-                    openSucceeded = CALLER.open(fo, offsetToOpen[0]);
+                if (!target[0].success)  {
+                    CALLER.beep(goToSource, javadoc);
                 } else {
-                    if (elementToOpen[0] != null) {
-                        openSucceeded = CALLER.open(cpInfo[0], elementToOpen[0]);
+                    if (target[0].offsetToOpen >= 0) {
+                        openSucceeded = CALLER.open(fo, target[0].offsetToOpen);
+                    } else {
+                        if (target[0].elementToOpen != null) {
+                            openSucceeded = CALLER.open(target[0].cpInfo, target[0].elementToOpen);
+                        }
+                    }
+                    if (!openSucceeded) {
+                        CALLER.warnCannotOpen(target[0].displayNameForError);
                     }
-                }
-                if (!openSucceeded) {
-                    CALLER.warnCannotOpen(displayNameForError[0]);
                 }
             }
         } catch (ParseException ex) {
@@ -265,6 +241,54 @@ public class GoToSupport {
         }
     }
     
+    public static GoToTarget computeGoToTarget(CompilationController controller, Context resolved, int offset) {
+        TreePath elpath = getPath(controller, resolved.resolved);
+
+        if (elpath != null) {
+            Tree tree = elpath.getLeaf();
+            long startPos = controller.getTrees().getSourcePositions().getStartPosition(controller.getCompilationUnit(), tree);
+
+            if (startPos != (-1)) {
+                //check if the caret is inside the declaration itself, as jump in this case is not very usefull:
+                if (isCaretInsideDeclarationName(controller, tree, elpath, offset)) {
+                    return new GoToTarget(-1, null, null, null, false);
+                } else {
+                    //#71272: it is necessary to translate the offset:
+                    return new GoToTarget(controller.getSnapshot().getOriginalOffset((int) startPos),
+                                          null,
+                                          null,
+                                          controller.getElementUtilities().getElementName(resolved.resolved, false).toString(),
+                                          true);
+                }
+            } else {
+                return new GoToTarget(-1, null, null, null, false);
+            }
+        } else {
+            return new GoToTarget(-1,
+                                  controller.getClasspathInfo(),
+                                  ElementHandle.create(resolved.resolved),
+                                  controller.getElementUtilities().getElementName(resolved.resolved, false).toString(),
+                                  true);
+        }
+    }
+
+    public static final class GoToTarget {
+        public final int offsetToOpen;
+        public final ClasspathInfo cpInfo;
+        public final ElementHandle elementToOpen;
+        public final String displayNameForError;
+        public final boolean success;
+
+        public GoToTarget(int offsetToOpen, ClasspathInfo cpInfo, ElementHandle elementToOpen, String displayNameForError, boolean success) {
+            this.offsetToOpen = offsetToOpen;
+            this.cpInfo = cpInfo;
+            this.elementToOpen = elementToOpen;
+            this.displayNameForError = displayNameForError;
+            this.success = success;
+        }
+
+    }
+
     public static void goTo(final Document doc, final int offset, final boolean goToSource) {
         performGoTo(doc, offset, goToSource, false);
     }
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/ErrorHintsProvider.java b/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/ErrorHintsProvider.java
index 04e7829..99d071d 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/ErrorHintsProvider.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/ErrorHintsProvider.java
@@ -91,7 +91,7 @@ public final class ErrorHintsProvider extends JavaParserResultTask {
     public static ErrorManager ERR = ErrorManager.getDefault().getInstance("org.netbeans.modules.java.hints"); // NOI18N
     public static Logger LOG = Logger.getLogger("org.netbeans.modules.java.hints"); // NOI18N
     
-    ErrorHintsProvider() {
+    public ErrorHintsProvider() {
         super(Phase.RESOLVED);
     }
     
@@ -109,7 +109,7 @@ public final class ErrorHintsProvider extends JavaParserResultTask {
     /**
      * @return errors for whole file
      */
-    List<ErrorDescription> computeErrors(CompilationInfo info, Document doc, String mimeType) throws IOException {
+    public List<ErrorDescription> computeErrors(CompilationInfo info, Document doc, String mimeType) throws IOException {
         return computeErrors(info, doc, null, mimeType);
     }
     
diff --git a/java/java.lsp.server/build.xml b/java/java.lsp.server/build.xml
new file mode 100644
index 0000000..a13d082
--- /dev/null
+++ b/java/java.lsp.server/build.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project basedir="." default="netbeans" name="java/java.lsp.server">
+    <description>Builds, tests, and runs the project org.netbeans.modules.java.lsp.server</description>
+    <import file="../../nbbuild/templates/projectized.xml"/>
+    <target name="build-lsp-server" depends="-set-use-jdk-javac,build,test-init">
+        <copy todir="${lsp.build.dir}/platform" failonerror="false">
+            <fileset dir="${netbeans.dest.dir}/platform" />
+        </copy>
+        <copy todir="${lsp.build.dir}/ide" failonerror="false">
+            <fileset dir="${netbeans.dest.dir}/ide" />
+        </copy>
+        <copy todir="${lsp.build.dir}/extide" failonerror="false">
+            <fileset dir="${netbeans.dest.dir}/extide" />
+        </copy>
+        <copy todir="${lsp.build.dir}/java" failonerror="false">
+            <fileset dir="${netbeans.dest.dir}/java" excludes="**/org-netbeans-modules-java-hints-test.jar,**/org-netbeans-modules-debugger-jpda-truffle.jar" />
+        </copy>
+        <mkdir dir="${lsp.build.dir}/bin" />
+        <copy todir="${lsp.build.dir}/bin" file="script/bin/nb-java-lsp-server" />
+        <mkdir dir="${lsp.build.dir}/etc" />
+        <copy todir="${lsp.build.dir}/etc" file="script/etc/nb-java-lsp-server.conf" />
+        <copy todir="${lsp.build.dir}/etc" file="script/etc/nb-java-lsp-server.clusters" />
+        <chmod file="${lsp.build.dir}/bin/nb-java-lsp-server" perm="u+x" />
+        <delete file="${lsp.build.dir}/lib/nb-javac-9-api.jar" />
+        <delete file="${lsp.build.dir}/lib/nb-javac-9-impl.jar" />
+        <delete file="${lsp.build.dir}/lib/org-netbeans-modules-java-source-nbjavac.jar" />
+    </target>
+    <target name="-set-use-jdk-javac">
+        <property name="test.use.jdk.javac" value="true" />
+    </target>
+    <target name="clean" depends="projectized.clean">
+        <delete dir="${lsp.build.dir}" />
+    </target>
+</project>
diff --git a/java/java.lsp.server/external/binaries-list b/java/java.lsp.server/external/binaries-list
new file mode 100644
index 0000000..ffd7e71
--- /dev/null
+++ b/java/java.lsp.server/external/binaries-list
@@ -0,0 +1,23 @@
+# 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.
+
+30A271A6787BA2A9E9DD3A453A480FFDCC3DEDC4 org.eclipse.lsp4j:org.eclipse.lsp4j:0.5.0
+23800C88EE887571B43A4F08041922107843E506 org.eclipse.lsp4j:org.eclipse.lsp4j.generator:0.5.0
+4FE4E91D47494B73DED7E420806661F7C36A1B05 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.5.0
+56459301B64C92BBBB6E13988B91CDBE1FC6264E org.eclipse.xtend:org.eclipse.xtend.lib:2.14.0
+F2FCA7A70C8A29374E79487417CEFB5F51A84867 org.eclipse.xtend:org.eclipse.xtend.lib.macro:2.14.0
+C40FFE7E5E6244DD094AE95009AA45072C629254 org.eclipse.xtext:org.eclipse.xtext.xbase.lib:2.14.0
diff --git a/java/java.lsp.server/external/lsp4j-0.5.0-license.txt b/java/java.lsp.server/external/lsp4j-0.5.0-license.txt
new file mode 100644
index 0000000..47af8f2
--- /dev/null
+++ b/java/java.lsp.server/external/lsp4j-0.5.0-license.txt
@@ -0,0 +1,73 @@
+Name: Eclipse Language Server Protocol Library
+Origin: Eclipse
+Version: 0.5.0
+License: EPL-v10
+URL: http://www.eclipse.org/
+Description: Eclipse Language Server Protocol Library
+Files: org.eclipse.lsp4j-0.5.0.jar org.eclipse.lsp4j.generator-0.5.0.jar org.eclipse.lsp4j.jsonrpc-0.5.0.jar
+
+Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+
+i) changes to the Program, and
+ii) additions to the Program;
+
+where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.
+"Contributor" means any person or entity that distributes the Program.
+"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.
+"Program" means the Contributions distributed in accordance with this Agreement.
+"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.
+b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contributio [...]
+c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted here [...]
+d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:
+a) it complies with the terms and conditions of this Agreement; and
+b) its license agreement:
+i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;
+ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;
+iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and
+iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.
+
+When the Program is made available in source code form:
+a) it must be made available under this Agreement; and
+b) a copy of this Agreement must be included with each copy of the Program.
+
+Contributors may not remove or alter any copyright notices contained within the Program.
+Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Con [...]
+
+For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims  [...]
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Ag [...]
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF [...]
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.
+All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Reci [...]
+Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the respons [...]
+This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.
diff --git a/java/java.lsp.server/external/xtend-2.14.0-license.txt b/java/java.lsp.server/external/xtend-2.14.0-license.txt
new file mode 100644
index 0000000..2158078
--- /dev/null
+++ b/java/java.lsp.server/external/xtend-2.14.0-license.txt
@@ -0,0 +1,73 @@
+Name: Eclipse Xtend library
+Origin: Eclipse
+Version: 2.14.0
+License: EPL-v10
+URL: http://www.eclipse.org/
+Description: Eclipse Xtend library
+Files: org.eclipse.xtend.lib-2.14.0.jar org.eclipse.xtend.lib.macro-2.14.0.jar org.eclipse.xtext.xbase.lib-2.14.0.jar
+
+Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+
+i) changes to the Program, and
+ii) additions to the Program;
+
+where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.
+"Contributor" means any person or entity that distributes the Program.
+"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.
+"Program" means the Contributions distributed in accordance with this Agreement.
+"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.
+b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contributio [...]
+c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted here [...]
+d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:
+a) it complies with the terms and conditions of this Agreement; and
+b) its license agreement:
+i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;
+ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;
+iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and
+iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.
+
+When the Program is made available in source code form:
+a) it must be made available under this Agreement; and
+b) a copy of this Agreement must be included with each copy of the Program.
+
+Contributors may not remove or alter any copyright notices contained within the Program.
+Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Con [...]
+
+For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims  [...]
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Ag [...]
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF [...]
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.
+All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Reci [...]
+Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the respons [...]
+This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.
diff --git a/java/java.lsp.server/licenseinfo.xml b/java/java.lsp.server/licenseinfo.xml
new file mode 100644
index 0000000..8e7d499
--- /dev/null
+++ b/java/java.lsp.server/licenseinfo.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<licenseinfo>
+    <fileset>
+        <file>vscode/package-lock.json</file>
+        <file>vscode/package.json</file>
+        <file>vscode/tsconfig.json</file>
+        <file>vscode/src/extension.ts</file>
+        <license ref="MIT-vscode-ext" />
+    </fileset>
+    <fileset>
+        <file>script/etc/nb-java-lsp-server.clusters</file>
+        <license ref="Apache-2.0-ASF" />
+        <comment type="COMMENT_UNSUPPORTED" />
+    </fileset>
+</licenseinfo>
diff --git a/java/java.lsp.server/manifest.mf b/java/java.lsp.server/manifest.mf
new file mode 100644
index 0000000..32384b0
--- /dev/null
+++ b/java/java.lsp.server/manifest.mf
@@ -0,0 +1,6 @@
+Manifest-Version: 1.0
+AutoUpdate-Show-In-Client: true
+OpenIDE-Module: org.netbeans.modules.java.lsp.server
+OpenIDE-Module-Implementation-Version: 1
+OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/java/lsp/server/Bundle.properties
+
diff --git a/java/java.lsp.server/nbproject/project.properties b/java/java.lsp.server/nbproject/project.properties
new file mode 100644
index 0000000..404cb0e
--- /dev/null
+++ b/java/java.lsp.server/nbproject/project.properties
@@ -0,0 +1,30 @@
+# 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.
+
+javac.source=1.8
+javac.compilerargs=-Xlint -Xlint:-serial
+spec.version.base=1.0
+requires.nb.javac=true
+lsp.build.dir=vscode/nb-java-lsp-server
+test.unit.run.cp.extra=
+
+release.external/org.eclipse.lsp4j-0.5.0.jar=modules/ext/org.eclipse.lsp4j-0.5.0.jar
+release.external/org.eclipse.lsp4j.generator-0.5.0.jar=modules/ext/org.eclipse.lsp4j.generator-0.5.0.jar
+release.external/org.eclipse.lsp4j.jsonrpc-0.5.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc-0.5.0.jar
+release.external/org.eclipse.xtend.lib-2.14.0.jar=modules/ext/org.eclipse.xtend.lib-2.14.0.jar
+release.external/org.eclipse.xtend.lib.macro-2.14.0.jar=modules/ext/org.eclipse.xtend.lib.macro-2.14.0.jar
+release.external/org.eclipse.xtext.xbase.lib-2.14.0.jar=modules/ext/org.eclipse.xtext.xbase.lib-2.14.0.jar
diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml
new file mode 100644
index 0000000..48f214e
--- /dev/null
+++ b/java/java.lsp.server/nbproject/project.xml
@@ -0,0 +1,374 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://www.netbeans.org/ns/project/1">
+    <type>org.netbeans.modules.apisupport.project</type>
+    <configuration>
+        <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
+            <code-name-base>org.netbeans.modules.java.lsp.server</code-name-base>
+            <module-dependencies>
+                <dependency>
+                    <code-name-base>com.google.gson</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency/>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.api.java.classpath</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.57</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.editor</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>3</release-version>
+                        <implementation-version/>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.editor.document</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>1.12</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.editor.lib</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>3</release-version>
+                        <specification-version>4.10</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.editor.mimelookup</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.43</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.java.completion</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <implementation-version/>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.java.editor</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <implementation-version/>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.java.editor.base</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <implementation-version/>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.java.hints</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <implementation-version/>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.java.source.base</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>2.42</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.java.sourceui</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <implementation-version/>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.lexer</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>2</release-version>
+                        <specification-version>1.66</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.parsing.api</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>9.10</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.parsing.indexing</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>9.13</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.projectapi</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.71</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.projectuiapi.base</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.89</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.refactoring.api</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>1.50</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.sendopts</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>2</release-version>
+                        <specification-version>2.41</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.spi.editor.hints</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>0</release-version>
+                        <specification-version>1.45</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.spi.java.hints</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <implementation-version/>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.filesystems</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>9.13</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.modules</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>7.51</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.nodes</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>7.48</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.text</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>6.70</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.util</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>9.10</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.util.lookup</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>8.36</specification-version>
+                    </run-dependency>
+                </dependency>
+            </module-dependencies>
+            <test-dependencies>
+                <test-type>
+                    <name>unit</name>
+                    <test-dependency>
+                        <code-name-base>com.google.gson</code-name-base>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>com.google.guava</code-name-base>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.bootstrap</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.core.startup</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.core.windows</code-name-base>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.libs.freemarker</code-name-base>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.libs.junit4</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.debugger.jpda.projects</code-name-base>
+                        <recursive/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.editor</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.editor.actions</code-name-base>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.editor.mimelookup.impl</code-name-base>
+                        <recursive/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.editor.tools.storage</code-name-base>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.java.j2seplatform</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.java.lexer</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.java.openjdk.project</code-name-base>
+                        <recursive/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.java.source</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.lexer.nbbridge</code-name-base>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.nbjunit</code-name-base>
+                        <recursive/>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.parsing.nb</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.projectapi.nb</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.projectui</code-name-base>
+                        <recursive/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.settings</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.spi.editor.hints.projects</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                </test-type>
+            </test-dependencies>
+            <public-packages/>
+            <class-path-extension>
+                <runtime-relative-path>ext/org.eclipse.lsp4j.generator-0.5.0.jar</runtime-relative-path>
+                <binary-origin>externel/org.eclipse.lsp4j.generator-0.5.0.jar</binary-origin>
+            </class-path-extension>
+            <class-path-extension>
+                <runtime-relative-path>ext/org.eclipse.lsp4j.jsonrpc-0.5.0.jar</runtime-relative-path>
+                <binary-origin>external/org.eclipse.lsp4j.jsonrpc-0.5.0.jar</binary-origin>
+            </class-path-extension>
+            <class-path-extension>
+                <runtime-relative-path>ext/org.eclipse.xtend.lib.macro-2.14.0.jar</runtime-relative-path>
+                <binary-origin>external/org.eclipse.xtend.lib.macro-2.14.0.jar</binary-origin>
+            </class-path-extension>
+            <class-path-extension>
+                <runtime-relative-path>ext/org.eclipse.xtend.lib-2.14.0.jar</runtime-relative-path>
+                <binary-origin>external/org.eclipse.xtend.lib-2.14.0.jar</binary-origin>
+            </class-path-extension>
+            <class-path-extension>
+                <runtime-relative-path>ext/org.eclipse.xtext.xbase.lib-2.14.0.jar</runtime-relative-path>
+                <binary-origin>external/org.eclipse.xtext.xbase.lib-2.14.0.jar</binary-origin>
+            </class-path-extension>
+            <class-path-extension>
+                <runtime-relative-path>ext/org.eclipse.lsp4j-0.5.0.jar</runtime-relative-path>
+                <binary-origin>external/org.eclipse.lsp4j-0.5.0.jar</binary-origin>
+            </class-path-extension>
+        </data>
+    </configuration>
+</project>
diff --git a/java/java.lsp.server/script/bin/nb-java-lsp-server b/java/java.lsp.server/script/bin/nb-java-lsp-server
new file mode 100755
index 0000000..326fe2d
--- /dev/null
+++ b/java/java.lsp.server/script/bin/nb-java-lsp-server
@@ -0,0 +1,146 @@
+#!/bin/sh
+
+# 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.
+
+#
+# resolve symlinks
+#
+
+PRG=$0
+
+while [ -h "$PRG" ]; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '^.*-> \(.*\)$' 2>/dev/null`
+    if expr "$link" : '^/' 2> /dev/null >/dev/null; then
+	PRG="$link"
+    else
+	PRG="`dirname "$PRG"`/$link"
+    fi
+done
+
+progdir=`dirname "$PRG"`
+APPNAME=`basename "$PRG"`
+case "`uname`" in
+    Darwin*)
+        # set default userdir and cachedir on Mac OS X
+        DEFAULT_USERDIR_ROOT="${HOME}/Library/Application Support/NetBeans/${APPNAME}"
+        DEFAULT_CACHEDIR_ROOT=${HOME}/Library/Caches/NetBeans/${APPNAME}
+        ;;
+    *) 
+        # set default userdir and cachedir on unix systems
+        DEFAULT_USERDIR_ROOT=${HOME}/.netbeans/${APPNAME}
+        DEFAULT_CACHEDIR_ROOT=${HOME}/.cache/netbeans/${APPNAME}
+        ;;
+esac
+
+if [ -f "$progdir/../etc/$APPNAME".conf ] ; then
+    . "$progdir/../etc/$APPNAME".conf
+fi
+
+# XXX does not correctly deal with spaces in non-userdir params
+args=""
+
+case "`uname`" in
+    Darwin*)
+        if [ ! -z "$default_mac_userdir" ]; then
+          userdir="${default_mac_userdir}"
+        else
+          userdir="${default_userdir}"
+        fi
+        ;;
+    *)
+        userdir="${default_userdir}"
+        ;;
+esac
+while [ $# -gt 0 ] ; do
+    case "$1" in
+        --userdir) shift; if [ $# -gt 0 ] ; then userdir="$1"; fi
+            ;;
+        *) args="$args \"$1\""
+            ;;
+    esac
+    shift
+done
+
+if [ -f "${userdir}/etc/$APPNAME".conf ] ; then
+    . "${userdir}/etc/$APPNAME".conf
+fi
+
+if [ -n "$jdkhome" -a \! -d "$jdkhome" -a -d "$progdir/../$jdkhome" ]; then
+    # #74333: permit jdkhome to be defined as relative to app dir
+    jdkhome="$progdir/../$jdkhome"
+fi
+
+readClusters() {
+  if [ -x /usr/ucb/echo ]; then
+    echo=/usr/ucb/echo
+  else
+    echo=echo
+  fi
+  while read X; do
+    if [ "$X" \!= "" ]; then
+      $echo "$progdir/../$X"
+    fi
+  done
+}
+
+absolutize_paths() {
+    while read path; do
+        if [ -d "$path" ]; then
+            (cd "$path" 2>/dev/null && pwd)
+        else
+            echo "$path"
+        fi
+    done
+}
+
+clusters=`(cat "$progdir/../etc/$APPNAME".clusters; echo) | readClusters | absolutize_paths | tr '\012' ':'`
+
+if [ ! -z "$extra_clusters" ] ; then
+    clusters="$clusters:$extra_clusters"
+fi
+
+nbexec=`echo "$progdir"/../platform*/lib/nbexec`
+
+case "`uname`" in
+    Darwin*)
+        eval exec sh '"$nbexec"' \
+            --jdkhome '"$jdkhome"' \
+            -J-Xdock:name='"$APPNAME"' \
+            '"-J-Xdock:icon=$progdir/../../$APPNAME.icns"' \
+            --clusters '"$clusters"' \
+            --userdir '"${userdir}"' \
+            ${default_options} \
+            "$args"
+        ;;
+    *)  
+       sh=sh
+       # #73162: Ubuntu uses the ancient Bourne shell, which does not implement trap well.
+       if [ -x /bin/bash ]
+       then
+           sh=/bin/bash
+       fi
+       eval exec $sh '"$nbexec"' \
+            --jdkhome '"$jdkhome"' \
+            --clusters '"$clusters"' \
+            --userdir '"${userdir}"' \
+            ${default_options} \
+            "$args"
+       exit 1
+        ;;
+esac
diff --git a/java/java.lsp.server/script/etc/nb-java-lsp-server.clusters b/java/java.lsp.server/script/etc/nb-java-lsp-server.clusters
new file mode 100644
index 0000000..4b18bae
--- /dev/null
+++ b/java/java.lsp.server/script/etc/nb-java-lsp-server.clusters
@@ -0,0 +1,4 @@
+platform
+ide
+extide
+java
diff --git a/java/java.lsp.server/script/etc/nb-java-lsp-server.conf b/java/java.lsp.server/script/etc/nb-java-lsp-server.conf
new file mode 100644
index 0000000..7c00a87
--- /dev/null
+++ b/java/java.lsp.server/script/etc/nb-java-lsp-server.conf
@@ -0,0 +1,68 @@
+# 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.
+
+# ${HOME} will be replaced by user home directory according to platform.
+#
+# Default locations of userdir and cachedir:
+# (http://wiki.netbeans.org/FaqWhatIsUserdir)
+#
+# On Windows ${DEFAULT_USERDIR_ROOT} will be replaced by the launcher
+# with <AppData>\<AppName>, where <AppData> is the value of 
+# "AppData" key in Windows Registry under
+# "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
+# and ${DEFAULT_CACHEDIR_ROOT} will be replaced by the launcher
+# with the value of "<LocalAppData>\<AppName>\Cache" where "LocalAppData"
+# is a key in Windows Registry under 
+# "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders".
+# <AppName> is the the executable's filename without the extension.
+#
+# On Mac ${DEFAULT_USERDIR_ROOT} will be replaced by the launcher
+# with "~/Library/Application Support/<AppName>" and
+# ${DEFAULT_CACHEDIR_ROOT} with "~/Library/Caches/<AppName>"
+# where <AppName> is the launcher script's name.
+#
+# On other systems ${DEFAULT_USERDIR_ROOT} will be replaced by the launcher
+# with "${HOME}/.<AppName>" and ${DEFAULT_CACHEDIR_ROOT} with 
+# "${HOME}/.cache/<AppName>".
+#
+# You can also use ${HOME} variable which will be replaced with
+# user.home JVM system property value. 
+#
+# The above location variables are valid only in the default_userdir and
+# default_cachedir properties.
+#
+# NOTE: If you specify a non-default userdir path on command line
+# (--userdir option) and don't specify a cachedir path (--cachedir option),
+# cachedir will be in "<userdir>/var/cache".
+#
+# Cachedir must be different from userdir. The same cachedir and userdir
+# would cause problems.
+#
+
+default_userdir="${DEFAULT_USERDIR_ROOT}/dev"
+default_cachedir="${DEFAULT_CACHEDIR_ROOT}/dev"
+
+# options used by the launcher by default, can be overridden by explicit
+# command line switches
+default_options="--nogui --nosplash -J-Djava.awt.headless=true --start-java-language-server"
+# for development purposes you may wish to append: -J-Dnetbeans.logger.console=true -J-ea
+
+# default location of JDK/JRE, can be overridden by using --jdkhome <dir> switch
+#jdkhome="/path/to/jdk"
+
+# clusters' paths separated by path.separator (semicolon on Windows, colon on Unices)
+#extra_clusters=
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Bundle.properties b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Bundle.properties
new file mode 100644
index 0000000..e056324
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Bundle.properties
@@ -0,0 +1,18 @@
+# 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.
+
+OpenIDE-Module-Name=Java LSP Server
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Server.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Server.java
new file mode 100644
index 0000000..cd2829f
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Server.java
@@ -0,0 +1,227 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+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.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.eclipse.lsp4j.CompletionOptions;
+import org.eclipse.lsp4j.InitializeParams;
+import org.eclipse.lsp4j.InitializeResult;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.ServerCapabilities;
+import org.eclipse.lsp4j.TextDocumentSyncKind;
+import org.eclipse.lsp4j.WorkspaceFolder;
+import org.eclipse.lsp4j.jsonrpc.Launcher;
+import org.eclipse.lsp4j.launch.LSPLauncher;
+import org.eclipse.lsp4j.services.LanguageClient;
+import org.eclipse.lsp4j.services.LanguageClientAware;
+import org.eclipse.lsp4j.services.LanguageServer;
+import org.eclipse.lsp4j.services.TextDocumentService;
+import org.eclipse.lsp4j.services.WorkspaceService;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.source.ClasspathInfo;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectUtils;
+import org.netbeans.api.project.Sources;
+import org.netbeans.api.project.ui.OpenProjects;
+import org.netbeans.api.sendopts.CommandException;
+import org.netbeans.modules.java.lsp.server.text.TextDocumentServiceImpl;
+import org.netbeans.modules.java.lsp.server.workspace.WorkspaceServiceImpl;
+import org.netbeans.spi.sendopts.Arg;
+import org.netbeans.spi.sendopts.ArgsProcessor;
+import org.netbeans.spi.sendopts.Description;
+import org.netbeans.spi.sendopts.Env;
+import org.netbeans.spi.sendopts.Option;
+import org.netbeans.spi.sendopts.OptionProcessor;
+import org.openide.filesystems.FileObject;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author lahvac
+ */
+public class Server implements ArgsProcessor {
+
+    @Arg(longName="start-java-language-server")
+    @Description(shortDescription="whatever")
+    @Messages("DESC_StartJavaLanguageServer=Starts the Java Language Server")
+    public boolean enable;
+
+    @Override
+    public void process(Env env) throws CommandException {
+        try {
+            run(env.getInputStream(), env.getOutputStream());
+        } catch (Exception ex) {
+            throw (CommandException) new CommandException(1).initCause(ex);
+        }
+    }
+    
+    @ServiceProvider(service=OptionProcessor.class)
+    public static class OptionProcessorImpl extends OptionProcessor {
+
+        @Override
+        protected Set<Option> getOptions() {
+            return new HashSet<>(Arrays.asList(Option.withoutArgument('\0', "--start-java-language-server")));
+        }
+
+        @Override
+        protected void process(Env env, Map<Option, String[]> optionValues) throws CommandException {
+            try {
+                run(env.getInputStream(), env.getOutputStream());
+            } catch (Exception ex) {
+                throw (CommandException) new CommandException(1).initCause(ex);
+            }
+        }
+        
+    }
+
+    private static void run(InputStream in, OutputStream out) throws Exception {
+        LanguageServerImpl server = new LanguageServerImpl();
+        Launcher<LanguageClient> serverLauncher = LSPLauncher.createServerLauncher(server, in, out);
+        ((LanguageClientAware) server).connect(serverLauncher.getRemoteProxy());
+        serverLauncher.startListening();
+
+        while (true) {
+            try {
+                Thread.sleep(100000);
+            } catch (InterruptedException ex) {
+                //ignore
+            }
+        }
+    }
+
+    private static class LanguageServerImpl implements LanguageServer, LanguageClientAware {
+
+        private static final Logger LOG = Logger.getLogger(LanguageServerImpl.class.getName());
+        private LanguageClient client;
+        private final TextDocumentService textDocumentService = new TextDocumentServiceImpl();
+
+        @Override
+        public CompletableFuture<InitializeResult> initialize(InitializeParams init) {
+            List<FileObject> projectCandidates = new ArrayList<>();
+            List<WorkspaceFolder> folders = init.getWorkspaceFolders();
+            if (folders != null) {
+                for (WorkspaceFolder w : folders) {
+                    try {
+                        projectCandidates.add(TextDocumentServiceImpl.fromUri(w.getUri()));
+                    } catch (MalformedURLException ex) {
+                        LOG.log(Level.FINE, null, ex);
+                    }
+                }
+            } else {
+                String root = init.getRootUri();
+
+                if (root != null) {
+                    try {
+                        projectCandidates.add(TextDocumentServiceImpl.fromUri(root));
+                    } catch (MalformedURLException ex) {
+                        LOG.log(Level.FINE, null, ex);
+                    }
+                } else {
+                    //TODO: use getRootPath()?
+                }
+            }
+            List<Project> projects = new ArrayList<>();
+            for (FileObject candidate : projectCandidates) {
+                Project prj = FileOwnerQuery.getOwner(candidate);
+                if (prj != null) {
+                    projects.add(prj);
+                }
+            }
+            //XXX: ensure project opened:
+            try {
+                Class.forName("org.netbeans.modules.project.ui.OpenProjectList", false, Lookup.getDefault().lookup(ClassLoader.class)).getDeclaredMethod("waitProjectsFullyOpen").invoke(null);
+            } catch (Exception ex) {
+                throw new IllegalStateException(ex);
+            }
+            OpenProjects.getDefault().open(projects.toArray(new Project[0]), false);
+            try {
+                OpenProjects.getDefault().openProjects().get();
+            } catch (InterruptedException | ExecutionException ex) {
+                throw new IllegalStateException(ex);
+            }
+            for (Project prj : projects) {
+                //init source groups/FileOwnerQuery:
+                ProjectUtils.getSources(prj).getSourceGroups(Sources.TYPE_GENERIC);
+            }
+            try {
+                JavaSource.create(ClasspathInfo.create(ClassPath.EMPTY, ClassPath.EMPTY, ClassPath.EMPTY))
+                          .runWhenScanFinished(cc -> {
+                  client.showMessage(new MessageParams(MessageType.Info, INDEXING_COMPLETED));
+                  //todo: refresh diagnostics all open editor?
+                }, true);
+            } catch (IOException ex) {
+                throw new IllegalStateException(ex);
+            }
+            ServerCapabilities capabilities = new ServerCapabilities();
+            capabilities.setTextDocumentSync(TextDocumentSyncKind.Incremental);
+            capabilities.setCompletionProvider(new CompletionOptions());
+            capabilities.setCodeActionProvider(true);
+            capabilities.setDocumentSymbolProvider(true);
+            capabilities.setDefinitionProvider(true);
+            capabilities.setDocumentHighlightProvider(true);
+            return CompletableFuture.completedFuture(new InitializeResult(capabilities));
+        }
+
+        @Override
+        public CompletableFuture<Object> shutdown() {
+            return CompletableFuture.completedFuture(null);
+        }
+
+        @Override
+        public void exit() {
+            System.exit(1);
+        }
+
+        @Override
+        public TextDocumentService getTextDocumentService() {
+            return textDocumentService;
+        }
+
+        @Override
+        public WorkspaceService getWorkspaceService() {
+            return new WorkspaceServiceImpl();
+        }
+
+        @Override
+        public void connect(LanguageClient client) {
+            this.client = client;
+            ((LanguageClientAware) getTextDocumentService()).connect(client);
+        }
+    }
+
+    static final String INDEXING_COMPLETED = "Indexing completed.";
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/text/TextDocumentServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/text/TextDocumentServiceImpl.java
new file mode 100644
index 0000000..3c46336
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/text/TextDocumentServiceImpl.java
@@ -0,0 +1,913 @@
+/*
+ * 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.text;
+
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.LineMap;
+import com.sun.source.util.TreePath;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.prefs.Preferences;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.StyledDocument;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.CodeLens;
+import org.eclipse.lsp4j.CodeLensParams;
+import org.eclipse.lsp4j.Command;
+import org.eclipse.lsp4j.CompletionItem;
+import org.eclipse.lsp4j.CompletionItemKind;
+import org.eclipse.lsp4j.CompletionList;
+import org.eclipse.lsp4j.CompletionParams;
+import org.eclipse.lsp4j.Diagnostic;
+import org.eclipse.lsp4j.DiagnosticSeverity;
+import org.eclipse.lsp4j.DidChangeTextDocumentParams;
+import org.eclipse.lsp4j.DidCloseTextDocumentParams;
+import org.eclipse.lsp4j.DidOpenTextDocumentParams;
+import org.eclipse.lsp4j.DidSaveTextDocumentParams;
+import org.eclipse.lsp4j.DocumentFormattingParams;
+import org.eclipse.lsp4j.DocumentHighlight;
+import org.eclipse.lsp4j.DocumentOnTypeFormattingParams;
+import org.eclipse.lsp4j.DocumentRangeFormattingParams;
+import org.eclipse.lsp4j.DocumentSymbol;
+import org.eclipse.lsp4j.DocumentSymbolParams;
+import org.eclipse.lsp4j.Hover;
+import org.eclipse.lsp4j.Location;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.PublishDiagnosticsParams;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.ReferenceParams;
+import org.eclipse.lsp4j.RenameParams;
+import org.eclipse.lsp4j.SignatureHelp;
+import org.eclipse.lsp4j.SymbolInformation;
+import org.eclipse.lsp4j.SymbolKind;
+import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
+import org.eclipse.lsp4j.TextDocumentEdit;
+import org.eclipse.lsp4j.TextDocumentPositionParams;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.eclipse.lsp4j.services.LanguageClient;
+import org.eclipse.lsp4j.services.LanguageClientAware;
+import org.eclipse.lsp4j.services.TextDocumentService;
+import org.netbeans.api.editor.document.LineDocument;
+import org.netbeans.api.editor.document.LineDocumentUtils;
+import org.netbeans.api.java.source.CompilationController;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementHandle;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.ModificationResult;
+import org.netbeans.api.java.source.support.ReferencesCount;
+import org.netbeans.api.java.source.ui.ElementOpen;
+import org.netbeans.api.lexer.TokenHierarchy;
+import org.netbeans.modules.editor.NbEditorUtilities;
+import org.netbeans.modules.editor.java.GoToSupport;
+import org.netbeans.modules.editor.java.GoToSupport.Context;
+import org.netbeans.modules.editor.java.GoToSupport.GoToTarget;
+import org.netbeans.modules.editor.java.Utilities;
+import org.netbeans.modules.java.completion.JavaCompletionTask;
+import org.netbeans.modules.java.completion.JavaCompletionTask.Options;
+import org.netbeans.modules.java.editor.base.semantic.MarkOccurrencesHighlighterBase;
+import org.netbeans.modules.java.editor.options.MarkOccurencesSettings;
+import org.netbeans.modules.java.hints.infrastructure.CreatorBasedLazyFixList;
+import org.netbeans.modules.java.hints.infrastructure.ErrorHintsProvider;
+import org.netbeans.modules.java.hints.spiimpl.JavaFixImpl;
+import org.netbeans.modules.java.hints.spiimpl.hints.HintsInvoker;
+import org.netbeans.modules.java.hints.spiimpl.options.HintsSettings;
+import org.netbeans.modules.java.source.ui.ElementOpenAccessor;
+import org.netbeans.modules.parsing.api.ParserManager;
+import org.netbeans.modules.parsing.api.ResultIterator;
+import org.netbeans.modules.parsing.api.Source;
+import org.netbeans.modules.parsing.api.UserTask;
+import org.netbeans.modules.parsing.spi.ParseException;
+import org.netbeans.modules.parsing.spi.SchedulerEvent;
+import org.netbeans.spi.editor.hints.ErrorDescription;
+import org.netbeans.spi.editor.hints.Fix;
+import org.netbeans.spi.editor.hints.LazyFixList;
+import org.netbeans.spi.java.hints.JavaFix;
+import org.netbeans.spi.lexer.MutableTextInput;
+import org.openide.cookies.EditorCookie;
+import org.openide.cookies.LineCookie;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.modules.Places;
+import org.openide.text.Line;
+import org.openide.text.NbDocument;
+import org.openide.text.PositionRef;
+import org.openide.util.Exceptions;
+import org.openide.util.RequestProcessor;
+
+/**
+ *
+ * @author lahvac
+ */
+public class TextDocumentServiceImpl implements TextDocumentService, LanguageClientAware {
+
+    private static final RequestProcessor BACKGROUND_TASKS = new RequestProcessor(TextDocumentServiceImpl.class.getName(), 1, false, false);
+
+    private final Map<String, Document> openedDocuments = new HashMap<>();
+    private final Map<String, RequestProcessor.Task> diagnosticTasks = new HashMap<>();
+    private LanguageClient client;
+
+    public TextDocumentServiceImpl() {
+    }
+
+    @Override
+    public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams params) {
+        try {
+            FileObject file = fromUri(params.getTextDocument().getUri());
+            EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
+            Document doc = ec.openDocument();
+            int caret = getOffset(doc, params.getPosition());
+            JavaCompletionTask<CompletionItem> task = JavaCompletionTask.create(caret, new ItemFactoryImpl(), EnumSet.noneOf(Options.class), () -> false);
+            ParserManager.parse(Collections.singletonList(Source.create(doc)), task);
+            List<CompletionItem> result = task.getResults();
+            for (Iterator<CompletionItem> it = result.iterator(); it.hasNext();) {
+                CompletionItem item = it.next();
+                if (item == null) {
+                    it.remove();
+                }
+            }
+            return CompletableFuture.completedFuture(Either.<List<CompletionItem>, CompletionList>forRight(new CompletionList(result)));
+        } catch (IOException | ParseException ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    @Override
+    public void connect(LanguageClient client) {
+        this.client = client;
+    }
+
+    private static class ItemFactoryImpl implements JavaCompletionTask.ItemFactory<CompletionItem> {
+
+        public ItemFactoryImpl() {
+        }
+
+        @Override
+        public CompletionItem createKeywordItem(String kwd, String postfix, int substitutionOffset, boolean smartType) {
+            CompletionItem item = new CompletionItem(kwd);
+            item.setKind(CompletionItemKind.Keyword);
+            return item;
+        }
+
+        @Override
+        public CompletionItem createPackageItem(String pkgFQN, int substitutionOffset, boolean inPackageStatement) {
+            return null; //TODO: fill
+        }
+
+        @Override
+        public CompletionItem createTypeItem(CompilationInfo info, TypeElement elem, DeclaredType type, int substitutionOffset, ReferencesCount referencesCount, boolean isDeprecated, boolean insideNew, boolean addTypeVars, boolean addSimpleName, boolean smartType, boolean autoImportEnclosingType) {
+            CompletionItem item = new CompletionItem(elem.getSimpleName().toString());
+            item.setKind(elementKind2CompletionItemKind(elem.getKind()));
+            return item;
+        }
+
+        @Override
+        public CompletionItem createTypeItem(ElementHandle<TypeElement> handle, EnumSet<ElementKind> kinds, int substitutionOffset, ReferencesCount referencesCount, Source source, boolean insideNew, boolean addTypeVars, boolean afterExtends) {
+            return null; //TODO: fill
+        }
+
+        @Override
+        public CompletionItem createArrayItem(CompilationInfo info, ArrayType type, int substitutionOffset, ReferencesCount referencesCount, Elements elements) {
+            return null; //TODO: fill
+        }
+
+        @Override
+        public CompletionItem createTypeParameterItem(TypeParameterElement elem, int substitutionOffset) {
+            CompletionItem item = new CompletionItem(elem.getSimpleName().toString());
+            item.setKind(elementKind2CompletionItemKind(elem.getKind()));
+            return item;
+        }
+
+        @Override
+        public CompletionItem createVariableItem(CompilationInfo info, VariableElement elem, TypeMirror type, int substitutionOffset, ReferencesCount referencesCount, boolean isInherited, boolean isDeprecated, boolean smartType, int assignToVarOffset) {
+            CompletionItem item = new CompletionItem(elem.getSimpleName().toString());
+            item.setKind(elementKind2CompletionItemKind(elem.getKind()));
+            return item;
+        }
+
+        @Override
+        public CompletionItem createVariableItem(CompilationInfo info, String varName, int substitutionOffset, boolean newVarName, boolean smartType) {
+            CompletionItem item = new CompletionItem(varName);
+            item.setKind(CompletionItemKind.Variable);
+            return item;
+        }
+
+        @Override
+        public CompletionItem createExecutableItem(CompilationInfo info, ExecutableElement elem, ExecutableType type, int substitutionOffset, ReferencesCount referencesCount, boolean isInherited, boolean isDeprecated, boolean inImport, boolean addSemicolon, boolean smartType, int assignToVarOffset, boolean memberRef) {
+            CompletionItem item = new CompletionItem(elem.getSimpleName().toString());
+            item.setKind(elementKind2CompletionItemKind(elem.getKind()));
+            Iterator<? extends VariableElement> it = elem.getParameters().iterator();
+            Iterator<? extends TypeMirror> tIt = type.getParameterTypes().iterator();
+            StringBuilder details = new StringBuilder();
+            String sep = "";
+            details.append("(");
+            while(it.hasNext() && tIt.hasNext()) {
+                TypeMirror tm = tIt.next();
+                if (tm == null) {
+                    break;
+                }
+                details.append(sep);
+                details.append(Utilities.getTypeName(info, tm, false, elem.isVarArgs() && !tIt.hasNext()).toString());
+                details.append(' ');
+                details.append(it.next().getSimpleName().toString());
+                sep = ", ";
+            }
+            details.append(")");
+            TypeMirror retType = type.getReturnType();
+            details.append(Utilities.getTypeName(info, retType, false).toString());
+            item.setDetail(details.toString());
+            return item;
+        }
+
+        @Override
+        public CompletionItem createThisOrSuperConstructorItem(CompilationInfo info, ExecutableElement elem, ExecutableType type, int substitutionOffset, boolean isDeprecated, String name) {
+            CompletionItem item = new CompletionItem(name);
+            item.setKind(CompletionItemKind.Field);
+            return item;
+        }
+
+        @Override
+        public CompletionItem createOverrideMethodItem(CompilationInfo info, ExecutableElement elem, ExecutableType type, int substitutionOffset, boolean implement) {
+            CompletionItem item = new CompletionItem(elem.getSimpleName().toString() + " - override");
+            item.setKind(elementKind2CompletionItemKind(elem.getKind()));
+            return item;
+        }
+
+        @Override
+        public CompletionItem createGetterSetterMethodItem(CompilationInfo info, VariableElement elem, TypeMirror type, int substitutionOffset, String name, boolean setter) {
+            return null; //TODO: fill
+        }
+
+        @Override
+        public CompletionItem createDefaultConstructorItem(TypeElement elem, int substitutionOffset, boolean smartType) {
+            return null; //TODO: fill
+        }
+
+        @Override
+        public CompletionItem createParametersItem(CompilationInfo info, ExecutableElement elem, ExecutableType type, int substitutionOffset, boolean isDeprecated, int activeParamIndex, String name) {
+            return null; //TODO: fill
+        }
+
+        @Override
+        public CompletionItem createAnnotationItem(CompilationInfo info, TypeElement elem, DeclaredType type, int substitutionOffset, ReferencesCount referencesCount, boolean isDeprecated) {
+            return null; //TODO: fill
+        }
+
+        @Override
+        public CompletionItem createAttributeItem(CompilationInfo info, ExecutableElement elem, ExecutableType type, int substitutionOffset, boolean isDeprecated) {
+            return null; //TODO: fill
+        }
+
+        @Override
+        public CompletionItem createAttributeValueItem(CompilationInfo info, String value, String documentation, TypeElement element, int substitutionOffset, ReferencesCount referencesCount) {
+            return null; //TODO: fill
+        }
+
+        @Override
+        public CompletionItem createStaticMemberItem(CompilationInfo info, DeclaredType type, Element memberElem, TypeMirror memberType, boolean multipleVersions, int substitutionOffset, boolean isDeprecated, boolean addSemicolon) {
+            return null; //TODO: fill
+        }
+
+        @Override
+        public CompletionItem createStaticMemberItem(ElementHandle<TypeElement> handle, String name, int substitutionOffset, boolean addSemicolon, ReferencesCount referencesCount, Source source) {
+            return null; //TODO: fill
+        }
+
+        @Override
+        public CompletionItem createChainedMembersItem(CompilationInfo info, List<? extends Element> chainedElems, List<? extends TypeMirror> chainedTypes, int substitutionOffset, boolean isDeprecated, boolean addSemicolon) {
+            return null; //TODO: fill
+        }
+
+        @Override
+        public CompletionItem createInitializeAllConstructorItem(CompilationInfo info, boolean isDefault, Iterable<? extends VariableElement> fields, ExecutableElement superConstructor, TypeElement parent, int substitutionOffset) {
+            return null; //TODO: fill
+        }
+
+        private static CompletionItemKind elementKind2CompletionItemKind(ElementKind kind) {
+            switch (kind) {
+                case PACKAGE:
+                    return CompletionItemKind.Folder;
+                case ENUM:
+                    return CompletionItemKind.Enum;
+                case CLASS:
+                    return CompletionItemKind.Class;
+                case ANNOTATION_TYPE:
+                    return CompletionItemKind.Interface;
+                case INTERFACE:
+                    return CompletionItemKind.Interface;
+                case ENUM_CONSTANT:
+                    return CompletionItemKind.EnumMember;
+                case FIELD:
+                    return CompletionItemKind.Field;
+                case PARAMETER:
+                    return CompletionItemKind.Variable;
+                case LOCAL_VARIABLE:
+                    return CompletionItemKind.Variable;
+                case EXCEPTION_PARAMETER:
+                    return CompletionItemKind.Variable;
+                case METHOD:
+                    return CompletionItemKind.Method;
+                case CONSTRUCTOR:
+                    return CompletionItemKind.Constructor;
+                case TYPE_PARAMETER:
+                    return CompletionItemKind.TypeParameter;
+                case RESOURCE_VARIABLE:
+                    return CompletionItemKind.Variable;
+                case MODULE:
+                    return CompletionItemKind.Module;
+                case STATIC_INIT:
+                case INSTANCE_INIT:
+                case OTHER:
+                default:
+                    return CompletionItemKind.Text;
+            }
+        }
+    }
+
+    @Override
+    public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem arg0) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public CompletableFuture<Hover> hover(TextDocumentPositionParams arg0) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public CompletableFuture<SignatureHelp> signatureHelp(TextDocumentPositionParams arg0) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public CompletableFuture<List<? extends Location>> definition(TextDocumentPositionParams params) {
+        JavaSource js = getSource(params.getTextDocument().getUri());
+        GoToTarget[] target = new GoToTarget[1];
+        LineMap[] lm = new LineMap[1];
+        try {
+            js.runUserActionTask(cc -> {
+                cc.toPhase(JavaSource.Phase.RESOLVED);
+                Document doc = cc.getSnapshot().getSource().getDocument(true);
+                int offset = getOffset(doc, params.getPosition());
+                Context context = GoToSupport.resolveContext(cc, doc, offset, false, false);
+                if (context == null) {
+                    return ;
+                }
+                target[0] = GoToSupport.computeGoToTarget(cc, context, offset);
+                lm[0] = cc.getCompilationUnit().getLineMap();
+            }, true);
+        } catch (IOException ex) {
+            //TODO: include stack trace:
+            client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+        }
+
+        List<Location> result = new ArrayList<>();
+
+        if (target[0] != null && target[0].success) {
+            if (target[0].offsetToOpen < 0) {
+                Object[] openInfo = ElementOpenAccessor.getInstance().getOpenInfo(target[0].cpInfo, target[0].elementToOpen, new AtomicBoolean());
+                if (openInfo != null) {
+                    FileObject file = (FileObject) openInfo[0];
+                    int start = (int) openInfo[1];
+                    int end = (int) openInfo[2];
+                    result.add(new Location(toUri(file),
+                                            new Range(createPosition(lm[0], start),
+                                                      createPosition(lm[0], end))));
+                }
+            } else {
+                Position pos = createPosition(js.getFileObjects().iterator().next(), target[0].offsetToOpen);
+                result.add(new Location(params.getTextDocument().getUri(),
+                                        new Range(pos, pos)));
+            }
+        }
+        return CompletableFuture.completedFuture(result);
+    }
+
+    @Override
+    public CompletableFuture<List<? extends Location>> references(ReferenceParams arg0) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public CompletableFuture<List<? extends DocumentHighlight>> documentHighlight(TextDocumentPositionParams params) {
+        class MOHighligther extends MarkOccurrencesHighlighterBase {
+            @Override
+            protected void process(CompilationInfo arg0, Document arg1, SchedulerEvent arg2) {
+                throw new UnsupportedOperationException("Should not be called.");
+            }
+            @Override
+            public List<int[]> processImpl(CompilationInfo info, Preferences node, Document doc, int caretPosition) {
+                return super.processImpl(info, node, doc, caretPosition);
+            }
+        }
+
+        Preferences node = MarkOccurencesSettings.getCurrentNode();
+
+        JavaSource js = getSource(params.getTextDocument().getUri());
+        List<DocumentHighlight> result = new ArrayList<>();
+        try {
+            js.runUserActionTask(cc -> {
+                cc.toPhase(JavaSource.Phase.RESOLVED);
+                Document doc = cc.getSnapshot().getSource().getDocument(true);
+                int offset = getOffset(doc, params.getPosition());
+                List<int[]> spans = new MOHighligther().processImpl(cc, node, doc, offset);
+                if (spans != null) {
+                    for (int[] span : spans) {
+                        result.add(new DocumentHighlight(new Range(createPosition(cc.getCompilationUnit(), span[0]),
+                                                                   createPosition(cc.getCompilationUnit(), span[1]))));
+                    }
+                }
+            }, true);
+        } catch (IOException ex) {
+            //TODO: include stack trace:
+            client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+        }
+        return CompletableFuture.completedFuture(result);
+    }
+
+    @Override
+    public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> documentSymbol(DocumentSymbolParams params) {
+        JavaSource js = getSource(params.getTextDocument().getUri());
+        List<Either<SymbolInformation, DocumentSymbol>> result = new ArrayList<>();
+        try {
+            js.runUserActionTask(cc -> {
+                cc.toPhase(JavaSource.Phase.RESOLVED);
+                for (Element tel : cc.getTopLevelElements()) {
+                    DocumentSymbol ds = element2DocumentSymbol(cc, tel);
+                    if (ds != null)
+                        result.add(Either.forRight(ds));
+                }
+            }, true);
+        } catch (IOException ex) {
+            //TODO: include stack trace:
+            client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+        }
+
+        return CompletableFuture.completedFuture(result);
+    }
+
+    private DocumentSymbol element2DocumentSymbol(CompilationInfo info, Element el) throws BadLocationException {
+        TreePath path = info.getTrees().getPath(el);
+        if (path == null)
+            return null;
+        long start = info.getTrees().getSourcePositions().getStartPosition(path.getCompilationUnit(), path.getLeaf());
+        long end   = info.getTrees().getSourcePositions().getEndPosition(path.getCompilationUnit(), path.getLeaf());
+        if (end == (-1))
+            return null;
+        Range range = new Range(createPosition(info.getCompilationUnit(), (int) start),
+                                createPosition(info.getCompilationUnit(), (int) end));
+        List<DocumentSymbol> children = new ArrayList<>();
+        for (Element c : el.getEnclosedElements()) {
+            DocumentSymbol ds = element2DocumentSymbol(info, c);
+            if (ds != null) {
+                children.add(ds);
+            }
+        }
+        return new DocumentSymbol(el.getSimpleName().toString(), elementKind2SymbolKind(el.getKind()), range, range, null, children);
+    }
+
+    private static SymbolKind elementKind2SymbolKind(ElementKind kind) {
+        switch (kind) {
+            case PACKAGE:
+                return SymbolKind.Package;
+            case ENUM:
+                return SymbolKind.Enum;
+            case CLASS:
+                return SymbolKind.Class;
+            case ANNOTATION_TYPE:
+                return SymbolKind.Interface;
+            case INTERFACE:
+                return SymbolKind.Interface;
+            case ENUM_CONSTANT:
+                return SymbolKind.EnumMember;
+            case FIELD:
+                return SymbolKind.Field; //TODO: constant
+            case PARAMETER:
+                return SymbolKind.Variable;
+            case LOCAL_VARIABLE:
+                return SymbolKind.Variable;
+            case EXCEPTION_PARAMETER:
+                return SymbolKind.Variable;
+            case METHOD:
+                return SymbolKind.Method;
+            case CONSTRUCTOR:
+                return SymbolKind.Constructor;
+            case TYPE_PARAMETER:
+                return SymbolKind.TypeParameter;
+            case RESOURCE_VARIABLE:
+                return SymbolKind.Variable;
+            case MODULE:
+                return SymbolKind.Module;
+            case STATIC_INIT:
+            case INSTANCE_INIT:
+            case OTHER:
+            default:
+                return SymbolKind.File; //XXX: what here?
+        }
+    }
+
+    @Override
+    public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
+        Document doc = openedDocuments.get(params.getTextDocument().getUri());
+        if (doc == null) {
+            return CompletableFuture.completedFuture(Collections.emptyList());
+        }
+        Map<String, ErrorDescription> id2Errors = (Map<String, ErrorDescription>) doc.getProperty("lsp-errors");
+        if (id2Errors == null) {
+            return CompletableFuture.completedFuture(Collections.emptyList());
+        }
+        JavaSource js = JavaSource.forDocument(doc);
+        List<Either<Command, CodeAction>> result = new ArrayList<>();
+        for (Diagnostic diag : params.getContext().getDiagnostics()) {
+            ErrorDescription err = id2Errors.get(diag.getCode());
+
+            if (err == null) {
+                client.logMessage(new MessageParams(MessageType.Log, "Cannot resolve error, code: " + diag.getCode()));
+                continue;
+            }
+
+            LazyFixList lfl = err.getFixes();
+
+            if (lfl instanceof CreatorBasedLazyFixList) {
+                try {
+                    js.runUserActionTask(cc -> {
+                        cc.toPhase(JavaSource.Phase.RESOLVED);
+                        ((CreatorBasedLazyFixList) lfl).compute(cc, new AtomicBoolean());
+                    }, true);
+                } catch (IOException ex) {
+                    //TODO: include stack trace:
+                    client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+                }
+            }
+            List<Fix> fixes = lfl.getFixes();
+
+            //TODO: ordering
+
+            for (Fix f : fixes) {
+                if (f instanceof JavaFixImpl) {
+                    try {
+                        LineMap[] lm = new LineMap[1];
+                        ModificationResult changes = js.runModificationTask(wc -> {
+                            wc.toPhase(JavaSource.Phase.RESOLVED);
+                            Map<FileObject, byte[]> resourceContentChanges = new HashMap<FileObject, byte[]>();
+                            JavaFix jf = ((JavaFixImpl) f).jf;
+                            JavaFixImpl.Accessor.INSTANCE.process(jf, wc, true, resourceContentChanges, /*Ignored in editor:*/new ArrayList<>());
+                            lm[0] = wc.getCompilationUnit().getLineMap();
+                        });
+                        //TODO: full, correct and safe edit production:
+                        List<? extends ModificationResult.Difference> diffs = changes.getDifferences(changes.getModifiedFileObjects().iterator().next());
+                        List<TextEdit> edits = new ArrayList<>();
+                        for (ModificationResult.Difference diff : diffs) {
+                            edits.add(new TextEdit(new Range(createPosition(lm[0], diff.getStartPosition().getOffset()),
+                                                             createPosition(lm[0], diff.getEndPosition().getOffset())),
+                                                   diff.getNewText()));
+                        }
+                        TextDocumentEdit te = new TextDocumentEdit(new VersionedTextDocumentIdentifier(params.getTextDocument().getUri(),
+                                                                                                       -1),
+                                                                   edits);
+                        CodeAction action = new CodeAction(f.getText());
+                        action.setDiagnostics(Collections.singletonList(diag));
+                        action.setKind(CodeActionKind.QuickFix);
+                        action.setEdit(new WorkspaceEdit(Collections.singletonList(te)));
+                        result.add(Either.forRight(action));
+                    } catch (IOException ex) {
+                        //TODO: include stack trace:
+                        client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+                    }
+                }
+            }
+        }
+
+        return CompletableFuture.completedFuture(result);
+    }
+
+    @Override
+    public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams arg0) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public CompletableFuture<CodeLens> resolveCodeLens(CodeLens arg0) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams arg0) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public CompletableFuture<List<? extends TextEdit>> rangeFormatting(DocumentRangeFormattingParams arg0) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public CompletableFuture<List<? extends TextEdit>> onTypeFormatting(DocumentOnTypeFormattingParams arg0) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public CompletableFuture<WorkspaceEdit> rename(RenameParams arg0) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public void didOpen(DidOpenTextDocumentParams params) {
+        try {
+            FileObject file = fromUri(params.getTextDocument().getUri());
+            EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
+            Document doc = ec.openDocument();
+            openedDocuments.put(params.getTextDocument().getUri(), doc);
+            String text = params.getTextDocument().getText();
+            try {
+                doc.remove(0, doc.getLength());
+                doc.insertString(0, text, null);
+            } catch (BadLocationException ex) {
+                //TODO: include stack trace:
+                client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+            }
+            runDiagnoticTasks(params.getTextDocument().getUri());
+        } catch (IOException ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    @Override
+    public void didChange(DidChangeTextDocumentParams params) {
+        Document doc = openedDocuments.get(params.getTextDocument().getUri());
+        NbDocument.runAtomic((StyledDocument) doc, () -> {
+            for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
+                try {
+                    int start = getOffset(doc, change.getRange().getStart());
+                    int end   = getOffset(doc, change.getRange().getEnd());
+                    doc.remove(start, end - start);
+                    doc.insertString(start, change.getText(), null);
+                } catch (BadLocationException ex) {
+                    throw new IllegalStateException(ex);
+                }
+            }
+        });
+        runDiagnoticTasks(params.getTextDocument().getUri());
+    }
+
+    @Override
+    public void didClose(DidCloseTextDocumentParams params) {
+        openedDocuments.remove(params.getTextDocument().getUri());
+    }
+
+    @Override
+    public void didSave(DidSaveTextDocumentParams arg0) {
+        //TODO: nothing for now?
+    }
+
+    private void runDiagnoticTasks(String uri) {
+        //XXX: cancelling/deferring the tasks!
+        diagnosticTasks.computeIfAbsent(uri, u -> {
+            return BACKGROUND_TASKS.create(() -> {
+                computeDiags(u, (info, doc) -> {
+                    ErrorHintsProvider ehp = new ErrorHintsProvider();
+                    return ehp.computeErrors(info, doc, "text/x-java"); //TODO: mimetype?
+                }, "errors", false);
+                BACKGROUND_TASKS.create(() -> {
+                    computeDiags(u, (info, doc) -> {
+                        return new HintsInvoker(HintsSettings.getGlobalSettings(), new AtomicBoolean()).computeHints(info);
+                    }, "hints", true);
+                }).schedule(DELAY);
+            });
+        }).schedule(DELAY);
+    }
+
+    private static final int DELAY = 500;
+
+    private void computeDiags(String uri, ProduceErrors produceErrors, String keyPrefix, boolean update) {
+        try {
+            FileObject file = fromUri(uri);
+            EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
+            Document doc = ec.openDocument();
+            ParserManager.parse(Collections.singletonList(Source.create(doc)), new UserTask() {
+                @Override
+                public void run(ResultIterator it) throws Exception {
+                    CompilationController cc = CompilationController.get(it.getParserResult());
+                    cc.toPhase(JavaSource.Phase.RESOLVED);
+                    Map<String, ErrorDescription> id2Errors = new HashMap<>();
+                    List<Diagnostic> diags = new ArrayList<>();
+                    int idx = 0;
+                    List<ErrorDescription> errors = produceErrors.computeErrors(cc, doc);
+                    if (errors == null) {
+                        errors = Collections.emptyList();
+                    }
+                    for (ErrorDescription err : errors) {
+                        Diagnostic diag = new Diagnostic(new Range(createPosition(cc.getCompilationUnit(), err.getRange().getBegin().getOffset()),
+                                                                   createPosition(cc.getCompilationUnit(), err.getRange().getEnd().getOffset())),
+                                                         err.getDescription());
+                        switch (err.getSeverity()) {
+                            case ERROR: diag.setSeverity(DiagnosticSeverity.Error); break;
+                            case VERIFIER:
+                            case WARNING: diag.setSeverity(DiagnosticSeverity.Warning); break;
+                            case HINT: diag.setSeverity(DiagnosticSeverity.Hint); break;
+                            default: diag.setSeverity(DiagnosticSeverity.Information); break;
+                        }
+                        String id = keyPrefix + ":" + idx + "-" + err.getId();
+                        diag.setCode(id);
+                        id2Errors.put(id, err);
+                        diags.add(diag);
+                    }
+                    doc.putProperty("lsp-errors-" + keyPrefix, id2Errors);
+                    doc.putProperty("lsp-errors-diags-" + keyPrefix, diags);
+                    Map<String, ErrorDescription> mergedId2Errors = new HashMap<>();
+                    List<Diagnostic> mergedDiags = new ArrayList<>();
+                    for (String k : ERROR_KEYS) {
+                        Map<String, ErrorDescription> prevErrors = (Map<String, ErrorDescription>) doc.getProperty("lsp-errors-" + k);
+                        if (prevErrors != null) {
+                            mergedId2Errors.putAll(prevErrors);
+                        }
+                        List<Diagnostic> prevDiags = (List<Diagnostic>) doc.getProperty("lsp-errors-diags-" + k);
+                        if (prevDiags != null) {
+                            mergedDiags.addAll(prevDiags);
+                        }
+                    }
+                    doc.putProperty("lsp-errors", mergedId2Errors);
+                    doc.putProperty("lsp-errors-diags", mergedDiags);
+                    client.publishDiagnostics(new PublishDiagnosticsParams(uri, mergedDiags));
+                }
+            });
+        } catch (IOException | ParseException ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    private static final String[] ERROR_KEYS = {"errors", "hints"};
+
+    private interface ProduceErrors {
+        public List<ErrorDescription> computeErrors(CompilationInfo info, Document doc) throws IOException;
+    }
+
+    private JavaSource getSource(String fileUri) {
+        Document doc = openedDocuments.get(fileUri);
+        if (doc == null) {
+            try {
+                FileObject file = fromUri(fileUri);
+                return JavaSource.forFileObject(file);
+            } catch (MalformedURLException ex) {
+                return null;
+            }
+        } else {
+            return JavaSource.forDocument(doc);
+        }
+    }
+
+    public static Position createPosition(CompilationUnitTree cut, int offset) {
+        return createPosition(cut.getLineMap(), offset);
+    }
+
+    public static Position createPosition(LineMap lm, int offset) {
+        return new Position((int) lm.getLineNumber(offset) - 1,
+                            (int) lm.getColumnNumber(offset) - 1);
+    }
+
+    public static Position createPosition(FileObject file, int offset) {
+        try {
+            EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
+            StyledDocument doc = ec.openDocument();
+            int line = NbDocument.findLineNumber(doc, offset);
+            int column = NbDocument.findLineColumn(doc, offset);
+
+            return new Position(line, column);
+        } catch (IOException ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    public static int getOffset(Document doc, Position pos) {
+        return LineDocumentUtils.getLineStartFromIndex((LineDocument) doc, pos.getLine()) + pos.getCharacter();
+    }
+
+    private static String toUri(FileObject file) {
+        if (FileUtil.isArchiveArtifact(file)) {
+            //VS code cannot open jar:file: URLs, workaround:
+            File cacheDir = Places.getCacheSubfile("java-server");
+            File segments = new File(cacheDir, "segments");
+            Properties props = new Properties();
+
+            try (InputStream in = new FileInputStream(segments)) {
+                props.load(in);
+            } catch (IOException ex) {
+                //OK, may not exist yet
+            }
+            FileObject archive = FileUtil.getArchiveFile(file);
+            String archiveString = archive.toURL().toString();
+            File foundSegment = null;
+            for (String segment : props.stringPropertyNames()) {
+                if (archiveString.equals(props.getProperty(segment))) {
+                    foundSegment = new File(cacheDir, segment);
+                    break;
+                }
+            }
+            if (foundSegment == null) {
+                int i = 0;
+                while (props.getProperty("s" + i) != null)
+                    i++;
+                foundSegment = new File(cacheDir, "s" + i);
+                props.put("s" + i, archiveString);
+                try (OutputStream in = new FileOutputStream(segments)) {
+                    props.store(in, "");
+                } catch (IOException ex) {
+                    Exceptions.printStackTrace(ex);
+                }
+            }
+            File cache = new File(foundSegment, FileUtil.getRelativePath(FileUtil.getArchiveRoot(archive), file));
+            cache.getParentFile().mkdirs();
+            try (OutputStream out = new FileOutputStream(cache)) {
+                out.write(file.asBytes());
+                return cache.toURI().toString();
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return file.toURI().toString();
+    }
+
+    //TODO: move to a separate Utils class:
+    public static FileObject fromUri(String uri) throws MalformedURLException {
+        File cacheDir = Places.getCacheSubfile("java-server");
+        URI uriUri = URI.create(uri);
+        URI relative = cacheDir.toURI().relativize(uriUri);
+        if (relative != null && new File(cacheDir, relative.toString()).canRead()) {
+            String segmentAndPath = relative.toString();
+            int slash = segmentAndPath.indexOf('/');
+            String segment = segmentAndPath.substring(0, slash);
+            String path = segmentAndPath.substring(slash + 1);
+            File segments = new File(cacheDir, "segments");
+            Properties props = new Properties();
+
+            try (InputStream in = new FileInputStream(segments)) {
+                props.load(in);
+                String archiveUri = props.getProperty(segment);
+                FileObject archive = URLMapper.findFileObject(URI.create(archiveUri).toURL());
+                archive = archive != null ? FileUtil.getArchiveRoot(archive) : null;
+                FileObject file = archive != null ? archive.getFileObject(path) : null;
+                if (file != null) {
+                    return file;
+                }
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return URLMapper.findFileObject(URI.create(uri).toURL());
+    }
+
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/workspace/WorkspaceServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/workspace/WorkspaceServiceImpl.java
new file mode 100644
index 0000000..5c0afd8
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/workspace/WorkspaceServiceImpl.java
@@ -0,0 +1,50 @@
+/*
+ * 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.workspace;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.eclipse.lsp4j.DidChangeConfigurationParams;
+import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
+import org.eclipse.lsp4j.SymbolInformation;
+import org.eclipse.lsp4j.WorkspaceSymbolParams;
+import org.eclipse.lsp4j.services.WorkspaceService;
+
+/**
+ *
+ * @author lahvac
+ */
+public class WorkspaceServiceImpl implements WorkspaceService {
+
+    @Override
+    public CompletableFuture<List<? extends SymbolInformation>> symbol(WorkspaceSymbolParams arg0) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public void didChangeConfiguration(DidChangeConfigurationParams arg0) {
+        //TODO: no real configuration right now
+    }
+
+    @Override
+    public void didChangeWatchedFiles(DidChangeWatchedFilesParams arg0) {
+        //TODO: not watching files for now
+    }
+    
+}
diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/ServerTest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/ServerTest.java
new file mode 100644
index 0000000..58151ee
--- /dev/null
+++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/ServerTest.java
@@ -0,0 +1,560 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionContext;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.Command;
+import org.eclipse.lsp4j.CompletionItem;
+import org.eclipse.lsp4j.CompletionList;
+import org.eclipse.lsp4j.CompletionParams;
+import org.eclipse.lsp4j.Diagnostic;
+import org.eclipse.lsp4j.DidChangeTextDocumentParams;
+import org.eclipse.lsp4j.DidOpenTextDocumentParams;
+import org.eclipse.lsp4j.DocumentHighlight;
+import org.eclipse.lsp4j.DocumentHighlightKind;
+import org.eclipse.lsp4j.DocumentSymbol;
+import org.eclipse.lsp4j.DocumentSymbolParams;
+import org.eclipse.lsp4j.InitializeParams;
+import org.eclipse.lsp4j.InitializeResult;
+import org.eclipse.lsp4j.Location;
+import org.eclipse.lsp4j.MessageActionItem;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.PublishDiagnosticsParams;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.ShowMessageRequestParams;
+import org.eclipse.lsp4j.SymbolInformation;
+import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
+import org.eclipse.lsp4j.TextDocumentIdentifier;
+import org.eclipse.lsp4j.TextDocumentItem;
+import org.eclipse.lsp4j.TextDocumentPositionParams;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
+import org.eclipse.lsp4j.WorkspaceFolder;
+import org.eclipse.lsp4j.jsonrpc.Launcher;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.eclipse.lsp4j.launch.LSPLauncher;
+import org.eclipse.lsp4j.services.LanguageClient;
+import org.eclipse.lsp4j.services.LanguageServer;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.sendopts.CommandLine;
+import org.netbeans.junit.NbModuleSuite;
+import org.netbeans.junit.NbTestCase;
+import org.netbeans.modules.parsing.impl.indexing.implspi.CacheFolderProvider;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.modules.ModuleInfo;
+import org.openide.modules.Places;
+import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.Utilities;
+
+/**
+ *
+ * @author lahvac
+ */
+public class ServerTest extends NbTestCase {
+
+    private Socket client;
+    private Thread serverThread;
+
+    public ServerTest(String name) {
+        super(name);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        clearWorkDir();
+        ServerSocket srv = new ServerSocket(0);
+        serverThread = new Thread(() -> {
+            try {
+                Socket server = srv.accept();
+
+                Path tempDir = Files.createTempDirectory("lsp-server");
+                File userdir = tempDir.resolve("scratch-user").toFile();
+                File cachedir = tempDir.resolve("scratch-cache").toFile();
+                System.setProperty("netbeans.user", userdir.getAbsolutePath());
+                File varLog = new File(new File(userdir, "var"), "log");
+                varLog.mkdirs();
+                System.setProperty("jdk.home", System.getProperty("java.home")); //for j2seplatform
+                Class<?> main = Class.forName("org.netbeans.core.startup.Main");
+                main.getDeclaredMethod("initializeURLFactory").invoke(null);
+                new File(cachedir, "index").mkdirs();
+                Class jsClass = JavaSource.class;
+                File javaCluster = Utilities.toFile(jsClass.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile().getParentFile();
+                System.setProperty("netbeans.dirs", javaCluster.getAbsolutePath());
+                CacheFolderProvider.getCacheFolderForRoot(Places.getUserDirectory().toURI().toURL(), EnumSet.noneOf(CacheFolderProvider.Kind.class), CacheFolderProvider.Mode.EXISTENT);
+
+                Lookup.getDefault().lookup(ModuleInfo.class); //start the module system
+
+                CommandLine.getDefault().process(new String[] {"--start-java-language-server"}, server.getInputStream(), server.getOutputStream(), System.err, getWorkDir());
+            } catch (Exception ex) {
+                throw new IllegalStateException(ex);
+            }
+        });
+        serverThread.start();
+        client = new Socket(InetAddress.getLocalHost(), srv.getLocalPort());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        serverThread.stop();
+    }
+
+    public void testMain() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test { int i = \"\".hashCode(); }";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        List<Diagnostic>[] diags = new List[1];
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+            @Override
+            public void telemetryEvent(Object arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void publishDiagnostics(PublishDiagnosticsParams params) {
+                synchronized (diags) {
+                    diags[0] = params.getDiagnostics();
+                    diags.notifyAll();
+                }
+            }
+
+            @Override
+            public void showMessage(MessageParams arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @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();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+        assertDiags(diags);//errors
+        assertDiags(diags);//hints
+        int hashCodeStart = code.indexOf("hashCode");
+        Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, hashCodeStart + 2))).get();
+        assertTrue(completion.isRight());
+        List<String> actualItems = completion.getRight().getItems().stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
+        assertEquals(Arrays.asList("Method:hashCode"), actualItems);
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(1);
+        id.setUri(src.toURI().toString());
+        server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, hashCodeStart), new Position(0, hashCodeStart + "hashCode".length())), "hashCode".length(), "equ"))));
+        assertDiags(diags, "Error:0:31-0:34");//errors
+        assertDiags(diags, "Error:0:31-0:34");//hints
+        completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, hashCodeStart + 2))).get();
+        actualItems = completion.getRight().getItems().stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
+        assertEquals(Arrays.asList("Method:equals", "Method:equalsIgnoreCase"), actualItems);
+        completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, 0))).get();
+        actualItems = completion.getRight().getItems().stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
+        assertTrue(actualItems.contains("Keyword:interface"));
+        server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, hashCodeStart), new Position(0, hashCodeStart + "equ".length())), "equ".length(), "hashCode"))));
+        int closingBrace = code.indexOf("}");
+        server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, closingBrace), new Position(0, closingBrace)), 0, "private String c(Object o) {\nreturn o;\n}"))));
+        List<Diagnostic> diagnostics = assertDiags(diags, "Error:1:0-1:9"); //errors
+        assertDiags(diags, "Error:1:0-1:9");//hints
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(1, 0), new Position(1, 9)), new CodeActionContext(Arrays.asList(diagnostics.get(0))))).get();
+        String log = codeActions.toString();
+        assertEquals(log, 2, codeActions.size());
+        assertTrue(log, codeActions.get(0).isRight());
+        CodeAction action = codeActions.get(0).getRight();
+        assertEquals("Cast ...o to String", action.getTitle());
+        assertEquals(1, action.getEdit().getDocumentChanges().size());
+        assertEquals(1, action.getEdit().getDocumentChanges().get(0).getEdits().size());
+        TextEdit edit = action.getEdit().getDocumentChanges().get(0).getEdits().get(0);
+        assertEquals(1, edit.getRange().getStart().getLine());
+        assertEquals(7, edit.getRange().getStart().getCharacter());
+        assertEquals(1, edit.getRange().getEnd().getLine());
+        assertEquals(7, edit.getRange().getEnd().getCharacter());
+        assertEquals("(String) ", edit.getNewText());
+        server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, closingBrace), new Position(0, closingBrace)), 0, "private void assignToSelf(Object o) { o = o; }"))));
+        assertDiags(diags, "Error:1:0-1:9");//errors
+        assertDiags(diags, "Error:1:0-1:9", "Warning:0:81-0:86", "Warning:0:85-0:86");//hints
+    }
+
+    private List<Diagnostic> assertDiags(List<Diagnostic>[] diags, String... expected) {
+        synchronized (diags) {
+            while (diags[0] == null) {
+                try {
+                    diags.wait();
+                } catch (InterruptedException ex) {
+                    //ignore
+                }
+            }
+            Set<String> actualDiags = diags[0].stream()
+                                               .map(d -> d.getSeverity() + ":" +
+                                                         d.getRange().getStart().getLine() + ":" + d.getRange().getStart().getCharacter() + "-" +
+                                                         d.getRange().getEnd().getLine() + ":" + d.getRange().getEnd().getCharacter())
+                                               .collect(Collectors.toSet());
+            String diagsMessage = diags[0].stream()
+                                          .map(d -> d.getSeverity() + ":" +
+                                                    d.getRange().getStart().getLine() + ":" + d.getRange().getStart().getCharacter() + "-" +
+                                                    d.getRange().getEnd().getLine() + ":" + d.getRange().getEnd().getCharacter() + ": " +
+                                                    d.getMessage())
+                                               .collect(Collectors.joining("\n"));
+            assertEquals(diagsMessage, new HashSet<>(Arrays.asList(expected)), actualDiags);
+
+            List<Diagnostic> result = diags[0];
+
+            diags[0] = null;
+
+            return result;
+        }
+    }
+
+    public void testNavigator() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private int field;\n" +
+                      "    public void method() {\n" +
+                      "    }\n" +
+                      "    class Inner {\n" +
+                      "        public void innerMethod() {\n" +
+                      "        }\n" +
+                      "    }\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        FileUtil.refreshFor(getWorkDir());
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+            @Override
+            public void telemetryEvent(Object arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void publishDiagnostics(PublishDiagnosticsParams params) {
+            }
+
+            @Override
+            public void showMessage(MessageParams arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @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();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+        List<Either<SymbolInformation, DocumentSymbol>> symbols = server.getTextDocumentService().documentSymbol(new DocumentSymbolParams(new TextDocumentIdentifier(src.toURI().toString()))).get();
+        String textualSymbols = "";
+        String sep = "";
+        for (Either<SymbolInformation, DocumentSymbol> sym : symbols) {
+            assertTrue(sym.isRight());
+            textualSymbols += sep;
+            textualSymbols += toString(sym.getRight());
+            sep = ", ";
+        }
+        String expected = "Class:Test:Range [\n" +
+                          "  start = Position [\n" +
+                          "    line = 0\n" +
+                          "    character = 0\n" +
+                          "  ]\n" +
+                          "  end = Position [\n" +
+                          "    line = 8\n" +
+                          "    character = 1\n" +
+                          "  ]\n" +
+                          "]:(Class:Inner:Range [\n" +
+                          "  start = Position [\n" +
+                          "    line = 4\n" +
+                          "    character = 4\n" +
+                          "  ]\n" +
+                          "  end = Position [\n" +
+                          "    line = 7\n" +
+                          "    character = 5\n" +
+                          "  ]\n" +
+                          "]:(Method:innerMethod:Range [\n" +
+                          "  start = Position [\n" +
+                          "    line = 5\n" +
+                          "    character = 8\n" +
+                          "  ]\n" +
+                          "  end = Position [\n" +
+                          "    line = 6\n" +
+                          "    character = 9\n" +
+                          "  ]\n" +
+                          "]:()), Field:field:Range [\n" +
+                          "  start = Position [\n" +
+                          "    line = 1\n" +
+                          "    character = 4\n" +
+                          "  ]\n" +
+                          "  end = Position [\n" +
+                          "    line = 1\n" +
+                          "    character = 22\n" +
+                          "  ]\n" +
+                          "]:(), Method:method:Range [\n" +
+                          "  start = Position [\n" +
+                          "    line = 2\n" +
+                          "    character = 4\n" +
+                          "  ]\n" +
+                          "  end = Position [\n" +
+                          "    line = 3\n" +
+                          "    character = 5\n" +
+                          "  ]\n" +
+                          "]:())";
+        assertEquals(expected, textualSymbols);
+    }
+
+    private String toString(DocumentSymbol sym) {
+        return sym.getKind().toString() + ":" +
+               sym.getName() + ":" +
+               sym.getRange() + ":" +
+               sym.getChildren()
+                  .stream()
+                  .map(this::toString)
+                  .collect(Collectors.joining(", ", "(", ")"));
+    }
+
+    public void testGoToDefinition() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private int field;\n" +
+                      "    public void method(int ppp) {\n" +
+                      "        System.err.println(field);\n" +
+                      "        System.err.println(ppp);\n" +
+                      "    }\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        FileUtil.refreshFor(getWorkDir());
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+            @Override
+            public void telemetryEvent(Object arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void publishDiagnostics(PublishDiagnosticsParams params) {
+            }
+
+            @Override
+            public void showMessage(MessageParams arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @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();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+        Position pos = new Position(3, 30);
+        List<? extends Location> definition = server.getTextDocumentService().definition(new TextDocumentPositionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get();
+        assertEquals(1, definition.size());
+        assertEquals(src.toURI().toString(), definition.get(0).getUri());
+        assertEquals(1, definition.get(0).getRange().getStart().getLine());
+        assertEquals(4, definition.get(0).getRange().getStart().getCharacter());
+        pos = new Position(4, 30);
+        definition = server.getTextDocumentService().definition(new TextDocumentPositionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get();
+        assertEquals(1, definition.size());
+        assertEquals(src.toURI().toString(), definition.get(0).getUri());
+        assertEquals(2, definition.get(0).getRange().getStart().getLine());
+        assertEquals(23, definition.get(0).getRange().getStart().getCharacter());
+        //XXX: test jump to another file!
+    }
+
+    public void testOpenProjectOpenJDK() throws Exception {
+        getWorkDir().mkdirs();
+
+        FileObject root = FileUtil.toFileObject(getWorkDir());
+        try (Writer w = new OutputStreamWriter(FileUtil.createData(root, "jdk/src/java.base/share/classes/java/lang/Object.java").getOutputStream(), StandardCharsets.UTF_8)) {
+            w.write("package java.lang; public class Object {}");
+        }
+        FileUtil.createData(root, "jdk/src/java.base/share/classes/impl/Service.java");
+        FileObject javaBaseMI = FileUtil.createData(root, "jdk/src/java.base/share/classes/module-info.java");
+        try (Writer w = new OutputStreamWriter(javaBaseMI.getOutputStream(), StandardCharsets.UTF_8)) {
+            w.write("module java.base { exports java.lang; }");
+        }
+        try (Writer w = new OutputStreamWriter(FileUtil.createData(root, "jdk/src/java.compiler/share/classes/module-info.java").getOutputStream(), StandardCharsets.UTF_8)) {
+            w.write("module java.compiler { }");
+        }
+
+        List<Diagnostic>[] diags = new List[1];
+        boolean[] indexingComplete = new boolean[1];
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+            @Override
+            public void telemetryEvent(Object arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void publishDiagnostics(PublishDiagnosticsParams params) {
+                synchronized (diags) {
+                    diags[0] = params.getDiagnostics();
+                    diags.notifyAll();
+                }
+            }
+
+            @Override
+            public void showMessage(MessageParams params) {
+                if (Server.INDEXING_COMPLETED.equals(params.getMessage())) {
+                    synchronized (indexingComplete) {
+                        indexingComplete[0] = true;
+                        indexingComplete.notifyAll();
+                    }
+                } else {
+                    throw new UnsupportedOperationException("Unexpected message.");
+                }
+            }
+
+            @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();
+        InitializeParams initParams = new InitializeParams();
+        initParams.setWorkspaceFolders(Arrays.asList(new WorkspaceFolder(root.getFileObject("jdk/src/java.base").toURI().toString())));
+        InitializeResult result = server.initialize(initParams).get();
+        synchronized (indexingComplete) {
+            while (!indexingComplete[0]) {
+                try {
+                    indexingComplete.wait();
+                } catch (InterruptedException ex) {
+                    //ignore...
+                }
+            }
+        }
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(javaBaseMI.toURI().toString(), "java", 0, javaBaseMI.asText("UTF-8"))));
+        assertDiags(diags);
+    }
+    
+    public void testMarkOccurrences() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    public int method(int ppp) {\n" +
+                      "        if (ppp < 0) return -1;\n" +
+                      "        else if (ppp > 0) return 1;\n" +
+                      "        else return 0;\n" +
+                      "    }\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        FileUtil.refreshFor(getWorkDir());
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+            @Override
+            public void telemetryEvent(Object arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @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();
+        assertTrue(result.getCapabilities().getDocumentHighlightProvider());
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+        assertHighlights(server.getTextDocumentService().documentHighlight(new TextDocumentPositionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(1, 13))).get(),
+                         "<none>:2:21-2:31", "<none>:3:26-3:35", "<none>:4:13-4:22");
+        assertHighlights(server.getTextDocumentService().documentHighlight(new TextDocumentPositionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(1, 27))).get(),
+                         "<none>:1:26-1:29", "<none>:2:12-2:15", "<none>:3:17-3:20");
+    }
+
+    private void assertHighlights(List<? extends DocumentHighlight> highlights, String... expected) {
+        Set<String> stringHighlights = new HashSet<>();
+        for (DocumentHighlight h : highlights) {
+            DocumentHighlightKind kind = h.getKind();
+            stringHighlights.add((kind != null ? kind.name() : "<none>") + ":" +
+                                 h.getRange().getStart().getLine() + ":" + h.getRange().getStart().getCharacter() + "-" +
+                                 h.getRange().getEnd().getLine() + ":" + h.getRange().getEnd().getCharacter());
+        }
+        assertEquals(new HashSet<>(Arrays.asList(expected)),
+                     stringHighlights);
+    }
+}
diff --git a/java/java.lsp.server/vscode/.gitignore b/java/java.lsp.server/vscode/.gitignore
new file mode 100644
index 0000000..e23e71c
--- /dev/null
+++ b/java/java.lsp.server/vscode/.gitignore
@@ -0,0 +1,3 @@
+nb-java-lsp-server
+node_modules
+out
diff --git a/java/java.lsp.server/vscode/README.md b/java/java.lsp.server/vscode/README.md
new file mode 100644
index 0000000..1819793
--- /dev/null
+++ b/java/java.lsp.server/vscode/README.md
@@ -0,0 +1,39 @@
+<!--
+
+    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.
+
+-->
+
+This is an extension for VS Code. Based on "lsp-sample" from:
+https://github.com/microsoft/vscode-extension-samples
+
+# Building
+
+To build the VS Code extension do:
+ * cd java/java.lsp.server
+ * ant build-lsp-server
+ * cd vscode
+ * npm install
+ * npm run compile
+ * (cd ../build/; npm install vsce)
+ * ../build/node_modules/vsce/out/vsce package
+
+The resulting extension is then in this directory, with the vsix extension.
+
+
+
diff --git a/java/java.lsp.server/vscode/package-lock.json b/java/java.lsp.server/vscode/package-lock.json
new file mode 100644
index 0000000..b87b0c0
--- /dev/null
+++ b/java/java.lsp.server/vscode/package-lock.json
@@ -0,0 +1,26 @@
+{
+	"name": "netbeans-java",
+	"version": "1.0.0",
+	"lockfileVersion": 1,
+	"requires": true,
+	"dependencies": {
+		"@types/mocha": {
+			"version": "5.2.0",
+			"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.0.tgz",
+			"integrity": "sha512-YeDiSEzznwZwwp766SJ6QlrTyBYUGPSIwmREHVTmktUYiT/WADdWtpt9iH0KuUSf8lZLdI4lP0X6PBzPo5//JQ==",
+			"dev": true
+		},
+		"@types/node": {
+			"version": "8.10.18",
+			"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.18.tgz",
+			"integrity": "sha512-WoepSz+wJlU5Bjq5oK6cO1oXe2FgPcjMtQPgKPS8fVaTAD0lxkScMCCbMimdkVCsykqaA4lvHWz3cmj28yimhA==",
+			"dev": true
+		},
+		"typescript": {
+			"version": "2.8.3",
+			"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz",
+			"integrity": "sha512-K7g15Bb6Ra4lKf7Iq2l/I5/En+hLIHmxWZGq3D4DIRNFxMNV6j2SHSvDOqs2tGd4UvD/fJvrwopzQXjLrT7Itw==",
+			"dev": true
+		}
+	}
+}
diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json
new file mode 100644
index 0000000..f3fdfac
--- /dev/null
+++ b/java/java.lsp.server/vscode/package.json
@@ -0,0 +1,51 @@
+{
+	"name": "netbeans-java",
+	"description": "A NetBeans Java plugin for Visual Studio Code",
+	"author": "Jan Lahoda",
+	"license": "MIT",
+	"version": "1.0.0",
+	"repository": {
+		"type": "git",
+		"url": "TBD"
+	},
+	"publisher": "jlahoda",
+	"categories": [],
+	"keywords": [
+		"multi-root ready"
+	],
+	"engines": {
+		"vscode": "^1.26.0"
+	},
+	"activationEvents": [
+		"onLanguage:java"
+	],
+	"main": "./out/extension",
+	"scripts": {
+		"vscode:prepublish": "npm run update-vscode && npm run compile",
+		"compile": "tsc -p ./tsconfig.json",
+		"watch": "tsc -w -p ./tsconfig.json",
+		"postinstall": "vscode-install",
+		"update-vscode": "vscode-install"
+	},
+	"devDependencies": {
+		"@types/mocha": "^5.2.0",
+		"@types/node": "^8.0.0",
+		"typescript": "2.8.3",
+		"vscode": "^1.1.21"
+	},
+	"dependencies": {
+		"vscode-languageclient": "^4.1.4"
+	},
+        "contributes": {
+            "configuration": {
+                "title": "Java",
+                "properties": {
+                    "netbeans.jdkhome": {
+                         "type": ["string", "null"],
+                         "default": null,
+                         "description": "Specifies the JDK on which the Java language server should be run"
+                    }
+                }
+            }
+        }
+}
diff --git a/java/java.lsp.server/vscode/src/VerifyJDK11.java b/java/java.lsp.server/vscode/src/VerifyJDK11.java
new file mode 100644
index 0000000..80e5928
--- /dev/null
+++ b/java/java.lsp.server/vscode/src/VerifyJDK11.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+public class VerifyJDK11 {
+    public static void main(String[] args) {
+        if (Runtime.version().compareTo(Runtime.Version.parse("11-build")) < 0)
+            throw new IllegalStateException("Not JDK 11+.");
+    }
+}
diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts
new file mode 100644
index 0000000..e1f39b8
--- /dev/null
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -0,0 +1,79 @@
+/* --------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ * ------------------------------------------------------------------------------------------ */
+/*Heavily influenced by the extension for Kotlin Language Server which is:
+ * Copyright (c) 2016 George Fraser
+ * Copyright (c) 2018 fwcd
+ */
+'use strict';
+
+import { window, workspace, ExtensionContext } from 'vscode';
+
+import {
+	LanguageClient,
+	LanguageClientOptions,
+	ServerOptions
+} from 'vscode-languageclient';
+
+import * as path from 'path';
+import { execSync } from 'child_process';
+
+let client: LanguageClient;
+
+export function activate(context: ExtensionContext) {
+    //verify acceptable JDK is available/set:
+    let specifiedJDK: string = workspace.getConfiguration('netbeans').get('jdkhome');
+
+    try {
+        let targetJava = specifiedJDK != null ? specifiedJDK + '/bin/java' : 'java';
+        execSync(targetJava + ' ' + context.extensionPath + '/src/VerifyJDK11.java');
+    } catch (e) {
+        window.showErrorMessage('The Java language server needs a JDK 11 to run, but none found. Please configure it under File/Preferences/Settings/Extensions/Java and restart VS Code.');
+        return ;
+    }
+    let serverPath = path.resolve(context.extensionPath, "nb-java-lsp-server", "bin", "nb-java-lsp-server");
+
+    let serverOptions: ServerOptions;
+    let args: string[] = [];
+    if (specifiedJDK != null) {
+        args = ['--jdkhome', specifiedJDK];
+    }
+    serverOptions = {
+        command: serverPath,
+        args: args,
+        options: { cwd: workspace.rootPath }
+    }
+
+    // Options to control the language client
+    let clientOptions: LanguageClientOptions = {
+        // Register the server for java documents
+        documentSelector: ['java'],
+        synchronize: {
+            configurationSection: 'java',
+            fileEvents: [
+                workspace.createFileSystemWatcher('**/*.java')
+            ]
+        },
+        outputChannelName: 'Java',
+        revealOutputChannelOn: 4 // never
+    }
+
+	// Create the language client and start the client.
+	client = new LanguageClient(
+		'java',
+		'NetBeans Java',
+		serverOptions,
+		clientOptions
+	);
+
+	// Start the client. This will also launch the server
+	client.start();
+}
+
+export function deactivate(): Thenable<void> {
+	if (!client) {
+		return undefined;
+	}
+	return client.stop();
+}
diff --git a/java/java.lsp.server/vscode/tsconfig.json b/java/java.lsp.server/vscode/tsconfig.json
new file mode 100644
index 0000000..f2e9de2
--- /dev/null
+++ b/java/java.lsp.server/vscode/tsconfig.json
@@ -0,0 +1,20 @@
+{
+	"compilerOptions": {
+		"module": "commonjs",
+		"target": "es6",
+		"outDir": "out",
+		"rootDir": "src",
+		"lib": [ "es6" ],
+		"sourceMap": true,
+		"noImplicitAny": true,
+		"noImplicitReturns": true,
+		"noUnusedLocals": true,
+		"noUnusedParameters": false,
+	},
+	"include": [
+		"src"
+	],
+	"exclude": [
+		"node_modules"
+	]
+}
diff --git a/java/java.openjdk.project/src/org/netbeans/modules/java/openjdk/jtreg/ClassPathProviderImpl.java b/java/java.openjdk.project/src/org/netbeans/modules/java/openjdk/jtreg/ClassPathProviderImpl.java
index de24904..803ef58 100644
--- a/java/java.openjdk.project/src/org/netbeans/modules/java/openjdk/jtreg/ClassPathProviderImpl.java
+++ b/java/java.openjdk.project/src/org/netbeans/modules/java/openjdk/jtreg/ClassPathProviderImpl.java
@@ -71,11 +71,12 @@ public class ClassPathProviderImpl implements ClassPathProvider {
             if (testRoot != null) {
                 boolean javac = (Utilities.isLangtoolsRepository(search.getParent()) || search.getNameExt().equals("langtools")) &&
                                 ShortcutUtils.getDefault().shouldUseCustomTest("langtools", FileUtil.getRelativePath(search.getParent(), file));
+                FileObject keyRoot = javac ? search.getNameExt().equals("langtools") ? Utilities.getLangtoolsKeyRoot(search.getParent().getParent()) : Utilities.getLangtoolsKeyRoot(search.getParent()) : null;
                 //XXX: hack to make things work for langtools:
                 switch (type) {
                     case ClassPath.COMPILE:
                         if (javac) {
-                            ClassPath langtoolsCP = ClassPath.getClassPath(Utilities.getLangtoolsKeyRoot(search.getParent()), ClassPath.COMPILE);
+                            ClassPath langtoolsCP = ClassPath.getClassPath(keyRoot, ClassPath.COMPILE);
                             Library testngLib = LibraryManager.getDefault().getLibrary("testng");
 
                             if (testngLib != null) {
@@ -92,7 +93,7 @@ public class ClassPathProviderImpl implements ClassPathProvider {
                     case ClassPath.BOOT:
                         if (javac) {
                             try {
-                                ClassPath langtoolsBCP = ClassPath.getClassPath(Utilities.getLangtoolsKeyRoot(search.getParent()), ClassPath.BOOT);
+                                ClassPath langtoolsBCP = ClassPath.getClassPath(keyRoot, ClassPath.BOOT);
                                 List<URL> roots = new ArrayList<>();
                                 for (String rootPaths : new String[] {"build/classes/",
                                                                       "build/java.compiler/classes/",
diff --git a/java/java.openjdk.project/src/org/netbeans/modules/java/openjdk/jtreg/Utilities.java b/java/java.openjdk.project/src/org/netbeans/modules/java/openjdk/jtreg/Utilities.java
index dfa87a9..21b50d8 100644
--- a/java/java.openjdk.project/src/org/netbeans/modules/java/openjdk/jtreg/Utilities.java
+++ b/java/java.openjdk.project/src/org/netbeans/modules/java/openjdk/jtreg/Utilities.java
@@ -60,10 +60,15 @@ public class Utilities {
     }
 
     public static FileObject getLangtoolsKeyRoot(FileObject root) {
-        FileObject javaBase = BuildUtils.getFileObject(root, "src/jdk.compiler/share/classes");
+        FileObject jdkCompiler = BuildUtils.getFileObject(root, "src/jdk.compiler/share/classes");
 
-        if (javaBase != null)
-            return javaBase;
+        if (jdkCompiler != null)
+            return jdkCompiler;
+
+        jdkCompiler = root.getFileObject("src/jdk.compiler/share/classes");
+
+        if (jdkCompiler != null)
+            return jdkCompiler;
 
         return BuildUtils.getFileObject(root, "src/share/classes");
     }
diff --git a/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementOpen.java b/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementOpen.java
index 5f85fe7..f16649f 100644
--- a/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementOpen.java
+++ b/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementOpen.java
@@ -44,6 +44,7 @@ import org.netbeans.modules.java.BinaryElementOpen;
 import org.netbeans.modules.java.source.JavaSourceAccessor;
 import org.netbeans.modules.java.source.parsing.ClassParser;
 import org.netbeans.modules.java.source.parsing.FileObjects;
+import org.netbeans.modules.java.source.ui.ElementOpenAccessor;
 import org.openide.awt.StatusDisplayer;
 import org.openide.cookies.EditorCookie;
 import org.openide.filesystems.FileObject;
@@ -448,5 +449,14 @@ public final class ElementOpen {
         }
     
     }
-    
+
+    static {
+        ElementOpenAccessor.setInstance(new ElementOpenAccessor() {
+            @Override
+            public Object[] getOpenInfo(ClasspathInfo cpInfo, ElementHandle<? extends Element> el, AtomicBoolean cancel) {
+                return ElementOpen.getOpenInfo(cpInfo, el, cancel);
+            }
+        });
+    }
+
 }
diff --git a/java/java.sourceui/src/org/netbeans/modules/java/source/ui/ElementOpenAccessor.java b/java/java.sourceui/src/org/netbeans/modules/java/source/ui/ElementOpenAccessor.java
new file mode 100644
index 0000000..2789371
--- /dev/null
+++ b/java/java.sourceui/src/org/netbeans/modules/java/source/ui/ElementOpenAccessor.java
@@ -0,0 +1,52 @@
+/*
+ * 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.source.ui;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.lang.model.element.Element;
+import org.netbeans.api.java.source.ClasspathInfo;
+import org.netbeans.api.java.source.ElementHandle;
+import org.netbeans.api.java.source.ui.ElementOpen;
+
+/**
+ *
+ * @author jlahoda
+ */
+public abstract class ElementOpenAccessor {
+    
+    private static ElementOpenAccessor instance;
+
+    public synchronized static ElementOpenAccessor getInstance() {
+        return instance;
+    }
+
+    public synchronized static void setInstance(ElementOpenAccessor instance) {
+        ElementOpenAccessor.instance = instance;
+    }
+    
+    public abstract Object[] getOpenInfo(final ClasspathInfo cpInfo, final ElementHandle<? extends Element> el, AtomicBoolean cancel);
+
+    static {
+        try {
+            Class.forName(ElementOpen.class.getName(), true, ElementOpen.class.getClassLoader());
+        } catch (ClassNotFoundException ex) {
+            //ignore
+        }
+    }
+}
diff --git a/java/lib.nbjavac/src/org/netbeans/lib/nbjavac/services/NBJavacTrees.java b/java/lib.nbjavac/src/org/netbeans/lib/nbjavac/services/NBJavacTrees.java
index aa50942..bdd05b5 100644
--- a/java/lib.nbjavac/src/org/netbeans/lib/nbjavac/services/NBJavacTrees.java
+++ b/java/lib.nbjavac/src/org/netbeans/lib/nbjavac/services/NBJavacTrees.java
@@ -27,8 +27,13 @@ import com.sun.tools.javac.tree.JCTree;
 import com.sun.tools.javac.tree.TreeInfo;
 import com.sun.tools.javac.tree.TreeMaker;
 import com.sun.tools.javac.util.Context;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.lang.model.element.Element;
 import org.netbeans.lib.nbjavac.services.NBTreeMaker.IndexedClassDecl;
 
@@ -55,7 +60,14 @@ public class NBJavacTrees extends JavacTrees {
     protected Copier createCopier(TreeMaker make) {
         return new Copier(make) {
             @Override public JCTree visitClass(ClassTree node, JCTree p) {
-                JCTree result = super.visitClass(node, p);
+                JCTree result;
+                try {
+                    MethodHandle superVisitClass = MethodHandles.lookup().findSpecial(Copier.class, "visitClass", MethodType.methodType(JCTree.class, new Class[]{ClassTree.class, JCTree.class}), getClass());
+                    result = (JCTree) superVisitClass.invokeExact(this, node, p);
+                } catch (Throwable ex) {
+                    Logger.getLogger(NBJavacTrees.class.getName()).log(Level.FINE, null, ex);
+                    result = super.visitClass(node, p);
+                }
 
                 if (node instanceof IndexedClassDecl && result instanceof IndexedClassDecl) {
                     ((IndexedClassDecl) result).index = ((IndexedClassDecl) node).index;
diff --git a/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps b/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps
index e6a9965..feb28bc 100644
--- a/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps
+++ b/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps
@@ -68,5 +68,10 @@ enterprise/javaee7.api/external/jsr181-api-1.0-MR1.jar java/websvc.jaxws21api/ex
 # the jcodings is used internally in two unrelated modules:
 ide/libs.bytelist/external/jcodings-1.0.18.jar ide/textmate.lexer/external/jcodings-1.0.18.jar
 
+# ide/lsp.client and java/java.lsp.server both use LSP libraries, but are independent:
+ide/lsp.client/external/org.eclipse.xtend.lib-2.14.0.jar java/java.lsp.server/external/org.eclipse.xtend.lib-2.14.0.jar
+ide/lsp.client/external/org.eclipse.xtend.lib.macro-2.14.0.jar java/java.lsp.server/external/org.eclipse.xtend.lib.macro-2.14.0.jar
+ide/lsp.client/external/org.eclipse.xtext.xbase.lib-2.14.0.jar java/java.lsp.server/external/org.eclipse.xtext.xbase.lib-2.14.0.jar
+
 # Derby jar is used when run tests, so we can ignore the duplicates
 ide/db/external/derby-10.2.2.0.jar ide/db.metadata.model/external/derby-10.2.2.0.jar
diff --git a/nbbuild/cluster.properties b/nbbuild/cluster.properties
index d16330a..0b0565e 100644
--- a/nbbuild/cluster.properties
+++ b/nbbuild/cluster.properties
@@ -627,6 +627,7 @@ nb.cluster.java=\
         java.j2seproject,\
         java.kit,\
         java.lexer,\
+        java.lsp.server,\
         java.metrics,\
         java.module.graph,\
         java.navigation,\
diff --git a/nbbuild/licenses/MIT-vscode-ext b/nbbuild/licenses/MIT-vscode-ext
new file mode 100644
index 0000000..7cb39aa
--- /dev/null
+++ b/nbbuild/licenses/MIT-vscode-ext
@@ -0,0 +1,19 @@
+Copyright (c) Microsoft Corporation
+Copyright (c) 2016 George Fraser
+Copyright (c) 2018 fwcd
+
+All rights reserved.
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
+(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/nbbuild/licenses/names.properties b/nbbuild/licenses/names.properties
index b323e0a..e417236 100644
--- a/nbbuild/licenses/names.properties
+++ b/nbbuild/licenses/names.properties
@@ -61,6 +61,7 @@ MIT-jruby=MIT license JRuby variant
 MIT-nodejs=MIT license NodeJS variant plus node external libraries licenses
 MIT-phpstan=MIT license PHPStan variant
 MIT-validator=MIT license validator variant
+MIT-vscode-ext=MIT license VS Code variant
 MPL-1.0=MPL 1.0 license
 CDDL-SPL=CDDL 1.0 + SPL 1.0
 W3C2=W3C Software and Document Notice and License


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

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