Merge pull request #2664 from JaroslavTulach/ReadmeChangelogVersionForRelease12.2.1

Readme, changelog and version for release 12.2.1
diff --git a/.gitignore b/.gitignore
index d9d0aa9..3c10d76 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,7 @@
 /nbi/engine/native/*/*/dist/
 /nb-javac/
 /java.source.nbjavac/test/test-nb-javac/nbproject/private/
+/java/java.lsp.server/vscode/.vscode-test/
 /harness/apisupport.harness/windows-launcher-src/*.exe
 /harness/apisupport.harness/windows-launcher-src/*.res
 /ide/versioning/test/qa-functional/data/tck.properties
diff --git a/.travis.yml b/.travis.yml
index 8ccec65..24ff19a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -747,7 +747,7 @@
         - name: Build the Visual Studio Code extension for Java
           jdk: openjdk8
           before_install:
-            - nvm install 8
+            - nvm install 12.14.1
           env:
             - OPTS="-Dmetabuild.jsonurl=https://raw.githubusercontent.com/apache/netbeans-jenkins-lib/master/meta/netbeansrelease.json -quiet -Dcluster.config=java -Djavac.compilerargs=-nowarn -Dbuild.compiler.deprecation=false -Dtest-unit-sys-prop.ignore.random.failures=true"
           before_script:
@@ -755,6 +755,7 @@
             - ant $OPTS build
           script:
             - (cd java/java.lsp.server; ant build-vscode-ext)
+            - (cd java/java.lsp.server; ant test-vscode-ext)
 
         - name: "GraalVM Tests (latest)"
           jdk: openjdk8
diff --git a/cpplite/cpplite.editor/nbproject/project.xml b/cpplite/cpplite.editor/nbproject/project.xml
index db43b93..58f824f 100644
--- a/cpplite/cpplite.editor/nbproject/project.xml
+++ b/cpplite/cpplite.editor/nbproject/project.xml
@@ -26,6 +26,15 @@
             <code-name-base>org.netbeans.modules.cpplite.editor</code-name-base>
             <module-dependencies>
                 <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.63</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.api.templates</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -66,7 +75,6 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>0</release-version>
-                        <!--<implementation-version/>-->
                     </run-dependency>
                 </dependency>
                 <dependency>
@@ -79,6 +87,14 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.parsing.indexing</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>9.17</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.projectapi</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/PathRecognizerImpl.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/PathRecognizerImpl.java
new file mode 100644
index 0000000..2436f94
--- /dev/null
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/PathRecognizerImpl.java
@@ -0,0 +1,53 @@
+/*
+ * 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.cpplite.editor;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.modules.cpplite.editor.file.MIMETypes;
+import org.netbeans.modules.parsing.spi.indexing.PathRecognizer;
+import org.openide.util.lookup.ServiceProvider;
+
+@ServiceProvider(service=PathRecognizer.class)
+public class PathRecognizerImpl extends PathRecognizer{
+
+    @Override
+    public Set<String> getSourcePathIds() {
+        return Collections.singleton(ClassPath.SOURCE);
+    }
+
+    @Override
+    public Set<String> getLibraryPathIds() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Set<String> getBinaryLibraryPathIds() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Set<String> getMimeTypes() {
+        return new HashSet<>(Arrays.asList(MIMETypes.C, MIMETypes.CPP, MIMETypes.H, MIMETypes.HPP));
+    }
+
+}
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CDataObject.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CDataObject.java
index bbaa61d..eb6c211 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CDataObject.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CDataObject.java
@@ -100,8 +100,13 @@
     @ActionReference(
             path = "Editors/" + MIMETypes.CPP + "/Popup",
             id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.WhereUsedAction"),
-            position = 1400,
-            separatorAfter = 1450
+            position = 1400
+    ),
+    @ActionReference(
+            path = "Editors/" + MIMETypes.CPP + "/Popup",
+            id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.RenameAction"),
+            position = 1500,
+            separatorAfter = 1550
     )
 })
 @GrammarRegistration(grammar="resources/c.tmLanguage.json", mimeType=MIMETypes.C)
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CPPDataObject.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CPPDataObject.java
index b9fada4..294ab0a 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CPPDataObject.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/CPPDataObject.java
@@ -100,8 +100,13 @@
     @ActionReference(
             path = "Editors/" + MIMETypes.CPP + "/Popup",
             id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.WhereUsedAction"),
-            position = 1400,
-            separatorAfter = 1450
+            position = 1400
+    ),
+    @ActionReference(
+            path = "Editors/" + MIMETypes.CPP + "/Popup",
+            id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.RenameAction"),
+            position = 1500,
+            separatorAfter = 1550
     )
 })
 @GrammarRegistration(grammar="resources/cpp.tmLanguage.json", mimeType=MIMETypes.CPP)
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HDataObject.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HDataObject.java
index df473e3..be8d498 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HDataObject.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HDataObject.java
@@ -100,8 +100,13 @@
     @ActionReference(
             path = "Editors/" + MIMETypes.CPP + "/Popup",
             id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.WhereUsedAction"),
-            position = 1400,
-            separatorAfter = 1450
+            position = 1400
+    ),
+    @ActionReference(
+            path = "Editors/" + MIMETypes.CPP + "/Popup",
+            id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.RenameAction"),
+            position = 1500,
+            separatorAfter = 1550
     )
 })
 @GrammarRegistration(grammar="resources/c.tmLanguage.json", mimeType=MIMETypes.H)
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HPPDataObject.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HPPDataObject.java
index ea6349d..1222141 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HPPDataObject.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/file/HPPDataObject.java
@@ -100,8 +100,13 @@
     @ActionReference(
             path = "Editors/" + MIMETypes.CPP + "/Popup",
             id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.WhereUsedAction"),
-            position = 1400,
-            separatorAfter = 1450
+            position = 1400
+    ),
+    @ActionReference(
+            path = "Editors/" + MIMETypes.CPP + "/Popup",
+            id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.RenameAction"),
+            position = 1500,
+            separatorAfter = 1550
     )
 })
 @GrammarRegistration(grammar="resources/cpp.tmLanguage.json", mimeType=MIMETypes.HPP)
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/LanguageServerImpl.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/LanguageServerImpl.java
index 52478eb..27a3971 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/LanguageServerImpl.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/LanguageServerImpl.java
@@ -66,7 +66,7 @@
 
     private static final Logger LOG = Logger.getLogger(LanguageServerImpl.class.getName());
 
-    private Map<Project, LanguageServerDescription> prj2Server = new HashMap<>();
+    private static final Map<Project, LanguageServerDescription> prj2Server = new HashMap<>();
 
     @Override
     public LanguageServerDescription startServer(Lookup lookup) {
@@ -117,8 +117,18 @@
                             command.add("--clang-tidy");
                             command.add("--completion-style=detailed");
                         }
-                        Process process = new ProcessBuilder(command).redirectError(Redirect.INHERIT).start();
-                        return LanguageServerDescription.create(new CopyInput(process.getInputStream(), System.err), new CopyOutput(process.getOutputStream(), System.err), process);
+                        ProcessBuilder builder = new ProcessBuilder(command);
+                        if (LOG.isLoggable(Level.FINEST)) {
+                            builder.redirectError(Redirect.INHERIT);
+                        }
+                        Process process = builder.start();
+                        InputStream in = process.getInputStream();
+                        OutputStream out = process.getOutputStream();
+                        if (LOG.isLoggable(Level.FINEST)) {
+                            in = new CopyInput(in, System.err);
+                            out = new CopyOutput(out, System.err);
+                        }
+                        return LanguageServerDescription.create(in, out, process);
                     }
                     return null;
                 } catch (IOException ex) {
diff --git a/cpplite/cpplite.project/nbproject/project.xml b/cpplite/cpplite.project/nbproject/project.xml
index a2d843f..993b7dc 100644
--- a/cpplite/cpplite.project/nbproject/project.xml
+++ b/cpplite/cpplite.project/nbproject/project.xml
@@ -26,6 +26,15 @@
             <code-name-base>org.netbeans.modules.cpplite.project</code-name-base>
             <module-dependencies>
                 <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.63</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.api.templates</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
diff --git a/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteProject.java b/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteProject.java
index 4182887..e14fc4c 100644
--- a/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteProject.java
+++ b/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteProject.java
@@ -18,17 +18,24 @@
  */
 package org.netbeans.modules.cpplite.project;
 
+import java.beans.PropertyChangeListener;
 import java.io.IOException;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.prefs.BackingStoreException;
 import java.util.prefs.Preferences;
+import javax.swing.Icon;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.classpath.GlobalPathRegistry;
 import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectInformation;
 import org.netbeans.api.project.ProjectManager;
 import org.netbeans.modules.cpplite.project.ui.customizer.CustomizerProviderImpl;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
 import org.netbeans.spi.project.ProjectFactory;
 import org.netbeans.spi.project.ProjectFactory2;
 import org.netbeans.spi.project.ProjectState;
 import org.netbeans.spi.project.ui.PrivilegedTemplates;
+import org.netbeans.spi.project.ui.ProjectOpenedHook;
 import org.netbeans.spi.project.ui.RecommendedTemplates;
 import org.openide.filesystems.FileObject;
 import org.openide.util.ImageUtilities;
@@ -92,6 +99,8 @@
                                     new CPPLiteCProjectConfigurationProvider(getRootPreferences(projectDirectory)),
                                     new RecommendedTemplatesImpl(),
                                     new PrivilegedTemplatesImpl(),
+                                    new ProjectInfo(this),
+                                    new ProjectOpenHookImpl(this),
                                     this);
         buildConfigurations.set(BuildConfiguration.read(getBuildPreferences(projectDirectory)));
     }
@@ -130,6 +139,10 @@
         getRootPreferences(projectDirectory).put(KEY_COMPILE_COMMANDS_EXECUTABLE, compileCommandsExecutable);
     }
 
+    private static Icon loadProjectIcon() {
+        return ImageUtilities.image2Icon(ImageUtilities.loadImage("org/netbeans/modules/cpplite/project/resources/project.gif"));
+    }
+
     @ServiceProvider(service=ProjectFactory.class)
     public static final class FactoryImpl implements ProjectFactory2 {
 
@@ -137,7 +150,7 @@
         public ProjectManager.Result isProject2(FileObject projectDirectory) {
             Preferences prefs = getRootPreferences(projectDirectory, false);
             if (prefs != null && prefs.getBoolean(KEY_IS_PROJECT, false)) {
-                return new ProjectManager.Result(ImageUtilities.image2Icon(ImageUtilities.loadImage("org/netbeans/modules/cpplite/project/resources/project.gif")));
+                return new ProjectManager.Result(loadProjectIcon());
             }
             return null;
         }
@@ -187,4 +200,60 @@
             return TEMPLATES;
         }
     }
+
+    private static final class ProjectInfo implements ProjectInformation {
+
+        private final Project prj;
+
+        public ProjectInfo(Project prj) {
+            this.prj = prj;
+        }
+
+        @Override
+        public String getName() {
+            return prj.getProjectDirectory().getNameExt();
+        }
+
+        @Override
+        public String getDisplayName() {
+            return prj.getProjectDirectory().getNameExt();
+        }
+
+        @Override
+        public Icon getIcon() {
+            return loadProjectIcon();
+        }
+
+        @Override
+        public Project getProject() {
+            return prj;
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener listener) {}
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener listener) {}
+
+    }
+
+    private static final class ProjectOpenHookImpl extends ProjectOpenedHook {
+
+        private final ClassPath source;
+
+        public ProjectOpenHookImpl(Project prj) {
+            this.source = ClassPathSupport.createClassPath(prj.getProjectDirectory());
+        }
+
+        @Override
+        protected void projectOpened() {
+            GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {source});
+        }
+
+        @Override
+        protected void projectClosed() {
+            GlobalPathRegistry.getDefault().unregister(ClassPath.SOURCE, new ClassPath[] {source});
+        }
+
+    }
 }
diff --git a/extide/gradle/nbproject/project.xml b/extide/gradle/nbproject/project.xml
index 69cf6d2..60dadee 100644
--- a/extide/gradle/nbproject/project.xml
+++ b/extide/gradle/nbproject/project.xml
@@ -57,15 +57,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.47.1</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.netbeans.api.progress.nb</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>1.47.1</specification-version>
+                        <specification-version>1.57.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/extide/gradle/src/org/netbeans/modules/gradle/api/execute/GradleDistributionManager.java b/extide/gradle/src/org/netbeans/modules/gradle/api/execute/GradleDistributionManager.java
index 710c0a4..22a2b75 100644
--- a/extide/gradle/src/org/netbeans/modules/gradle/api/execute/GradleDistributionManager.java
+++ b/extide/gradle/src/org/netbeans/modules/gradle/api/execute/GradleDistributionManager.java
@@ -57,7 +57,6 @@
 import org.json.simple.parser.JSONParser;
 import org.json.simple.parser.ParseException;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.modules.gradle.api.NbGradleProject;
 import org.netbeans.modules.gradle.spi.GradleFiles;
 import org.openide.awt.Notification;
@@ -500,7 +499,7 @@
         })
         public DownloadTask(GradleDistribution dist) {
             this.dist = dist;
-            handle = ProgressHandleFactory.createSystemHandle(Bundle.TIT_Download_Gradle(dist.getVersion()));
+            handle = ProgressHandle.createSystemHandle(Bundle.TIT_Download_Gradle(dist.getVersion()), null);
             notification = NotificationDisplayer.getDefault().notify(
                     Bundle.TIT_Download_Gradle(dist.getVersion()),
                     NbGradleProject.getIcon(),
diff --git a/extide/gradle/src/org/netbeans/modules/gradle/execute/GradleDaemonExecutor.java b/extide/gradle/src/org/netbeans/modules/gradle/execute/GradleDaemonExecutor.java
index 410b324..219be06 100644
--- a/extide/gradle/src/org/netbeans/modules/gradle/execute/GradleDaemonExecutor.java
+++ b/extide/gradle/src/org/netbeans/modules/gradle/execute/GradleDaemonExecutor.java
@@ -51,7 +51,6 @@
 import org.gradle.tooling.ProgressEvent;
 import org.gradle.tooling.ProjectConnection;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.api.project.ProjectInformation;
 import org.netbeans.api.project.ProjectUtils;
 import org.netbeans.modules.gradle.NbGradleProjectImpl;
@@ -91,7 +90,7 @@
     @SuppressWarnings("LeakingThisInConstructor")
     public GradleDaemonExecutor(RunConfig config) {
         super(config);
-        handle = ProgressHandleFactory.createHandle(config.getTaskDisplayName(), this, new AbstractAction() {
+        handle = ProgressHandle.createHandle(config.getTaskDisplayName(), this, new AbstractAction() {
 
             @Override
             public void actionPerformed(ActionEvent e) {
diff --git a/ide/editor.lib2/nbproject/project.properties b/ide/editor.lib2/nbproject/project.properties
index 4069275..cb51f55 100644
--- a/ide/editor.lib2/nbproject/project.properties
+++ b/ide/editor.lib2/nbproject/project.properties
@@ -18,7 +18,7 @@
 is.autoload=true
 javac.source=1.8
 javac.compilerargs=-Xlint:unchecked
-spec.version.base=2.30.0
+spec.version.base=2.30.1
 
 javadoc.arch=${basedir}/arch.xml
 javadoc.apichanges=${basedir}/apichanges.xml
diff --git a/ide/extexecution/nbproject/project.xml b/ide/extexecution/nbproject/project.xml
index 7119b1a..2eeb431 100644
--- a/ide/extexecution/nbproject/project.xml
+++ b/ide/extexecution/nbproject/project.xml
@@ -40,15 +40,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.40</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.netbeans.api.progress.nb</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>1.40</specification-version>
+                        <specification-version>1.57.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/ide/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java b/ide/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java
index 11a5acf..1c1de51 100644
--- a/ide/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java
+++ b/ide/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java
@@ -34,7 +34,6 @@
 import javax.swing.AbstractAction;
 import org.netbeans.api.annotations.common.NonNull;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.api.extexecution.ExecutionDescriptor.InputProcessorFactory;
 import org.netbeans.api.extexecution.ExecutionDescriptor.InputProcessorFactory2;
 import org.netbeans.api.extexecution.ExecutionDescriptor.LineConvertorFactory;
@@ -379,7 +378,7 @@
             return null;
         }
 
-        ProgressHandle handle = ProgressHandleFactory.createHandle(displayName,
+        ProgressHandle handle = ProgressHandle.createHandle(displayName,
                 cancellable, new ProgressAction(inputOutput));
 
         handle.setInitialDelay(0);
diff --git a/ide/lsp.client/nbproject/project.xml b/ide/lsp.client/nbproject/project.xml
index 14c73d2..182caec 100644
--- a/ide/lsp.client/nbproject/project.xml
+++ b/ide/lsp.client/nbproject/project.xml
@@ -111,6 +111,15 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.editor.fold</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.55</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.editor.indent</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -165,6 +174,15 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.jumpto</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.64</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.lexer</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -183,6 +201,14 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.parsing.indexing</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>9.17</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.projectapi</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -192,6 +218,15 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.queries</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.53</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.refactoring.api</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java
index 27d5d18..b65920a 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java
@@ -51,6 +51,7 @@
 import org.eclipse.lsp4j.InitializeResult;
 import org.eclipse.lsp4j.ResourceOperationKind;
 import org.eclipse.lsp4j.ServerCapabilities;
+import org.eclipse.lsp4j.SymbolCapabilities;
 import org.eclipse.lsp4j.SymbolKind;
 import org.eclipse.lsp4j.SymbolKindCapabilities;
 import org.eclipse.lsp4j.TextDocumentClientCapabilities;
@@ -61,6 +62,7 @@
 import org.eclipse.lsp4j.services.LanguageServer;
 import org.eclipse.lsp4j.services.TextDocumentService;
 import org.eclipse.lsp4j.services.WorkspaceService;
+import org.eclipse.lsp4j.util.Preconditions;
 import org.netbeans.api.editor.mimelookup.MimeLookup;
 import org.netbeans.api.progress.*;
 import org.netbeans.api.project.FileOwnerQuery;
@@ -89,6 +91,11 @@
  */
 public class LSPBindings {
 
+    static {
+        //Don't perform null checks. The servers may not adhere to the specification, and send illegal nulls.
+        Preconditions.enableNullChecks(false);
+    }
+
     private static final RequestProcessor WORKER = new RequestProcessor(LanguageClientImpl.class.getName(), 1, false, false);
     private static final int DELAY = 500;
 
@@ -110,43 +117,50 @@
                 break;
             }
         }
+
+        String mimeType = FileUtil.getMIMEType(file);
         Project prj = FileOwnerQuery.getOwner(file);
+
+        if (mimeType == null) {
+            return null;
+        }
+
+        return getBindingsImpl(prj, file, mimeType, true);
+    }
+
+    public static void ensureServerRunning(Project prj, String mimeType) {
+        getBindingsImpl(prj, prj.getProjectDirectory(), mimeType, false);
+    }
+
+    public static synchronized LSPBindings getBindingsImpl(Project prj, FileObject file, String mimeType, boolean forceBindings) {
         FileObject dir;
+
         if (prj == null) {
             dir = file.getParent();
         } else {
             dir = prj.getProjectDirectory();
         }
+
         URI uri = dir.toURI();
 
-        String mimeType = FileUtil.getMIMEType(file);
-
-        if (mimeType == null) {
-            return null;
-        }
-
         boolean[] created = new boolean[1];
 
         LSPBindings bindings =
                 project2MimeType2Server.computeIfAbsent(uri, p -> new HashMap<>())
                                        .computeIfAbsent(mimeType, mt -> {
                                            MimeTypeInfo mimeTypeInfo = new MimeTypeInfo(mt);
-                                           Reference<Project> prjRef = new WeakReference<>(prj);
                                            ServerRestarter restarter = () -> {
                                                synchronized (LSPBindings.class) {
-                                                   Project p = prjRef.get();
-                                                   if (p != null) {
-                                                       LSPBindings b = project2MimeType2Server.getOrDefault(uri, Collections.emptyMap()).remove(mimeType);
+                                                   LSPBindings b = project2MimeType2Server.getOrDefault(uri, Collections.emptyMap()).remove(mimeType);
 
-                                                       if (b != null) {
-                                                           try {
-                                                               b.server.shutdown().get();
-                                                           } catch (InterruptedException | ExecutionException ex) {
-                                                               LOG.log(Level.FINE, null, ex);
-                                                           }
-                                                           if (b.process != null) {
-                                                               b.process.destroy();
-                                                           }
+                                                   if (b != null) {
+                                                       try {
+                                                           b.server.shutdown().get();
+                                                       } catch (InterruptedException | ExecutionException ex) {
+                                                           LOG.log(Level.FINE, null, ex);
+                                                       }
+                                                       if (b.process != null) {
+                                                           b.process.destroy();
                                                        }
                                                    }
                                                }
@@ -181,9 +195,12 @@
                                                    }
                                                }
                                            }
-                                           return new LSPBindings(null, null, null);
+                                           return forceBindings ? new LSPBindings(null, null, null) : null;
                                        });
 
+        if (bindings == null) {
+            return null;
+        }
         if (bindings.process != null && !bindings.process.isAlive()) {
             //XXX: what now
             return null;
@@ -246,6 +263,8 @@
        wcc.setWorkspaceEdit(new WorkspaceEditCapabilities());
        wcc.getWorkspaceEdit().setDocumentChanges(true);
        wcc.getWorkspaceEdit().setResourceOperations(Arrays.asList(ResourceOperationKind.Create, ResourceOperationKind.Delete, ResourceOperationKind.Rename));
+       SymbolCapabilities sc = new SymbolCapabilities(new SymbolKindCapabilities(Arrays.asList(SymbolKind.values())));
+       wcc.setSymbol(sc);
        initParams.setCapabilities(new ClientCapabilities(wcc, tdcc, null));
        CompletableFuture<InitializeResult> initResult = server.initialize(initParams);
        while (true) {
@@ -261,6 +280,21 @@
        }
     }
 
+    public static Set<LSPBindings> getAllBindings() {
+        Set<LSPBindings> allBindings = Collections.newSetFromMap(new IdentityHashMap<>());
+
+        project2MimeType2Server.values()
+                               .stream()
+                               .flatMap(n -> n.values().stream())
+                               .forEach(allBindings::add);
+        workspace2Extension2Server.values()
+                                  .stream()
+                                  .flatMap(n -> n.values().stream())
+                                  .forEach(allBindings::add);
+
+        return allBindings;
+    }
+
     private final LanguageServer server;
     private final InitializeResult initResult;
     private final Process process;
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/BaseSymbolProvider.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/BaseSymbolProvider.java
new file mode 100644
index 0000000..97ba03d
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/BaseSymbolProvider.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.lsp.client.bindings;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.swing.Icon;
+import org.eclipse.lsp4j.SymbolInformation;
+import org.eclipse.lsp4j.WorkspaceSymbolParams;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectInformation;
+import org.netbeans.api.project.ProjectUtils;
+import org.netbeans.modules.lsp.client.LSPBindings;
+import org.netbeans.modules.lsp.client.Utils;
+import org.netbeans.spi.jumpto.support.NameMatcher;
+import org.netbeans.spi.jumpto.support.NameMatcherFactory;
+import org.netbeans.spi.jumpto.type.SearchType;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author lahvac
+ */
+public class BaseSymbolProvider {
+
+    private static final Pattern WORD_START = Pattern.compile("(^|[^\\p{L}])(\\p{L})");
+    private final AtomicBoolean cancel = new AtomicBoolean();
+    private CompletableFuture<List<? extends SymbolInformation>> currentQuery;
+
+    public String name() {
+        return "lsp-client";
+    }
+
+    public void computeSymbolNames(SearchType searchType, String searchText, BiConsumer<SymbolInformation, String> found) {
+        cancel.set(false);
+
+        List<CompletableFuture<List<? extends SymbolInformation>>> queries = new ArrayList<>();
+
+        try {
+            for (LSPBindings b : LSPBindings.getAllBindings()) {
+                if (cancel.get()) {
+                    return ;
+                }
+                queries.add(b.getWorkspaceService().symbol(new WorkspaceSymbolParams(searchText)));
+            }
+
+            NameMatcher matcher = NameMatcherFactory.createNameMatcher(searchText, searchType);
+
+            while (!queries.isEmpty()) {
+                if (cancel.get()) {
+                    return ;
+                }
+
+
+                try {
+                    currentQuery = queries.remove(queries.size() - 1);
+
+                    List<? extends SymbolInformation> infos = currentQuery.get();
+
+                    currentQuery = null;
+
+                    if (infos != null) {
+                        for (SymbolInformation info : infos) {
+                            if (cancel.get()) {
+                                return ;
+                            }
+                            Matcher wordStartMatcher = WORD_START.matcher(info.getName());
+                            while (wordStartMatcher.find()) {
+                                int nameStart = wordStartMatcher.start(2);
+                                String namePart = info.getName().substring(nameStart);
+                                if (matcher.accept(namePart)) {
+                                    found.accept(info, namePart);
+                                }
+                            }
+                        }
+                    }
+                } catch (InterruptedException ex) {
+                    //ignore?
+                } catch (CancellationException ex) {
+                    return ;
+                } catch (ExecutionException ex) {
+                    LOG.log(Level.FINE, null, ex);
+                }
+            }
+        } finally {
+            if (cancel.get()) {
+                if (currentQuery != null) {
+                    currentQuery.cancel(true);
+                }
+                queries.forEach(cf -> cf.cancel(true));
+            }
+            currentQuery = null;
+        }
+    }
+
+    private static final Logger LOG = Logger.getLogger(BaseSymbolProvider.class.getName());
+
+    public void cancel() {
+        cancel.set(true);
+        if (currentQuery != null) {
+            currentQuery.cancel(true);
+        }
+    }
+
+    public void cleanup() {
+    }
+
+    public static interface BaseSymbolDescriptor {
+
+        public SymbolInformation getInfo();
+
+        public default Icon getIcon() {
+            return Icons.getSymbolIcon(getInfo().getKind());
+        }
+
+        public default String getSymbolName() {
+            return getInfo().getName();
+        }
+
+        public default String getOwnerName() {
+            String container = getInfo().getContainerName();
+
+            if (container == null || "".equals(container)) {
+                String uri = getInfo().getLocation().getUri();
+
+                container = uri.substring(uri.lastIndexOf('/') + 1);
+            }
+
+            return container;
+        }
+
+        public default String getProjectName() {
+            return getProjectInformation().map(pi -> pi.getDisplayName()).orElse(null);
+        }
+
+        public default Icon getProjectIcon() {
+            return getProjectInformation().map(pi -> pi.getIcon()).orElse(null);
+        }
+
+        //XXX: should be private:
+        public default Optional<ProjectInformation> getProjectInformation() {
+            FileObject file = getFileObject();
+
+            if (file != null) {
+                Project owningProject = FileOwnerQuery.getOwner(file);
+
+                if (owningProject != null) {
+                    return Optional.of(ProjectUtils.getInformation(owningProject));
+                }
+            }
+
+            return Optional.empty();
+        }
+
+        public default FileObject getFileObject() {
+            try {
+                URI target = URI.create(getInfo().getLocation().getUri());
+
+                return URLMapper.findFileObject(target.toURL());
+            } catch (MalformedURLException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+            return null;
+        }
+
+        public default int getOffset() {
+            return -1; //XXX
+        }
+
+        public default void open() {
+            Utils.open(getInfo().getLocation().getUri(), getInfo().getLocation().getRange());
+        }
+
+    }
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CustomIndexerImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CustomIndexerImpl.java
new file mode 100644
index 0000000..7c5c989
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CustomIndexerImpl.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.lsp.client.bindings;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.lsp.client.LSPBindings;
+import org.netbeans.modules.parsing.spi.indexing.Context;
+import org.netbeans.modules.parsing.spi.indexing.CustomIndexer;
+import org.netbeans.modules.parsing.spi.indexing.CustomIndexerFactory;
+import org.netbeans.modules.parsing.spi.indexing.Indexable;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.EditableProperties;
+import org.openide.util.Exceptions;
+import org.openide.util.RequestProcessor;
+
+/**
+ *
+ * @author lahvac
+ */
+public class CustomIndexerImpl extends CustomIndexer {
+
+    private static final RequestProcessor WORKER = new RequestProcessor(CustomIndexerImpl.class.getName(), 1, false, false);
+
+    @Override
+    protected void index(Iterable<? extends Indexable> files, Context context) {
+        handleStoredFiles(context, props -> {
+            FileObject root = context.getRoot();
+            for (Indexable i : files) {
+                FileObject file = root.getFileObject(i.getRelativePath());
+                if (file != null) {
+                    props.setProperty(i.getRelativePath(), FileUtil.getMIMEType(file));
+                }
+            }
+
+            Set<String> mimeTypes = new HashSet<>(props.values());
+            System.err.println("mimeTypes=" + mimeTypes);
+            Project prj = FileOwnerQuery.getOwner(root);
+
+            if (prj != null) {
+                WORKER.post(() -> {
+                    for (String mimeType : mimeTypes) {
+                        LSPBindings.ensureServerRunning(prj, mimeType);
+                    }
+                });
+            }
+        });
+    }
+
+    private static final String INDEX_FILE_NAME = "index.properties";
+
+    private static void handleStoredFiles(Context context, Consumer<EditableProperties> handleProperties) {
+        EditableProperties props = new EditableProperties(true);
+        FileObject index = context.getIndexFolder().getFileObject(INDEX_FILE_NAME);
+
+        if (index != null) {
+            try (InputStream in = index.getInputStream()) {
+                props.load(in);
+            } catch (IOException ex) {
+                //ignore...
+            }
+        }
+
+        EditableProperties old = props.cloneProperties();
+
+        handleProperties.accept(props);
+
+        if (!old.equals(props)) {
+            try {
+                if (index == null) {
+                    index = context.getIndexFolder().createData(INDEX_FILE_NAME);
+                }
+                try (OutputStream out = index.getOutputStream()) {
+                    props.store(out);
+                }
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+    }
+
+    @MimeRegistration(mimeType="", service=CustomIndexerFactory.class)
+    public static final class FactoryImpl extends CustomIndexerFactory {
+
+        @Override
+        public CustomIndexer createIndexer() {
+            return new CustomIndexerImpl();
+        }
+
+        @Override
+        public boolean supportsEmbeddedIndexers() {
+            return true;
+        }
+
+        @Override
+        public void filesDeleted(Iterable<? extends Indexable> deleted, Context context) {
+            handleStoredFiles(context, props -> {
+                for (Indexable d : deleted) {
+                    props.remove(d.getRelativePath());
+                }
+            });
+        }
+
+        @Override
+        public void filesDirty(Iterable<? extends Indexable> dirty, Context context) {
+        }
+
+        @Override
+        public String getIndexerName() {
+            return "lsp-indexer";
+        }
+
+        @Override
+        public int getIndexVersion() {
+            return 0;
+        }
+
+    }
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java
index 6570b06..8de64f7 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java
@@ -75,4 +75,8 @@
         }
         return null;
     }
+
+    public static Icon getSymbolIcon(SymbolKind symbolKind) {
+        return ImageUtilities.loadImageIcon(getSymbolIconBase(symbolKind), false);
+    }
 }
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/SymbolProviderImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/SymbolProviderImpl.java
new file mode 100644
index 0000000..250b783
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/SymbolProviderImpl.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.lsp.client.bindings;
+
+import javax.swing.Icon;
+import org.eclipse.lsp4j.SymbolInformation;
+import org.netbeans.spi.jumpto.symbol.SymbolDescriptor;
+import org.netbeans.spi.jumpto.symbol.SymbolProvider;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author lahvac
+ */
+@ServiceProvider(service=SymbolProvider.class)
+public class SymbolProviderImpl extends BaseSymbolProvider implements SymbolProvider {
+
+    @Override
+    @Messages("DN_getDisplayName=Language Server Symbol Provider")
+    public String getDisplayName() {
+        return Bundle.DN_Symbols();
+    }
+
+    @Override
+    public void computeSymbolNames(Context context, Result result) {
+        computeSymbolNames(context.getSearchType(), context.getText(), (info, simpleName) -> result.addResult(new SymbolDescriptorImpl(info, simpleName)));
+    }
+
+    public static class SymbolDescriptorImpl extends SymbolDescriptor implements BaseSymbolDescriptor {
+
+        private final SymbolInformation info;
+        private final String simpleName;
+
+        public SymbolDescriptorImpl(SymbolInformation info, String simpleName) {
+            this.info = info;
+            this.simpleName = simpleName;
+        }
+
+        @Override
+        public SymbolInformation getInfo() {
+            return info;
+        }
+
+        @Override
+        public String getSimpleName() {
+            return simpleName;
+        }
+
+        @Override
+        public Icon getIcon() {
+            return BaseSymbolDescriptor.super.getIcon();
+        }
+
+        @Override
+        public String getSymbolName() {
+            return BaseSymbolDescriptor.super.getSymbolName();
+        }
+
+        @Override
+        public String getOwnerName() {
+            return BaseSymbolDescriptor.super.getOwnerName();
+        }
+
+        @Override
+        public String getProjectName() {
+            return BaseSymbolDescriptor.super.getProjectName();
+        }
+
+        @Override
+        public Icon getProjectIcon() {
+            return BaseSymbolDescriptor.super.getProjectIcon();
+        }
+
+        @Override
+        public FileObject getFileObject() {
+            return BaseSymbolDescriptor.super.getFileObject();
+        }
+
+        @Override
+        public int getOffset() {
+            return BaseSymbolDescriptor.super.getOffset();
+        }
+
+        @Override
+        public void open() {
+            BaseSymbolDescriptor.super.open();
+        }
+
+    }
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TypeProviderImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TypeProviderImpl.java
new file mode 100644
index 0000000..498cdb3
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TypeProviderImpl.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.lsp.client.bindings;
+
+import java.util.EnumSet;
+import java.util.Set;
+import javax.swing.Icon;
+import org.eclipse.lsp4j.SymbolInformation;
+import org.eclipse.lsp4j.SymbolKind;
+import org.netbeans.spi.jumpto.type.TypeDescriptor;
+import org.netbeans.spi.jumpto.type.TypeProvider;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author lahvac
+ */
+@ServiceProvider(service=TypeProvider.class)
+public class TypeProviderImpl extends BaseSymbolProvider implements TypeProvider {
+
+    private static final Set<SymbolKind> TYPE_KINDS = EnumSet.of(
+            SymbolKind.Class, SymbolKind.Enum, SymbolKind.Interface,
+            SymbolKind.Struct
+    );
+
+    @Override
+    @Messages("DN_TypeProviderImpl=Language Server Type Provider")
+    public String getDisplayName() {
+        return Bundle.DN_TypeProviderImpl();
+    }
+
+    @Override
+    public void computeTypeNames(Context context, Result result) {
+        computeSymbolNames(context.getSearchType(),
+                           context.getText(),
+                           (info, simpleName) -> {
+                               if (TYPE_KINDS.contains(info.getKind())) {
+                                   result.addResult(new TypeDescriptorImpl(info, simpleName));
+                               }
+                           });
+    }
+
+    public static class TypeDescriptorImpl extends TypeDescriptor implements BaseSymbolDescriptor {
+
+        private final SymbolInformation info;
+        private final String simpleName;
+
+        public TypeDescriptorImpl(SymbolInformation info, String simpleName) {
+            this.info = info;
+            this.simpleName = simpleName;
+        }
+
+        @Override
+        public SymbolInformation getInfo() {
+            return info;
+        }
+
+        @Override
+        public String getSimpleName() {
+            return simpleName;
+        }
+
+        @Override
+        public Icon getIcon() {
+            return BaseSymbolDescriptor.super.getIcon();
+        }
+
+        @Override
+        public String getTypeName() {
+            return BaseSymbolDescriptor.super.getSymbolName();
+        }
+
+        @Override
+        public String getProjectName() {
+            return BaseSymbolDescriptor.super.getProjectName();
+        }
+
+        @Override
+        public Icon getProjectIcon() {
+            return BaseSymbolDescriptor.super.getProjectIcon();
+        }
+
+        @Override
+        public FileObject getFileObject() {
+            return BaseSymbolDescriptor.super.getFileObject();
+        }
+
+        @Override
+        public int getOffset() {
+            return BaseSymbolDescriptor.super.getOffset();
+        }
+
+        @Override
+        public void open() {
+            BaseSymbolDescriptor.super.open();
+        }
+
+        @Override
+        public String getOuterName() {
+            return null;
+        }
+
+        @Override
+        public String getContextName() {
+            return null;
+        }
+
+    }
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Bundle.properties b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Bundle.properties
new file mode 100644
index 0000000..00f04bf
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Bundle.properties
@@ -0,0 +1,19 @@
+# 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.
+
+RenamePanel.jLabel1.text=Name:
+RenamePanel.name.text=
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/ModificationResult.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/ModificationResult.java
new file mode 100644
index 0000000..f70816d
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/ModificationResult.java
@@ -0,0 +1,430 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.netbeans.modules.lsp.client.bindings.refactoring;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.StyledDocument;
+import org.netbeans.api.queries.FileEncodingQuery;
+import org.openide.cookies.EditorCookie;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.loaders.DataObject;
+import org.openide.text.NbDocument;
+import org.openide.text.PositionRef;
+import org.openide.util.Parameters;
+
+/**Copied from csl.api
+ * Class that collects changes built during a modification task run.
+ *
+ * @author Dusan Balek
+ */
+public final class ModificationResult implements org.netbeans.modules.refactoring.spi.ModificationResult {
+
+    private boolean committed;
+    private final Map<FileObject, List<Difference>> diffs = new HashMap<FileObject, List<Difference>>();
+    
+    private static final Comparator<Difference> COMPARATOR = new Comparator<Difference>() {
+        public int compare(Difference d1, Difference d2) {
+            return d1.getStartPosition().getOffset() - d2.getStartPosition().getOffset();
+        }
+    };
+
+    /** Creates a new instance of ModificationResult */
+    public ModificationResult() {
+    }
+
+    // API of the class --------------------------------------------------------
+
+    public void addDifferences(FileObject fo, List<Difference> differences) {
+        List<Difference> fileDiffs = diffs.get(fo);
+        if (fileDiffs == null) {
+            fileDiffs = new ArrayList<Difference>();
+            diffs.put(fo, fileDiffs);
+        }
+        fileDiffs.addAll(differences);
+
+        // Sort the diffs, if applicable
+        if (fileDiffs.size() > 0) {
+            Collections.sort(fileDiffs, COMPARATOR);
+        }
+    }
+
+    public Set<? extends FileObject> getModifiedFileObjects() {
+        return diffs.keySet();
+    }
+    
+    public List<? extends Difference> getDifferences(FileObject fo) {
+        return diffs.get(fo);
+    }
+    
+    public Set<File> getNewFiles() {
+        Set<File> newFiles = new HashSet<File>();
+        for (List<Difference> ds:diffs.values()) {
+            for (Difference d: ds) {
+                if (d.getKind() == Difference.Kind.CREATE) {
+                    newFiles.add(((CreateFileDifference) d).getFile());
+                }
+            }
+        }
+        return newFiles;
+    }
+    
+    /**
+     * Once all of the changes have been collected, this method can be used
+     * to commit the changes to the source files
+     */
+    public void commit() throws IOException {
+        if (this.committed) {
+            throw new IllegalStateException ("Calling commit on already committed Modificationesult."); //NOI18N
+        }
+        try {
+            for (Map.Entry<FileObject, List<Difference>> me : diffs.entrySet()) {
+                commit(me.getKey(), me.getValue(), null);
+            }
+        } finally {
+            this.committed = true;
+        }
+    }
+            
+    private void commit (final FileObject fo, final List<Difference> differences, final Writer out) throws IOException {
+        DataObject dObj = DataObject.find(fo);
+        EditorCookie ec = dObj != null ? dObj.getCookie(org.openide.cookies.EditorCookie.class) : null;
+        // if editor cookie was found and user does not provided his own
+        // writer where he wants to see changes, commit the changes to 
+        // found document.
+        if (ec != null && out == null) {
+            final StyledDocument doc = ec.getDocument();
+            if (doc != null) {
+                final IOException[] exceptions = new IOException [1];
+                NbDocument.runAtomic(doc, new Runnable () {
+                    public void run () {
+                        try {
+                            commit2 (doc, differences, out);
+                        } catch (IOException ex) {
+                            exceptions [0] = ex;
+                        }
+                    }
+                });
+                if (exceptions [0] != null)
+                    throw exceptions [0];
+                return;
+            }
+        }
+        InputStream ins = null;
+        ByteArrayOutputStream baos = null;           
+        Reader in = null;
+        Writer out2 = out;
+        try {
+            Charset encoding = FileEncodingQuery.getEncoding(fo);
+            ins = fo.getInputStream();
+            baos = new ByteArrayOutputStream();
+            FileUtil.copy(ins, baos);
+
+            ins.close();
+            ins = null;
+            byte[] arr = baos.toByteArray();
+            int arrLength = convertToLF(arr);
+            baos.close();
+            baos = null;
+            in = new InputStreamReader(new ByteArrayInputStream(arr, 0, arrLength), encoding);
+            // initialize standard commit output stream, if user
+            // does not provide his own writer
+            boolean ownOutput = out != null;
+            if (out2 == null) {
+                out2 = new OutputStreamWriter(fo.getOutputStream(), encoding);
+            }
+            int offset = 0;                
+            for (Difference diff : differences) {
+                if (diff.isExcluded())
+                    continue;
+                if (Difference.Kind.CREATE == diff.getKind()) {
+                    if (!ownOutput) {
+                        createUnit((CreateFileDifference)diff, null);
+                    }
+                    continue;
+                }
+                int pos = diff.getStartPosition().getOffset();
+                int toread = pos - offset;
+                char[] buff = new char[toread];
+                int n;
+                int rc = 0;
+                while ((n = in.read(buff,0, toread - rc)) > 0 && rc < toread) {
+                    out2.write(buff, 0, n);
+                    rc+=n;
+                    offset += n;
+                }
+                switch (diff.getKind()) {
+                    case INSERT:
+                        out2.write(diff.getNewText());
+                        break;
+                    case REMOVE:
+                        int len = diff.getEndPosition().getOffset() - diff.getStartPosition().getOffset();
+                        in.skip(len);
+                        offset += len;
+                        break;
+                    case CHANGE:
+                        len = diff.getEndPosition().getOffset() - diff.getStartPosition().getOffset();
+                        in.skip(len);
+                        offset += len;
+                        out2.write(diff.getNewText());
+                        break;
+                }
+            }                    
+            char[] buff = new char[1024];
+            int n;
+            while ((n = in.read(buff)) > 0)
+                out2.write(buff, 0, n);
+        } finally {
+            if (ins != null)
+                ins.close();
+            if (baos != null)
+                baos.close();
+            if (in != null)
+                in.close();
+            if (out2 != null)
+                out2.close();
+        }            
+    }
+
+    private void commit2 (final StyledDocument doc, final List<Difference> differences, Writer out) throws IOException {
+        for (Difference diff : differences) {
+            if (diff.isExcluded())
+                continue;
+            switch (diff.getKind()) {
+                case INSERT:
+                case REMOVE:
+                case CHANGE:
+                    processDocument(doc, diff);
+                    break;
+                case CREATE:
+                    createUnit((CreateFileDifference)diff, out);
+                    break;
+            }
+        }
+    }
+    
+    private void processDocument(final StyledDocument doc, final Difference diff) throws IOException {
+        final BadLocationException[] blex = new BadLocationException[1];
+        Runnable task = new Runnable() {
+
+            public void run() {
+                try {
+                    processDocumentLocked(doc, diff);
+                } catch (BadLocationException ex) {
+                    blex[0] = ex;
+                }
+            }
+        };
+        if (diff.isCommitToGuards()) {
+            NbDocument.runAtomic(doc, task);
+        } else {
+            try {
+                NbDocument.runAtomicAsUser(doc, task);
+            } catch (BadLocationException ex) {
+                blex[0] = ex;
+            }
+        }
+        if (blex[0] != null) {
+            IOException ioe = new IOException();
+            ioe.initCause(blex[0]);
+            throw ioe;
+        }
+    }
+    
+    private void processDocumentLocked(Document doc, Difference diff) throws BadLocationException {
+        switch (diff.getKind()) {
+            case INSERT:
+                doc.insertString(diff.getStartPosition().getOffset(), diff.getNewText(), null);
+                break;
+            case REMOVE:
+                doc.remove(diff.getStartPosition().getOffset(), diff.getEndPosition().getOffset() - diff.getStartPosition().getOffset());
+                break;
+            case CHANGE:
+                doc.remove(diff.getStartPosition().getOffset(), diff.getEndPosition().getOffset() - diff.getStartPosition().getOffset());
+                doc.insertString(diff.getStartPosition().getOffset(), diff.getNewText(), null);
+                break;
+        }
+    }
+
+    private void createUnit(CreateFileDifference diff, Writer out) {
+        Writer w = out;
+        try {
+            if (w == null) {
+                w = new FileWriter(diff.getFile());
+            }
+            w.append(diff.getNewText());
+        } catch (IOException e) {
+            Logger.getLogger(ModificationResult.class.getName()).log(Level.SEVERE, e.getMessage(), e);
+        } finally {
+            if (w != null) {
+                try {
+                    w.close();
+                } catch (IOException e) {
+                    Logger.getLogger(ModificationResult.class.getName()).log(Level.SEVERE, e.getMessage(), e);
+                }
+            }
+        }
+    }
+    
+    private int convertToLF(byte[] buff) {
+        int j = 0;
+        for (int i = 0; i < buff.length; i++) {
+            if (buff[i] != '\r') {
+                buff[j++] = buff[i];
+            }
+        }
+        return j;
+    }
+    
+    /**
+     * Returned string represents preview of resulting source. No difference
+     * really is applied. Respects {@code isExcluded()} flag of difference.
+     * 
+     * @param   there can be more resulting source, user has to specify
+     *          which wants to preview.
+     * @return  if changes are applied source looks like return string
+     * @throws  IllegalArgumentException if the provided {@link FileObject} is not
+     *                                   modified in this {@link ModificationResult}
+     */
+    public String getResultingSource(FileObject fileObject) throws IOException, IllegalArgumentException {
+        Parameters.notNull("fileObject", fileObject);
+
+        if (!getModifiedFileObjects().contains(fileObject)) {
+            throw new IllegalArgumentException("File: " + FileUtil.getFileDisplayName(fileObject) + " is not modified in this ModificationResult");
+        }
+        
+        StringWriter writer = new StringWriter();
+        commit(fileObject, diffs.get(fileObject), writer);
+        
+        return writer.toString();
+    }
+
+    public static class Difference {
+        Kind kind;
+        PositionRef startPos;
+        PositionRef endPos;
+        String oldText;
+        String newText;
+        String description;
+        private boolean excluded;
+        private boolean ignoreGuards = false;
+
+        public Difference(Kind kind, PositionRef startPos, PositionRef endPos, String oldText, String newText, String description) {
+            this.kind = kind;
+            this.startPos = startPos;
+            this.endPos = endPos;
+            this.oldText = oldText;
+            this.newText = newText;
+            this.description = description;
+            this.excluded = false;
+        }
+        
+        public Difference(Kind kind, PositionRef startPos, PositionRef endPos, String oldText, String newText) {
+            this(kind, startPos, endPos, oldText, newText, null);
+        }
+        
+        public Kind getKind() {
+            return kind;
+        }
+        
+        public PositionRef getStartPosition() {
+            return startPos;
+        }
+        
+        public PositionRef getEndPosition() {
+            return endPos;
+        }
+        
+        public String getOldText() {
+            return oldText;
+        }
+        
+        public String getNewText() {
+            return newText;
+        }
+        
+        public boolean isExcluded() {
+            return excluded;
+        }
+        
+        public void exclude(boolean b) {
+            excluded = b;
+        }
+
+        /**
+         * Gets flag if it is possible to write to guarded sections.
+         * @return {@code true} in case the difference may be written even into
+         *          guarded sections.
+         * @see #guards(boolean)
+         * @since 0.33
+         */
+        public boolean isCommitToGuards() {
+            return ignoreGuards;
+        }
+        
+        /**
+         * Sets flag if it is possible to write to guarded sections.
+         * @param b flag if it is possible to write to guarded sections
+         * @since 0.33
+         */
+        public void setCommitToGuards(boolean b) {
+            ignoreGuards = b;
+        }
+
+        @Override
+        public String toString() {
+            return kind + "<" + startPos.getOffset() + ", " + endPos.getOffset() + ">: " + oldText + " -> " + newText;
+        }
+        public String getDescription() {
+            return description;
+        }
+        
+        public static enum Kind {
+            INSERT,
+            REMOVE,
+            CHANGE,
+            CREATE;
+        }
+    } // End of Difference class
+    
+    public static class CreateFileDifference extends Difference {
+        private final File file;
+        
+        public CreateFileDifference(File file, String text) {
+            super(Kind.CREATE, null, null, null, text, "Create file " + file.getPath());
+            this.file = file;
+        }
+
+        public final File getFile() {
+            return file;
+        }
+
+        @Override
+        public String toString() {
+            return kind + "Create File: " + file.getName() + "; contents = \"\n" + newText + "\"";
+        }
+    } // End of CreateFileDifference class
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java
index c267eb8..7ebc214 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java
@@ -19,16 +19,49 @@
 package org.netbeans.modules.lsp.client.bindings.refactoring;
 
 import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.swing.text.BadLocationException;
 import javax.swing.text.Position;
 import javax.swing.text.StyledDocument;
+import org.eclipse.lsp4j.CreateFile;
+import org.eclipse.lsp4j.DeleteFile;
 import org.eclipse.lsp4j.Location;
 import org.eclipse.lsp4j.ReferenceParams;
+import org.eclipse.lsp4j.RenameFile;
+import org.eclipse.lsp4j.RenameParams;
+import org.eclipse.lsp4j.ResourceOperation;
+import org.eclipse.lsp4j.ResourceOperationKind;
+import org.eclipse.lsp4j.TextDocumentEdit;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.netbeans.api.queries.FileEncodingQuery;
 import org.netbeans.modules.lsp.client.LSPBindings;
 import org.netbeans.modules.lsp.client.Utils;
+import org.netbeans.modules.lsp.client.bindings.refactoring.ModificationResult.Difference;
+import org.netbeans.modules.lsp.client.bindings.refactoring.tree.DiffElement;
 import org.netbeans.modules.refactoring.api.AbstractRefactoring;
 import org.netbeans.modules.refactoring.api.Problem;
+import org.netbeans.modules.refactoring.api.RenameRefactoring;
 import org.netbeans.modules.refactoring.api.WhereUsedQuery;
+import org.netbeans.modules.refactoring.spi.BackupFacility;
+import org.netbeans.modules.refactoring.spi.RefactoringCommit;
 import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
 import org.netbeans.modules.refactoring.spi.RefactoringPlugin;
 import org.netbeans.modules.refactoring.spi.RefactoringPluginFactory;
@@ -36,17 +69,25 @@
 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.loaders.DataObject;
+import org.openide.loaders.DataObjectNotFoundException;
 import org.openide.text.CloneableEditorSupport;
 import org.openide.text.Line;
 import org.openide.text.PositionBounds;
+import org.openide.text.PositionRef;
 import org.openide.util.Exceptions;
 import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.util.Pair;
 import org.openide.util.lookup.ServiceProvider;
 
 /**
  *
  * @author lahvac
  */
+@NbBundle.Messages("TXT_Canceled=Canceled")
 public class Refactoring {
 
     private static final class WhereUsedRefactoringPlugin implements RefactoringPlugin {
@@ -54,6 +95,8 @@
         private final WhereUsedQuery query;
         private final LSPBindings bindings;
         private final ReferenceParams params;
+        private final AtomicBoolean cancel = new AtomicBoolean();
+        private volatile CompletableFuture<List<? extends Location>> runningRequest;
 
         public WhereUsedRefactoringPlugin(WhereUsedQuery query, LSPBindings bindings, ReferenceParams params) {
             this.query = query;
@@ -78,28 +121,35 @@
 
         @Override
         public void cancelRequest() {
-            throw new UnsupportedOperationException("Not supported yet.");
+            cancel.set(true);
+            CompletableFuture localRunningRequest = runningRequest;
+            if(localRunningRequest != null) {
+                localRunningRequest.cancel(true);
+            }
         }
 
         @Override
         public Problem prepare(RefactoringElementsBag refactoringElements) {
             try {
-                for (Location l : bindings.getTextDocumentService().references(params).get()) {
+                runningRequest = bindings.getTextDocumentService().references(params);
+                for (Location l : runningRequest.get()) {
+                    if(cancel.get()) {
+                        break;
+                    }
                     FileObject file = Utils.fromURI(l.getUri());
-                    PositionBounds boundsTemp = null;
                     if (file != null) {
+                        PositionBounds bounds;
                         try {
                             CloneableEditorSupport es = file.getLookup().lookup(CloneableEditorSupport.class);
                             EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
                             StyledDocument doc = ec.openDocument();
 
-                            boundsTemp = new PositionBounds(es.createPositionRef(Utils.getOffset(doc, l.getRange().getStart()), Position.Bias.Forward),
+                            bounds = new PositionBounds(es.createPositionRef(Utils.getOffset(doc, l.getRange().getStart()), Position.Bias.Forward),
                                                             es.createPositionRef(Utils.getOffset(doc, l.getRange().getEnd()), Position.Bias.Forward));
                         } catch (IOException ex) {
                             Exceptions.printStackTrace(ex);
-                            boundsTemp = null;
+                            bounds = null;
                         }
-                        PositionBounds bounds = boundsTemp;
                         LineCookie lc = file.getLookup().lookup(LineCookie.class);
                         Line startLine = lc.getLineSet().getCurrent(l.getRange().getStart().getLine());
                         String lineText = startLine.getText();
@@ -110,14 +160,210 @@
                         refactoringElements.add(query, new LSPRefactoringElementImpl(annotatedLine, file, bounds));
                     }
                 }
+                runningRequest = null;
                 return null;
+            } catch (CancellationException ex) {
+                return new Problem(false, Bundle.TXT_Canceled());
             } catch (InterruptedException | ExecutionException ex) {
+                Exceptions.printStackTrace(ex);
                 return new Problem(true, ex.getLocalizedMessage());
             }
         }
 
     }
 
+    private static final class RenameRefactoringPlugin implements RefactoringPlugin {
+
+        private final RenameRefactoring refactoring;
+        private final LSPBindings bindings;
+        private final RenameParams params;
+        private final AtomicBoolean cancel = new AtomicBoolean();
+        private volatile CompletableFuture<WorkspaceEdit> runningRequest;
+
+        public RenameRefactoringPlugin(RenameRefactoring refactoring, LSPBindings bindings, RenameParams params) {
+            this.refactoring = refactoring;
+            this.bindings = bindings;
+            this.params = params;
+        }
+
+        @Override
+        public Problem preCheck() {
+            return null;
+        }
+
+        @Override
+        public Problem checkParameters() {
+            return null;
+        }
+
+        @Override
+        public Problem fastCheckParameters() {
+            return null;
+        }
+
+        @Override
+        public void cancelRequest() {
+            cancel.set(true);
+            CompletableFuture localRunningRequest = runningRequest;
+            if(localRunningRequest != null) {
+                localRunningRequest.cancel(true);
+            }
+        }
+
+        @Override
+        public Problem prepare(RefactoringElementsBag refactoringElements) {
+            if (cancel.get()) {
+                return new Problem(false, Bundle.TXT_Canceled());
+            }
+            Problem p = null;
+            try {
+                runningRequest = bindings.getTextDocumentService().rename(params);
+                WorkspaceEdit edit = runningRequest.get();
+                List<Either<TextDocumentEdit, ResourceOperation>> documentChanges = edit.getDocumentChanges();
+                ModificationResult result = new ModificationResult();
+                Map<FileObject, List<Difference>> file2Diffs = new HashMap<>();
+                Map<String, String> newURI2Old = new HashMap<>();
+                Map<String, String> newFileURI2Content = new HashMap<>();
+
+                if (documentChanges != null) {
+                    for (Either<TextDocumentEdit, ResourceOperation> part : documentChanges) {
+                        if(cancel.get()) {
+                            break;
+                        }
+                        if (part.isLeft()) {
+                            String uri = part.getLeft().getTextDocument().getUri();
+                            uri = newURI2Old.getOrDefault(uri, uri);
+                            FileObject file = Utils.fromURI(uri);
+
+                            if (file != null) {
+                                for (TextEdit te : part.getLeft().getEdits()) {
+                                    Difference diff = textEdit2Difference(file, te);
+                                    file2Diffs.computeIfAbsent(file, f -> new ArrayList<>())
+                                              .add(diff);
+                                }
+                            } else if (newFileURI2Content.containsKey(uri)) {
+                                FileObject temp = FileUtil.createMemoryFileSystem().getRoot().createData("temp.txt");
+                                try (OutputStream out = temp.getOutputStream()) {
+                                    out.write(newFileURI2Content.get(uri).getBytes()); //TODO: encoding - native, OK?
+                                }
+                                List<Difference> diffs = new ArrayList<>();
+                                for (TextEdit te : part.getLeft().getEdits()) {
+                                    diffs.add(textEdit2Difference(temp, te));
+                                }
+                                ModificationResult tempResult = new ModificationResult();
+                                tempResult.addDifferences(temp, diffs);
+                                newFileURI2Content.put(uri, tempResult.getResultingSource(temp));
+                            } else {
+                                //XXX: problem...
+                            }
+                        } else {
+                            switch (part.getRight().getKind()) {
+                                case ResourceOperationKind.Rename: {
+                                    RenameFile rename = (RenameFile) part.getRight();
+                                    FileObject file = Utils.fromURI(rename.getOldUri());
+                                    refactoringElements.addFileChange(refactoring, new LSPRenameFile(file, rename.getNewUri()));
+                                    newURI2Old.put(rename.getNewUri(), rename.getOldUri());
+                                    break;
+                                }
+                                case ResourceOperationKind.Delete: {
+                                    DeleteFile delete = (DeleteFile) part.getRight();
+                                    FileObject file = Utils.fromURI(delete.getUri());
+                                    refactoringElements.addFileChange(refactoring, new LSPDeleteFile(file));
+                                    break;
+                                }
+                                case ResourceOperationKind.Create: {
+                                    CreateFile create = (CreateFile) part.getRight();
+                                    String uri = create.getUri();
+                                    newFileURI2Content.put(uri, "");
+                                    break;
+                                }
+                                default:
+                                    p = chain(new Problem(true, "Unknown file operation: " + part.getRight().getKind()), p);
+                                    break;
+                            }
+                        }
+                    }
+                } else {
+                    for (Entry<String, List<TextEdit>> fileAndChanges : edit.getChanges().entrySet()) {
+                        if(cancel.get()) {
+                            break;
+                        }
+                        //TODO: errors:
+                        FileObject file = Utils.fromURI(fileAndChanges.getKey());
+
+                        for (TextEdit te : fileAndChanges.getValue()) {
+                            Difference diff = textEdit2Difference(file, te);
+                            file2Diffs.computeIfAbsent(file, f -> new ArrayList<>())
+                                      .add(diff);
+                        }
+                    }
+                }
+
+                if (cancel.get()) {
+                    p = chain(new Problem(false, Bundle.TXT_Canceled()), p);
+                } else {
+
+                    file2Diffs.entrySet()
+                        .forEach(e -> {
+                            e.getValue()
+                                .forEach(diff -> refactoringElements.add(refactoring, DiffElement.create(diff, e.getKey(), result)));
+                            result.addDifferences(e.getKey(), e.getValue());
+                        });
+
+                    newFileURI2Content.entrySet()
+                        .forEach(e -> {
+                            refactoringElements.add(refactoring, new LSPCreateFile(e.getKey(), e.getValue()));
+                        });
+                    refactoringElements.registerTransaction(new RefactoringCommit(Collections.singletonList(result)));
+
+                    if (cancel.get()) {
+                        p = chain(new Problem(false, Bundle.TXT_Canceled()), p);
+                    }
+                }
+
+                return p;
+            } catch (CancellationException ex) {
+                return chain(new Problem(false, Bundle.TXT_Canceled()), p);
+            } catch (InterruptedException | ExecutionException | IOException ex) {
+                return chain(new Problem(true, ex.getLocalizedMessage()), p);
+            } finally {
+                runningRequest = null;
+            }
+        }
+
+        private Problem chain(Problem current, Problem existing) {
+            if (existing != null) {
+                current.setNext(existing);
+            }
+            return current;
+        }
+
+        private Difference textEdit2Difference(FileObject file, TextEdit edit) {
+            if (file != null) {
+                try {
+                    EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
+                    StyledDocument doc = ec.openDocument();
+                    CloneableEditorSupport es = file.getLookup().lookup(CloneableEditorSupport.class);
+
+                    PositionRef start = es.createPositionRef(Utils.getOffset(doc, edit.getRange().getStart()), Position.Bias.Forward);
+                    PositionRef end   = es.createPositionRef(Utils.getOffset(doc, edit.getRange().getEnd()), Position.Bias.Forward);
+                    PositionBounds bounds = new PositionBounds(start, end);
+                    return new Difference(Difference.Kind.CHANGE, start, end, bounds.getText(), edit.getNewText());
+                } catch (IOException | BadLocationException ex) {
+                    Exceptions.printStackTrace(ex);
+                }
+            }
+
+            return null;
+        }
+    }
+
+    private static String uri2SimpleName(String uri) {
+        int dot = uri.lastIndexOf('/');
+
+        return uri.substring(dot + 1);
+    }
+
     public static class LSPRefactoringElementImpl extends SimpleRefactoringElementImplementation {
 
         private final String annotatedLine;
@@ -142,6 +388,8 @@
 
         @Override
         public void performChange() {
+            // Currently the LSPRefactoringElementImpl is only used for the
+            // WhereUsedRefactoring, which is not doing changes
             throw new UnsupportedOperationException();
         }
 
@@ -161,6 +409,258 @@
         }
     }
 
+    public static class LSPRenameFile extends SimpleRefactoringElementImplementation {
+
+        private final FileObject fo;
+        private final String newUri;
+        public LSPRenameFile(FileObject fo, String newUri) {
+            this.fo = fo;
+            this.oldUri = fo.toURI().toString();
+            this.newUri = newUri;
+        }
+
+        @Override
+        @NbBundle.Messages({
+            "# {0} - current name of the file",
+            "TXT_RenameFile=Rename file {0}",
+            "# {0} - current name of the folders",
+            "TXT_RenameFolder=Rename folder {0}"
+        })
+        public String getText() {
+            return fo.isFolder()? Bundle.TXT_RenameFolder(fo.getNameExt()) :
+                                  Bundle.TXT_RenameFile(fo.getNameExt());
+        }
+
+        @Override
+        public String getDisplayText() {
+            return getText();
+        }
+
+        private String oldUri;
+        
+        @Override
+        public void performChange() {
+            oldUri = fo.getName();
+            doRename(newUri);
+        }
+        
+        @Override
+        public void undoChange(){
+//            if (!fo.isValid()) {
+//                throw new CannotUndoRefactoring(Collections.singleton(fo.getPath()));
+//            }
+            doRename(oldUri);
+        }
+
+        private void doRename(String uri) {
+            try {
+                //XXX: different path, not only the name:
+                String newName = uri.substring(uri.lastIndexOf('/') + 1);
+                DataObject.find(fo).rename(newName);
+            } catch (DataObjectNotFoundException ex) {
+                throw new IllegalStateException(ex);
+            } catch (IOException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+
+        @Override
+        public Lookup getLookup() {
+            return Lookup.EMPTY;
+        }
+
+        @Override
+        public FileObject getParentFile() {
+            return fo;
+        }
+
+        @Override
+        public PositionBounds getPosition() {
+            return null;
+        }
+
+        @Override
+        public String toString() {
+            return uri2SimpleName(fo.toURI().toString()) + "=>" + uri2SimpleName(newUri);
+        }
+
+    }
+
+    public static class LSPDeleteFile extends SimpleRefactoringElementImplementation {
+        private final URL res;
+        private final String filename;
+
+        private BackupFacility.Handle id;
+
+        /**
+         *
+         * @param fo
+         * @param session
+         */
+        public LSPDeleteFile(FileObject fo) {
+            this.res = fo.toURL();
+            this.filename = fo.getNameExt();
+        }
+
+        @Override
+        @NbBundle.Messages({
+            "# {0} - name of the file to be deleted",
+            "TXT_DeleteFile=Delete file {0}"
+        })
+        public String getText() {
+            return Bundle.TXT_DeleteFile(filename);
+        }
+
+        @Override
+        public String getDisplayText() {
+            return getText();
+        }
+
+        @Override
+        public void performChange() {
+            try {
+                FileObject fo = URLMapper.findFileObject(res);
+                if (fo == null) {
+                    throw new IOException(res.toString());
+                }
+                id = BackupFacility.getDefault().backup(fo);
+                DataObject.find(fo).delete();
+            } catch (DataObjectNotFoundException ex) {
+                Exceptions.printStackTrace(ex);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+
+        @Override
+        public void undoChange() {
+            try {
+                FileObject f = URLMapper.findFileObject(res);
+                if (f != null) {
+//                        throw new CannotUndoRefactoring(Collections.singleton(f.getPath()));
+                }
+                id.restore();
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+
+        @Override
+        public Lookup getLookup() {
+            return Lookup.EMPTY;
+        }
+
+        @Override
+        public FileObject getParentFile() {
+            return URLMapper.findFileObject(res);
+        }
+
+        @Override
+        public PositionBounds getPosition() {
+            return null;
+        }
+
+        @Override
+        public String toString() {
+            return uri2SimpleName(res.toString()) + "=>";
+        }
+
+    }
+
+    public static class LSPCreateFile extends SimpleRefactoringElementImplementation {
+        private final String uri;
+        private final String content;
+
+        /**
+         * 
+         * @param fo
+         * @param session
+         */
+        public LSPCreateFile(String uri, String content) {
+            this.uri = uri;
+            this.content = content;
+        }
+
+        @Override
+        @NbBundle.Messages({
+            "# {0} - name of the newly created file",
+            "TXT_CreateFile=Create file {0}"
+        })
+        public String getText() {
+            return Bundle.TXT_CreateFile(uri2SimpleName(uri));
+        }
+
+        @Override
+        public String getDisplayText() {
+            return getText();
+        }
+
+        private FileObject target;
+
+        @Override
+        public void performChange() {
+            try {
+                Pair<FileObject, String> p = fileAndRemainingPath(uri);
+                target = FileUtil.createData(p.first(), p.second());
+                try (Writer w = new OutputStreamWriter(target.getOutputStream(), FileEncodingQuery.getEncoding(target))) {
+                    w.write(content);
+                }
+            } catch (IOException | URISyntaxException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+
+        @Override
+        public void undoChange() {
+            try {
+                target.delete();
+                target = null;
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+
+        @Override
+        public Lookup getLookup() {
+            return Lookup.EMPTY;
+        }
+
+        @Override
+        public FileObject getParentFile() {
+            try {
+                return fileAndRemainingPath(uri).first();
+            } catch (URISyntaxException | MalformedURLException ex) {
+                Exceptions.printStackTrace(ex);
+                return null;
+            }
+        }
+
+        @SuppressWarnings({"NestedAssignment", "AssignmentToMethodParameter"})
+        private static Pair<FileObject, String> fileAndRemainingPath(String uri) throws URISyntaxException, MalformedURLException {
+            StringBuilder path = new StringBuilder();
+            FileObject existing;
+            while ((existing = URLMapper.findFileObject(new URI(uri).toURL())) == null) {
+                int slash = uri.lastIndexOf('/');
+                if (path.length() > 0) {
+                    path.insert(0, '/');
+                }
+                path.insert(0, uri.substring(slash + 1));
+                uri = uri.substring(0, slash);
+            }
+            return Pair.of(existing, path.toString());
+        }
+
+        @Override
+        public PositionBounds getPosition() {
+            return null;
+        }
+
+        @Override
+        public String toString() {
+            return "=>" + uri2SimpleName(uri) + "(" + content + ")";
+        }
+
+    }
 
     @ServiceProvider(service=RefactoringPluginFactory.class)
     public static class FactoryImpl implements RefactoringPluginFactory {
@@ -174,6 +674,13 @@
                 if (bindings != null && params != null) {
                     return new WhereUsedRefactoringPlugin(q, bindings, params);
                 }
+            } else if (refactoring instanceof RenameRefactoring) {
+                RenameRefactoring r = (RenameRefactoring) refactoring;
+                LSPBindings bindings = r.getRefactoringSource().lookup(LSPBindings.class);
+                RenameParams params = r.getRefactoringSource().lookup(RenameParams.class);
+                if (bindings != null && params != null) {
+                    return new RenameRefactoringPlugin(r, bindings, params);
+                }
             }
             return null;
         }
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringActionsProvider.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringActionsProvider.java
index c1d72ed..5eff44b 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringActionsProvider.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringActionsProvider.java
@@ -22,12 +22,15 @@
 import javax.swing.Action;
 import javax.swing.JEditorPane;
 import javax.swing.SwingUtilities;
+import javax.swing.text.AbstractDocument;
 import javax.swing.text.BadLocationException;
 import javax.swing.text.Document;
 import org.eclipse.lsp4j.Position;
 import org.eclipse.lsp4j.ReferenceContext;
 import org.eclipse.lsp4j.ReferenceParams;
+import org.eclipse.lsp4j.RenameOptions;
 import org.eclipse.lsp4j.TextDocumentIdentifier;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
 import org.netbeans.api.lexer.TokenHierarchy;
 import org.netbeans.api.lexer.TokenSequence;
 import org.netbeans.modules.editor.NbEditorUtilities;
@@ -64,6 +67,7 @@
                 try {
                     JEditorPane c = ec.getOpenedPanes()[0];
                     Document doc = c.getDocument();
+                    AbstractDocument abstractDoc = (doc instanceof AbstractDocument) ? ((AbstractDocument) doc) : null;
                     FileObject file = NbEditorUtilities.getFileObject(doc);
                     LSPBindings bindings = LSPBindings.getBindings(file);
                     int caretPos = c.getCaretPosition();
@@ -72,16 +76,70 @@
                     params.setTextDocument(new TextDocumentIdentifier(Utils.toURI(file)));
                     params.setPosition(pos);
                     params.setContext(new ReferenceContext(false)); //(could be an option?)
-                    TokenSequence<?> ts = TokenHierarchy.get(doc).tokenSequence();
+
                     String name = Bundle.NM_Unknown();
-                    if (ts != null) {
-                        ts.move(caretPos);
-                        if (ts.moveNext()) {
-                            name = ts.token().text().toString();
+
+                    if (abstractDoc != null) {
+                        abstractDoc.readLock();
+                    }
+                    try {
+                        TokenSequence<?> ts = TokenHierarchy.get(doc).tokenSequence();
+                        if (ts != null) {
+                            ts.move(caretPos);
+                            if (ts.moveNext()) {
+                                name = ts.token().text().toString();
+                            }
+                        }
+                    } finally {
+                        if (abstractDoc != null) {
+                            abstractDoc.readUnlock();
                         }
                     }
 
-                    UI.openRefactoringUI(new RefactoringUIImpl(bindings, params, name),
+                    UI.openRefactoringUI(new WhereUsedRefactoringUIImpl(bindings, params, name),
+                                         TopComponent.getRegistry().getActivated());
+                } catch (BadLocationException ex) {
+                    Exceptions.printStackTrace(ex);
+                }
+            }
+        };
+        SwingUtilities.invokeLater(start);
+    }
+
+    @Override
+    public void doRename(Lookup lookup) {
+        Runnable start = () -> {
+            EditorCookie ec = lookup.lookup(EditorCookie.class);
+
+            if (isFromEditor(ec)) {
+                try {
+                    JEditorPane c = ec.getOpenedPanes()[0];
+                    Document doc = c.getDocument();
+                    AbstractDocument abstractDoc = (doc instanceof AbstractDocument) ? ((AbstractDocument) doc) : null;
+                    FileObject file = NbEditorUtilities.getFileObject(doc);
+                    LSPBindings bindings = LSPBindings.getBindings(file);
+                    int caretPos = c.getCaretPosition();
+                    Position pos = Utils.createPosition(doc, caretPos);
+                    String name;
+                    if(abstractDoc != null) {
+                        abstractDoc.readLock();
+                    }
+                    try {
+                        TokenSequence<?> ts = TokenHierarchy.get(doc).tokenSequence();
+                        name = "";
+                        if (ts != null) {
+                            ts.move(caretPos);
+                            if (ts.moveNext()) {
+                                name = ts.token().text().toString();
+                            }
+                        }
+                    } finally {
+                        if (abstractDoc != null) {
+                            abstractDoc.readUnlock();
+                        }
+                    }
+
+                    UI.openRefactoringUI(new RenameRefactoringUIImpl(bindings, file, pos, name),
                                          TopComponent.getRegistry().getActivated());
                 } catch (BadLocationException ex) {
                     Exceptions.printStackTrace(ex);
@@ -104,7 +162,17 @@
             return false;
         }
         Boolean hasReferences = bindings.getInitResult().getCapabilities().getReferencesProvider();
-        return hasReferences != null && hasReferences;
+        return Utils.isTrue(hasReferences);
+    }
+
+    @Override
+    public boolean canRename(Lookup lookup) {
+        LSPBindings bindings = getBindings(lookup);
+        if (bindings == null) {
+            return false;
+        }
+        Either<Boolean, RenameOptions> hasRename = bindings.getInitResult().getCapabilities().getRenameProvider();
+        return hasRename != null && ((hasRename.isLeft() && Utils.isTrue(hasRename.getLeft())) || hasRename.isRight());
     }
 
     private LSPBindings getBindings(Lookup lookup) {
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenamePanel.form b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenamePanel.form
new file mode 100644
index 0000000..93da9c9
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenamePanel.form
@@ -0,0 +1,72 @@
+<?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.
+
+-->
+
+<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+  <AuxValues>
+    <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+  </AuxValues>
+
+  <Layout>
+    <DimensionLayout dim="0">
+      <Group type="103" groupAlignment="0" attributes="0">
+          <Group type="102" alignment="0" attributes="0">
+              <Component id="jLabel1" min="-2" max="-2" attributes="0"/>
+              <EmptySpace max="-2" attributes="0"/>
+              <Component id="name" pref="355" max="32767" attributes="0"/>
+          </Group>
+      </Group>
+    </DimensionLayout>
+    <DimensionLayout dim="1">
+      <Group type="103" groupAlignment="0" attributes="0">
+          <Group type="103" groupAlignment="3" attributes="0">
+              <Component id="jLabel1" alignment="3" min="-2" max="-2" attributes="0"/>
+              <Component id="name" alignment="3" min="-2" max="-2" attributes="0"/>
+          </Group>
+      </Group>
+    </DimensionLayout>
+  </Layout>
+  <SubComponents>
+    <Component class="javax.swing.JLabel" name="jLabel1">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/netbeans/modules/lsp/client/bindings/refactoring/Bundle.properties" key="RenamePanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+    <Component class="javax.swing.JTextField" name="name">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/netbeans/modules/lsp/client/bindings/refactoring/Bundle.properties" key="RenamePanel.name.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+  </SubComponents>
+</Form>
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenamePanel.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenamePanel.java
new file mode 100644
index 0000000..b26e608
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenamePanel.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.lsp.client.bindings.refactoring;
+
+/**
+ *
+ * @author lahvac
+ */
+public class RenamePanel extends javax.swing.JPanel {
+
+    /**
+     * Creates new form RenamePanel
+     */
+    public RenamePanel() {
+        initComponents();
+    }
+
+    /**
+     * This method is called from within the constructor to initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is always
+     * regenerated by the Form Editor.
+     */
+    @SuppressWarnings("unchecked")
+    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+    private void initComponents() {
+
+        jLabel1 = new javax.swing.JLabel();
+        name = new javax.swing.JTextField();
+
+        org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(RenamePanel.class, "RenamePanel.jLabel1.text")); // NOI18N
+
+        name.setText(org.openide.util.NbBundle.getMessage(RenamePanel.class, "RenamePanel.name.text")); // NOI18N
+
+        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+        this.setLayout(layout);
+        layout.setHorizontalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(layout.createSequentialGroup()
+                .addComponent(jLabel1)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(name, javax.swing.GroupLayout.DEFAULT_SIZE, 355, Short.MAX_VALUE))
+        );
+        layout.setVerticalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                .addComponent(jLabel1)
+                .addComponent(name, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+        );
+    }// </editor-fold>//GEN-END:initComponents
+
+    public void setName(String name) {
+        this.name.setText(name);
+        this.name.setSelectionStart(0);
+        this.name.setSelectionEnd(name.length());
+    }
+
+    public String getName() {
+        return this.name.getText();
+    }
+
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.JLabel jLabel1;
+    private javax.swing.JTextField name;
+    // End of variables declaration//GEN-END:variables
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringUIImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenameRefactoringUIImpl.java
similarity index 68%
copy from ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringUIImpl.java
copy to ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenameRefactoringUIImpl.java
index 665e5cd..2cfb717 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringUIImpl.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenameRefactoringUIImpl.java
@@ -19,15 +19,18 @@
 package org.netbeans.modules.lsp.client.bindings.refactoring;
 
 import java.awt.Component;
-import javax.swing.JPanel;
 import javax.swing.event.ChangeListener;
-import org.eclipse.lsp4j.ReferenceParams;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.RenameParams;
+import org.eclipse.lsp4j.TextDocumentIdentifier;
 import org.netbeans.modules.lsp.client.LSPBindings;
+import org.netbeans.modules.lsp.client.Utils;
 import org.netbeans.modules.refactoring.api.AbstractRefactoring;
 import org.netbeans.modules.refactoring.api.Problem;
-import org.netbeans.modules.refactoring.api.WhereUsedQuery;
+import org.netbeans.modules.refactoring.api.RenameRefactoring;
 import org.netbeans.modules.refactoring.spi.ui.CustomRefactoringPanel;
 import org.netbeans.modules.refactoring.spi.ui.RefactoringUI;
+import org.openide.filesystems.FileObject;
 import org.openide.util.HelpCtx;
 import org.openide.util.NbBundle.Messages;
 import org.openide.util.lookup.Lookups;
@@ -36,16 +39,21 @@
  *
  * @author lahvac
  */
-public class RefactoringUIImpl implements RefactoringUI {
+public class RenameRefactoringUIImpl implements RefactoringUI {
 
     private final LSPBindings bindings;
-    private final ReferenceParams params;
+    private final FileObject file;
+    private final Position position;
     private final String name;
+    private final RenameParams params;
+    private RenamePanel panel;
 
-    public RefactoringUIImpl(LSPBindings binding, ReferenceParams params, String name) {
+    public RenameRefactoringUIImpl(LSPBindings binding, FileObject file, Position position, String name) {
         this.bindings = binding;
-        this.params = params;
+        this.file = file;
+        this.position = position;
         this.name = name;
+        this.params = new RenameParams();
     }
 
     @Override
@@ -56,19 +64,23 @@
     @Override
     @Messages({
         "# {0} - identifier",
-        "DESC_Usages=Usages of {0}"
+        "DESC_Rename=Renaming {0}"
     })
     public String getDescription() {
-        return Bundle.DESC_Usages(name);
+        return Bundle.DESC_Rename(name);
     }
 
     @Override
     public boolean isQuery() {
-        return true;
+        return false;
     }
 
     @Override
     public CustomRefactoringPanel getPanel(ChangeListener parent) {
+        if (panel == null) {
+            panel = new RenamePanel();
+        }
+        panel.setName(name);
         return new CustomRefactoringPanel() {
             @Override
             public void initialize() {
@@ -76,13 +88,16 @@
 
             @Override
             public Component getComponent() {
-                return new JPanel();
+                return panel;
             }
         };
     }
 
     @Override
     public Problem setParameters() {
+        params.setTextDocument(new TextDocumentIdentifier(Utils.toURI(file)));
+        params.setPosition(position);
+        params.setNewName(panel.getName());
         return null;
     }
 
@@ -93,12 +108,12 @@
 
     @Override
     public boolean hasParameters() {
-        return false;
+        return true;
     }
 
     @Override
     public AbstractRefactoring getRefactoring() {
-        return new WhereUsedQuery(Lookups.fixed(bindings, params));
+        return new RenameRefactoring(Lookups.fixed(bindings, params));
     }
 
     @Override
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringUIImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/WhereUsedRefactoringUIImpl.java
similarity index 94%
rename from ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringUIImpl.java
rename to ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/WhereUsedRefactoringUIImpl.java
index 665e5cd..bc16cc4 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/RefactoringUIImpl.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/WhereUsedRefactoringUIImpl.java
@@ -36,13 +36,13 @@
  *
  * @author lahvac
  */
-public class RefactoringUIImpl implements RefactoringUI {
+public class WhereUsedRefactoringUIImpl implements RefactoringUI {
 
     private final LSPBindings bindings;
     private final ReferenceParams params;
     private final String name;
 
-    public RefactoringUIImpl(LSPBindings binding, ReferenceParams params, String name) {
+    public WhereUsedRefactoringUIImpl(LSPBindings binding, ReferenceParams params, String name) {
         this.bindings = binding;
         this.params = params;
         this.name = name;
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/tree/DiffElement.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/tree/DiffElement.java
new file mode 100644
index 0000000..a9c3d25
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/tree/DiffElement.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.lsp.client.bindings.refactoring.tree;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import javax.swing.text.Position;
+import org.netbeans.modules.lsp.client.bindings.refactoring.ModificationResult;
+import org.netbeans.modules.lsp.client.bindings.refactoring.ModificationResult.Difference;
+import org.netbeans.modules.refactoring.spi.SimpleRefactoringElementImplementation;
+import org.openide.filesystems.FileObject;
+import org.openide.text.PositionBounds;
+import org.openide.text.PositionRef;
+import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.lookup.Lookups;
+
+/**
+ * Implementatation of RefactoringElementImplementation specific to refactoring
+ * in java files.
+ * 
+ * @author Jan Becicka
+ */
+ public final class DiffElement extends SimpleRefactoringElementImplementation {
+    private PositionBounds bounds;
+    private String displayText;
+    private FileObject parentFile;
+    private Difference diff;
+    private ModificationResult modification;
+    private WeakReference<String> newFileContent;
+    
+    @Messages("LBL_NotAvailable=Update")
+    private DiffElement(Difference diff, PositionBounds bounds, FileObject parentFile, ModificationResult modification) {
+        this.bounds = bounds;
+        final String description = diff.getDescription();
+        if (description == null) {
+            displayText = NbBundle.getMessage(DiffElement.class, "LBL_NotAvailable");
+        } else {
+            this.displayText = description;
+        }
+        this.parentFile = parentFile;
+        this.diff = diff;
+        this.modification = modification;
+    }
+
+    @Override
+    public String getDisplayText() {
+        return displayText;
+    }
+
+    @Override
+    public Lookup getLookup() {
+        Object composite = null;
+        if (bounds!=null) {
+//            composite = ElementGripFactory.getDefault().get(parentFile, bounds.getBegin().getOffset());
+        }
+        if (composite==null) {
+            composite = parentFile;
+        }
+        return Lookups.fixed(composite, diff);
+    }
+    
+    @Override
+    public void setEnabled(boolean enabled) {
+        diff.exclude(!enabled);
+        newFileContent = null;
+        super.setEnabled(enabled);
+    }
+
+    @Override
+    public PositionBounds getPosition() {
+        return bounds;
+    }
+
+    @Override
+    public String getText() {
+        return displayText;
+    }
+
+    @Override
+    public void performChange() {
+    }
+
+    @Override
+    public FileObject getParentFile() {
+        if (diff.getKind() == Difference.Kind.CREATE) {
+            return parentFile.getParent();
+        }
+        return parentFile;
+    }
+    
+    @Override
+    protected String getNewFileContent() {
+        String result;
+        if (newFileContent !=null) {
+            result = newFileContent.get();
+            if (result!=null) {
+                return result;
+            }
+        }
+        try {
+            if (diff.getKind()==Difference.Kind.CREATE) {
+                result = diff.getNewText();
+            } else {
+                result = modification.getResultingSource(parentFile);
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+            return null;
+        }
+        newFileContent = new WeakReference<String>(result);
+        return result;
+    }
+    
+    /**
+     * Factory method for DiffElement
+     * @param diff diff instance corresponding to thid Element
+     * @param fileObject fileObject corresponding to this Element
+     * @param modification 
+     * @return ModificationResult corresponding to this change
+     */
+    public static DiffElement create(Difference diff, FileObject fileObject, ModificationResult modification) {
+        Position start = diff.getStartPosition();
+        Position end = diff.getEndPosition();
+        PositionBounds bounds = null;
+        if (diff.getKind() != Difference.Kind.CREATE) {
+            // FIXME - unnecessary dependency on openide.text
+            bounds = new PositionBounds((PositionRef)start, (PositionRef)end);
+        }
+        return new DiffElement(diff, bounds, fileObject, modification);
+    }    
+}
diff --git a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/TestUtils.java b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/TestUtils.java
new file mode 100644
index 0000000..4ce03d3
--- /dev/null
+++ b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/TestUtils.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.lsp.client;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.function.Supplier;
+import org.eclipse.lsp4j.DidChangeConfigurationParams;
+import org.eclipse.lsp4j.DidChangeTextDocumentParams;
+import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
+import org.eclipse.lsp4j.DidCloseTextDocumentParams;
+import org.eclipse.lsp4j.DidOpenTextDocumentParams;
+import org.eclipse.lsp4j.DidSaveTextDocumentParams;
+import org.eclipse.lsp4j.launch.LSPLauncher;
+import org.eclipse.lsp4j.services.LanguageServer;
+import org.eclipse.lsp4j.services.TextDocumentService;
+import org.eclipse.lsp4j.services.WorkspaceService;
+import static org.junit.Assert.assertTrue;
+import org.netbeans.api.editor.mimelookup.MimePath;
+import org.netbeans.modules.editor.NbEditorKit;
+import org.netbeans.modules.lsp.client.spi.LanguageServerProvider;
+import org.netbeans.spi.editor.mimelookup.MimeDataProvider;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.MIMEResolver;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.Lookups;
+
+/**
+ *
+ * @author lahvac
+ */
+public class TestUtils {
+
+    public static final String MIME_TYPE = "application/mock-txt";
+    public static final String TEXT_PLAIN = "text/plain";
+   
+    public static final class MimeDataProviderImpl implements MimeDataProvider {
+        @Override
+        public Lookup getLookup(MimePath mp) {
+            switch (mp.getPath()) {
+                case MIME_TYPE:
+                    return Lookups.fixed(new MockLSP(), new NbEditorKit() {
+                        @Override
+                        public String getContentType() {
+                            return MIME_TYPE;
+                        }
+                    });
+                case TEXT_PLAIN:
+                    return Lookups.fixed(new NbEditorKit() {
+                        @Override
+                        public String getContentType() {
+                            return TEXT_PLAIN;
+                        }
+                    });
+                case "": return Lookup.EMPTY;
+                default: throw new AssertionError(mp.getPath());
+            }
+        }
+    }
+
+    public static class MockLSP implements LanguageServerProvider {
+        public static Supplier<LanguageServer> createServer = () -> { throw new UnsupportedOperationException(); };
+        @Override
+        public LanguageServerProvider.LanguageServerDescription startServer(Lookup lookup) {
+            try {
+                final MockProcess process = new MockProcess();
+                ServerSocket srv = new ServerSocket(0, 1, InetAddress.getLoopbackAddress());
+                Thread serverThread = new Thread(() -> {
+                    try {
+                        Socket server = srv.accept();
+
+                        LSPLauncher.createServerLauncher(createServer.get(), server.getInputStream(), server.getOutputStream()).startListening().get();
+                    } catch (Exception ex) {
+                        throw new IllegalStateException(ex);
+                    }
+                });
+                serverThread.start();
+                Socket client = new Socket(srv.getInetAddress(), srv.getLocalPort());
+
+                return LanguageServerProvider.LanguageServerDescription.create(client.getInputStream(), client.getOutputStream(), process);
+            } catch (Exception ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+    }
+
+    public final static class MockMimeResolver extends MIMEResolver {
+
+        public MockMimeResolver() {
+        }
+
+        @Override
+        public String findMIMEType(FileObject fo) {
+            switch (fo.getExt()) {
+                case "mock-txt": return MIME_TYPE;
+                case "txt": return TEXT_PLAIN;
+                default: return null;
+            }
+        }
+    }
+
+    static final class MockProcess extends Process {
+        final ByteArrayInputStream in;
+        final ByteArrayOutputStream out;
+
+        public MockProcess() {
+            this.in = new ByteArrayInputStream(new byte[0]);
+            this.out = new ByteArrayOutputStream();
+        }
+
+        @Override
+        public OutputStream getOutputStream() {
+            return out;
+        }
+
+        @Override
+        public InputStream getInputStream() {
+            return in;
+        }
+
+        @Override
+        public InputStream getErrorStream() {
+            return in;
+        }
+
+        @Override
+        public int waitFor() throws InterruptedException {
+            throw new InterruptedException();
+        }
+
+        @Override
+        public boolean isAlive() {
+            return true;
+        }
+
+        @Override
+        public int exitValue() {
+            return 0;
+        }
+
+        @Override
+        public void destroy() {
+        }
+    }
+    
+    public static class BaseTextDocumentServiceImpl implements TextDocumentService {
+        @Override
+        public void didOpen(DidOpenTextDocumentParams params) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void didChange(DidChangeTextDocumentParams params) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void didClose(DidCloseTextDocumentParams params) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void didSave(DidSaveTextDocumentParams params) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    public static class BaseWorkspaceServiceImpl implements WorkspaceService {
+
+        @Override
+        public void didChangeConfiguration(DidChangeConfigurationParams params) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+}
diff --git a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandlerTest.java b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandlerTest.java
index d7fe5a7..52cd005 100644
--- a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandlerTest.java
+++ b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandlerTest.java
@@ -18,13 +18,6 @@
  */
 package org.netbeans.modules.lsp.client.bindings;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -35,9 +28,7 @@
 import javax.swing.text.BadLocationException;
 import javax.swing.text.Document;
 import javax.swing.text.StyledDocument;
-import org.eclipse.lsp4j.DidChangeConfigurationParams;
 import org.eclipse.lsp4j.DidChangeTextDocumentParams;
-import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
 import org.eclipse.lsp4j.DidCloseTextDocumentParams;
 import org.eclipse.lsp4j.DidOpenTextDocumentParams;
 import org.eclipse.lsp4j.DidSaveTextDocumentParams;
@@ -50,28 +41,25 @@
 import org.eclipse.lsp4j.TextDocumentItem;
 import org.eclipse.lsp4j.TextDocumentSyncKind;
 import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
-import org.eclipse.lsp4j.launch.LSPLauncher;
 import org.eclipse.lsp4j.services.LanguageServer;
 import org.eclipse.lsp4j.services.TextDocumentService;
 import org.eclipse.lsp4j.services.WorkspaceService;
 import org.junit.Test;
-import org.netbeans.api.editor.mimelookup.MimePath;
 import org.netbeans.junit.MockServices;
-import org.netbeans.modules.editor.NbEditorKit;
 import org.netbeans.modules.editor.lib2.EditorApiPackageAccessor;
 import org.netbeans.modules.lsp.client.Utils;
-import org.netbeans.modules.lsp.client.spi.LanguageServerProvider;
-import org.netbeans.spi.editor.mimelookup.MimeDataProvider;
 import org.openide.cookies.EditorCookie;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileUtil;
-import org.openide.filesystems.MIMEResolver;
 import org.openide.loaders.DataObject;
 import org.openide.text.CloneableEditorSupport;
 import org.openide.text.NbDocument;
-import org.openide.util.Lookup;
-import org.openide.util.lookup.Lookups;
 import static org.junit.Assert.*;
+import org.netbeans.modules.lsp.client.TestUtils.BaseWorkspaceServiceImpl;
+import static org.netbeans.modules.lsp.client.TestUtils.MIME_TYPE;
+import org.netbeans.modules.lsp.client.TestUtils.MimeDataProviderImpl;
+import org.netbeans.modules.lsp.client.TestUtils.MockLSP;
+import org.netbeans.modules.lsp.client.TestUtils.MockMimeResolver;
 
 /**
  *
@@ -79,11 +67,11 @@
  */
 public class TextDocumentSyncServerCapabilityHandlerTest {
 
-    private static final String MIME_TYPE = "application/mock-txt";
     private static final List<String> eventLog = new ArrayList<>();
 
     @Test
     public void testOpenClose() throws Exception {
+        MockLSP.createServer = () -> new TestLanguageServer();
         MockServices.setServices(MimeDataProviderImpl.class, MockMimeResolver.class);
 
         new TextDocumentSyncServerCapabilityHandler.Init().run();
@@ -171,99 +159,6 @@
         }
     }
 
-    public static final class MimeDataProviderImpl implements MimeDataProvider {
-        @Override
-        public Lookup getLookup(MimePath mp) {
-            assertEquals("application/mock-txt", mp.getPath());
-            return Lookups.fixed(new MockLSP(), new NbEditorKit() {
-                @Override
-                public String getContentType() {
-                    return "application/mock-txt";
-                }
-            });
-        }
-    }
-
-    public static final class MockLSP implements LanguageServerProvider {
-        @Override
-        public LanguageServerProvider.LanguageServerDescription startServer(Lookup lookup) {
-            try {
-                final MockProcess process = new MockProcess();
-                ServerSocket srv = new ServerSocket(0, 1, InetAddress.getLoopbackAddress());
-                Thread serverThread = new Thread(() -> {
-                    try {
-                        Socket server = srv.accept();
-
-                        LSPLauncher.createServerLauncher(new TestLanguageServer(), server.getInputStream(), server.getOutputStream()).startListening().get();
-                    } catch (Exception ex) {
-                        throw new IllegalStateException(ex);
-                    }
-                });
-                serverThread.start();
-                Socket client = new Socket(srv.getInetAddress(), srv.getLocalPort());
-
-                return LanguageServerProvider.LanguageServerDescription.create(client.getInputStream(), client.getOutputStream(), process);
-            } catch (Exception ex) {
-                throw new IllegalStateException(ex);
-            }
-        }
-    }
-
-    public final static class MockMimeResolver extends MIMEResolver {
-
-        public MockMimeResolver() {
-        }
-
-        @Override
-        public String findMIMEType(FileObject fo) {
-            return fo.hasExt("mock-txt") ? "application/mock-txt" : null;
-        }
-    }
-
-    static final class MockProcess extends Process {
-        final ByteArrayInputStream in;
-        final ByteArrayOutputStream out;
-
-        public MockProcess() {
-            this.in = new ByteArrayInputStream(new byte[0]);
-            this.out = new ByteArrayOutputStream();
-        }
-
-        @Override
-        public OutputStream getOutputStream() {
-            return out;
-        }
-
-        @Override
-        public InputStream getInputStream() {
-            return in;
-        }
-
-        @Override
-        public InputStream getErrorStream() {
-            return in;
-        }
-
-        @Override
-        public int waitFor() throws InterruptedException {
-            throw new InterruptedException();
-        }
-
-        @Override
-        public boolean isAlive() {
-            return true;
-        }
-
-        @Override
-        public int exitValue() {
-            return 0;
-        }
-
-        @Override
-        public void destroy() {
-        }
-    }
-
     private static final class TestLanguageServer implements LanguageServer {
 
         @Override
@@ -327,17 +222,7 @@
 
         @Override
         public WorkspaceService getWorkspaceService() {
-            return new WorkspaceService() {
-                @Override
-                public void didChangeConfiguration(DidChangeConfigurationParams params) {
-                    throw new IllegalStateException("Should not be called.");
-                }
-
-                @Override
-                public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
-                    throw new IllegalStateException("Should not be called.");
-                }
-            };
+            return new BaseWorkspaceServiceImpl();
         }
 
     }
diff --git a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenameRefactoringTest.java b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenameRefactoringTest.java
new file mode 100644
index 0000000..c6fbdd5
--- /dev/null
+++ b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/refactoring/RenameRefactoringTest.java
@@ -0,0 +1,392 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.lsp.client.bindings.refactoring;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+import org.eclipse.lsp4j.CreateFile;
+import org.eclipse.lsp4j.DeleteFile;
+import org.eclipse.lsp4j.InitializeParams;
+import org.eclipse.lsp4j.InitializeResult;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.RenameFile;
+import org.eclipse.lsp4j.RenameOptions;
+import org.eclipse.lsp4j.RenameParams;
+import org.eclipse.lsp4j.ResourceOperation;
+import org.eclipse.lsp4j.ServerCapabilities;
+import org.eclipse.lsp4j.TextDocumentEdit;
+import org.eclipse.lsp4j.TextDocumentIdentifier;
+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.LanguageServer;
+import org.eclipse.lsp4j.services.TextDocumentService;
+import org.eclipse.lsp4j.services.WorkspaceService;
+import org.junit.Test;
+import org.netbeans.junit.MockServices;
+import org.netbeans.modules.lsp.client.Utils;
+import org.openide.cookies.EditorCookie;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.text.CloneableEditorSupport;
+import static org.junit.Assert.*;
+import org.netbeans.api.editor.mimelookup.MimePath;
+import org.netbeans.junit.NbTestCase;
+import org.netbeans.modules.editor.NbEditorKit;
+import org.netbeans.modules.lsp.client.LSPBindings;
+import org.netbeans.modules.lsp.client.TestUtils.BaseTextDocumentServiceImpl;
+import org.netbeans.modules.lsp.client.TestUtils.BaseWorkspaceServiceImpl;
+import static org.netbeans.modules.lsp.client.TestUtils.MIME_TYPE;
+import org.netbeans.modules.lsp.client.TestUtils.MimeDataProviderImpl;
+import org.netbeans.modules.lsp.client.TestUtils.MockLSP;
+import org.netbeans.modules.lsp.client.TestUtils.MockMimeResolver;
+import org.netbeans.modules.lsp.client.bindings.refactoring.Refactoring.LSPCreateFile;
+import org.netbeans.modules.lsp.client.bindings.refactoring.Refactoring.LSPDeleteFile;
+import org.netbeans.modules.lsp.client.bindings.refactoring.Refactoring.LSPRenameFile;
+import org.netbeans.modules.lsp.client.bindings.refactoring.tree.DiffElement;
+import org.netbeans.modules.refactoring.api.Problem;
+import org.netbeans.modules.refactoring.api.RefactoringElement;
+import org.netbeans.modules.refactoring.api.RefactoringSession;
+import org.netbeans.modules.refactoring.api.RenameRefactoring;
+import org.netbeans.modules.refactoring.api.impl.APIAccessor;
+import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
+import org.netbeans.modules.refactoring.spi.impl.UndoableWrapper;
+import org.netbeans.spi.editor.mimelookup.MimeDataProvider;
+import org.openide.text.PositionRef;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.Lookups;
+
+/**
+ *
+ * @author lahvac
+ */
+public class RenameRefactoringTest {
+
+    @Test
+    public void testSimpleRename() throws Exception {
+        MockLSP.createServer = () -> new TestLanguageServer();
+        MockServices.setServices(MimeDataProviderImpl.class, MockMimeResolver.class, RootMimeDataProviderImpl.class);
+
+        FileObject folder = FileUtil.createMemoryFileSystem().getRoot().createFolder("myfolder");
+        FileObject file1 = createFile(folder, "data1.mock-txt");
+
+        try (OutputStream out = file1.getOutputStream()) {
+            out.write(("  test  other\n" +
+                       "  other test\n").getBytes(StandardCharsets.UTF_8));
+        }
+
+        FileObject file2 = createFile(folder, "data2.mock-txt");
+
+        try (OutputStream out = file2.getOutputStream()) {
+            out.write(("  2test  other\n" +
+                       "  2other test\n").getBytes(StandardCharsets.UTF_8));
+        }
+
+        String uri = Utils.toURI(file1);
+
+        LSPBindings bindings = LSPBindings.getBindings(file1);
+        RenameParams renameParams = new RenameParams(new TextDocumentIdentifier(uri), new Position(1, 8), "newName");
+        List<Function<RenameParams, WorkspaceEdit>> renameFunctions = Arrays.asList(
+            params -> {
+                assertEquals(uri, params.getTextDocument().getUri());
+                assertEquals("newName", params.getNewName());
+
+                WorkspaceEdit result = new WorkspaceEdit();
+                String file1URI = params.getTextDocument().getUri();
+                TextDocumentEdit file1Edits = new TextDocumentEdit(new VersionedTextDocumentIdentifier(file1URI, -1),
+                                                                   Arrays.asList(new TextEdit(new Range(new Position(0, 2), new Position(0,  6)), "newName"),
+                                                                                 new TextEdit(new Range(new Position(1, 8), new Position(1, 12)), "newName")));
+                String file2URI = file1URI.replace("data1", "data2");
+                TextDocumentEdit file2Edits = new TextDocumentEdit(new VersionedTextDocumentIdentifier(file2URI, -1),
+                                                                   Arrays.asList(new TextEdit(new Range(new Position(0, 3), new Position(0,  7)), "newName"),
+                                                                                 new TextEdit(new Range(new Position(1, 9), new Position(1, 13)), "newName")));
+                result.setDocumentChanges(Arrays.asList(Either.forLeft(file1Edits), Either.forLeft(file2Edits)));
+                return result;
+            },
+            params -> {
+                assertEquals(uri, params.getTextDocument().getUri());
+                assertEquals("newName", params.getNewName());
+
+                WorkspaceEdit result = new WorkspaceEdit();
+                Map<String, List<TextEdit>> file2Edits = new HashMap<>();
+                String file1URI = params.getTextDocument().getUri();
+                file2Edits.put(file1URI, Arrays.asList(new TextEdit(new Range(new Position(0, 2), new Position(0,  6)), "newName"),
+                                                       new TextEdit(new Range(new Position(1, 8), new Position(1, 12)), "newName")));
+                String file2URI = file1URI.replace("data1", "data2");
+                file2Edits.put(file2URI, Arrays.asList(new TextEdit(new Range(new Position(0, 3), new Position(0,  7)), "newName"),
+                                                       new TextEdit(new Range(new Position(1, 9), new Position(1, 13)), "newName")));
+
+                result.setChanges(file2Edits);
+                return result;
+            }
+        );
+        for (Function<RenameParams, WorkspaceEdit> renameFunc : renameFunctions) {
+            renameFunction = renameFunc;
+
+            RenameRefactoring refactoring = new RenameRefactoring(Lookups.fixed(bindings, renameParams));
+
+            RefactoringSession session = RefactoringSession.create("test rename");
+            assertNull(refactoring.checkParameters());
+            assertNull(refactoring.preCheck());
+            assertNull(refactoring.prepare(session));
+
+            Set<String> elements = new HashSet<>();
+
+            for (RefactoringElement re : session.getRefactoringElements()) {
+                RefactoringElementImplementation impl =
+                        APIAccessor.DEFAULT.getRefactoringElementImplementation(re);
+                Method getNewFileContent = impl.getClass().getDeclaredMethod("getNewFileContent");
+
+                getNewFileContent.setAccessible(true);
+
+                String newFileContent = (String) getNewFileContent.invoke(impl);
+                String element = positionToString(re.getPosition().getBegin()) + "-" +
+                                 positionToString(re.getPosition().getEnd()) + ":" +
+                                 newFileContent;
+
+                elements.add(element);
+            }
+
+            Set<String> expectedElements = new HashSet<>(Arrays.asList(
+                    "1:9-1:13:  2newName  other\n" +
+                    "  2other newName\n",
+                    "0:3-0:7:  2newName  other\n" +
+                    "  2other newName\n",
+                    "1:8-1:12:  newName  other\n" +
+                    "  other newName\n",
+                    "0:2-0:6:  newName  other\n" +
+                    "  other newName\n"
+            ));
+
+            assertEquals(expectedElements, elements);
+
+            session.doRefactoring(true);
+
+            assertFile(file1, "  newName  other\n" +
+                              "  other newName\n");
+            assertFile(file2, "  2newName  other\n" +
+                              "  2other newName\n");
+
+            session.undoRefactoring(true);
+
+            assertFile(file1, "  test  other\n" +
+                              "  other test\n");
+            assertFile(file2, "  2test  other\n" +
+                              "  2other test\n");
+        }
+    }
+
+    @Test
+    public void testFileOperations() throws Exception {
+        MockLSP.createServer = () -> new TestLanguageServer();
+        MockServices.setServices(MimeDataProviderImpl.class, MockMimeResolver.class, RootMimeDataProviderImpl.class);
+
+        FileObject folder = FileUtil.createMemoryFileSystem().getRoot().createFolder("myfolder");
+        FileObject file1 = createFile(folder, "data1.mock-txt");
+
+        try (OutputStream out = file1.getOutputStream()) {
+            out.write(("  test  other\n" +
+                       "  other test\n").getBytes(StandardCharsets.UTF_8));
+        }
+
+        FileObject file2 = createFile(folder, "data2.mock-txt");
+
+        try (OutputStream out = file2.getOutputStream()) {
+            out.write(("  2test  other\n" +
+                       "  2other test\n").getBytes(StandardCharsets.UTF_8));
+        }
+
+        String uri = Utils.toURI(file1);
+
+        LSPBindings bindings = LSPBindings.getBindings(file1);
+        RenameParams renameParams = new RenameParams(new TextDocumentIdentifier(uri), new Position(1, 8), "newName");
+        renameFunction = params -> {
+            assertEquals(uri, params.getTextDocument().getUri());
+            assertEquals("newName", params.getNewName());
+
+            WorkspaceEdit result = new WorkspaceEdit();
+            String file1URI = params.getTextDocument().getUri();
+            TextDocumentEdit file1Edits = new TextDocumentEdit(new VersionedTextDocumentIdentifier(file1URI, -1),
+                                                               Arrays.asList(new TextEdit(new Range(new Position(1, 8), new Position(1, 12)), "newName")));
+            String file1aURI = file1URI.replace("data1", "data1a");
+            ResourceOperation file1Operation = new RenameFile(file1URI, file1aURI);
+            TextDocumentEdit file1aEdits = new TextDocumentEdit(new VersionedTextDocumentIdentifier(file1aURI, -1),
+                                                                Arrays.asList(new TextEdit(new Range(new Position(0, 2), new Position(0,  6)), "newName")));
+            String file2URI = file1URI.replace("data1", "data2");
+            ResourceOperation file2Operation = new DeleteFile(file2URI);
+            String file3URI = file1URI.replace("data1", "data3");
+            ResourceOperation file3Operation = new CreateFile(file3URI);
+            TextDocumentEdit file3Edits = new TextDocumentEdit(new VersionedTextDocumentIdentifier(file3URI, -1),
+                                                               Arrays.asList(new TextEdit(new Range(new Position(0, 0), new Position(0,  0)), "newName content\n")));
+            result.setDocumentChanges(Arrays.asList(Either.forLeft(file1Edits),
+                                                    Either.forRight(file1Operation),
+                                                    Either.forLeft(file1aEdits),
+                                                    Either.forRight(file2Operation),
+                                                    Either.forRight(file3Operation),
+                                                    Either.forLeft(file3Edits))
+            );
+            return result;
+        };
+
+        RenameRefactoring refactoring = new RenameRefactoring(Lookups.fixed(bindings, renameParams));
+
+        RefactoringSession session = RefactoringSession.create("test rename");
+        assertNull(refactoring.checkParameters());
+        assertNull(refactoring.preCheck());
+        Problem problem = refactoring.prepare(session);
+        assertNull(problem2String(problem), problem);
+
+        Set<String> elements = new HashSet<>();
+
+        for (RefactoringElement re : session.getRefactoringElements()) {
+            RefactoringElementImplementation impl =
+                    APIAccessor.DEFAULT.getRefactoringElementImplementation(re);
+            if (impl instanceof DiffElement) {
+                Method getNewFileContent = impl.getClass().getDeclaredMethod("getNewFileContent");
+
+                getNewFileContent.setAccessible(true);
+
+                String newFileContent = (String) getNewFileContent.invoke(impl);
+                String element = positionToString(re.getPosition().getBegin()) + "-" +
+                                 positionToString(re.getPosition().getEnd()) + ":" +
+                                 newFileContent;
+
+                elements.add(element);
+            } else if (impl instanceof LSPRenameFile ||
+                       impl instanceof LSPDeleteFile ||
+                       impl instanceof LSPCreateFile) {
+                elements.add(impl.toString());
+            } else {
+                fail("Unknown element class: " + impl.getClass());
+            }
+        }
+
+        Set<String> expectedElements = new HashSet<>(Arrays.asList(
+                "data2.mock-txt=>",
+                "data1.mock-txt=>data1a.mock-txt",
+                "1:8-1:12:  newName  other\n" +
+                "  other newName\n",
+                "0:2-0:6:  newName  other\n" +
+                "  other newName\n",
+                "=>data3.mock-txt(newName content\n)"
+        ));
+
+        assertEquals(expectedElements, elements);
+
+        session.doRefactoring(true);
+
+        assertEquals("data1a.mock-txt", file1.getNameExt());
+        assertFile(file1, "  newName  other\n" +
+                          "  other newName\n");
+        assertFalse(file2.isValid());
+
+        //Backup facility does not handle non file:// URLs:
+//        session.undoRefactoring(true);
+//
+//        assertFile(file1, "  test  other\n" +
+//                          "  other test\n");
+//        assertFile(file2, "  2test  other\n" +
+//                          "  2other test\n");
+    }
+
+    private FileObject createFile(FileObject folder, String name) throws Exception {
+        FileObject file = folder.createData(name);
+        EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
+        ((CloneableEditorSupport) ec).setMIMEType(MIME_TYPE);
+
+        return file;
+    }
+
+    private String positionToString(PositionRef p) throws IOException {
+        return "" + p.getLine() + ":" + p.getColumn();
+    }
+
+    private void assertFile(FileObject file, String expectedContent) throws IOException {
+        assertEquals(expectedContent, file.asText());
+    }
+
+    private String problem2String(Problem p) {
+        if (p == null){
+            return null;
+        }
+        return p.getMessage() + ":" + p.isFatal() + (p.getNext() != null ? "[" + problem2String(p.getNext()) + "]" : "");
+    }
+
+    private static Function<RenameParams, WorkspaceEdit> renameFunction;
+
+    private static final class TestLanguageServer implements LanguageServer {
+
+        @Override
+        public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
+            ServerCapabilities caps = new ServerCapabilities();
+            RenameOptions renameOptions = new RenameOptions();
+            caps.setRenameProvider(renameOptions);
+            InitializeResult initResult = new InitializeResult(caps);
+            return CompletableFuture.completedFuture(initResult);
+        }
+
+        @Override
+        public CompletableFuture<Object> shutdown() {
+            return CompletableFuture.completedFuture(null);
+        }
+
+        @Override
+        public void exit() {
+        }
+
+        @Override
+        public TextDocumentService getTextDocumentService() {
+            return new BaseTextDocumentServiceImpl() {
+                @Override
+                public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
+                    WorkspaceEdit result = renameFunction.apply(params);
+                    return CompletableFuture.completedFuture(result);
+                }
+            };
+        }
+
+        @Override
+        public WorkspaceService getWorkspaceService() {
+            return new BaseWorkspaceServiceImpl();
+        }
+
+    }
+
+    public static final class RootMimeDataProviderImpl implements MimeDataProvider {
+        @Override
+        public Lookup getLookup(MimePath mp) {
+            if ("".equals(mp.getPath())) {
+                return Lookups.fixed(new UndoableWrapper());
+            }
+            return Lookup.EMPTY;
+        }
+    }
+}
diff --git a/ide/parsing.api/src/org/netbeans/modules/parsing/api/Source.java b/ide/parsing.api/src/org/netbeans/modules/parsing/api/Source.java
index 95d076d..6fd7899 100644
--- a/ide/parsing.api/src/org/netbeans/modules/parsing/api/Source.java
+++ b/ide/parsing.api/src/org/netbeans/modules/parsing/api/Source.java
@@ -537,7 +537,6 @@
             @NonNull final Lookup context) {
         synchronized (Source.class) {
             final Source source = SourceFactory.getDefault().createSource(fileObject, mimeType, context);
-            assert source.context == context;
             return source;
         }
     }
diff --git a/ide/parsing.lucene/nbproject/project.properties b/ide/parsing.lucene/nbproject/project.properties
index 882f805..53ccf16 100644
--- a/ide/parsing.lucene/nbproject/project.properties
+++ b/ide/parsing.lucene/nbproject/project.properties
@@ -19,7 +19,7 @@
 javadoc.apichanges=${basedir}/apichanges.xml
 javac.compilerargs=-Xlint -Xlint:-serial
 
-spec.version.base=2.45.0
+spec.version.base=2.45.1
 test.config.stableBTD.includes=**/*Test.class
 test.config.stableBTD.excludes=\
     **/LuceneIndexTest.class
diff --git a/ide/parsing.lucene/nbproject/project.xml b/ide/parsing.lucene/nbproject/project.xml
index d9e096f..988a845 100644
--- a/ide/parsing.lucene/nbproject/project.xml
+++ b/ide/parsing.lucene/nbproject/project.xml
@@ -85,6 +85,7 @@
             </test-dependencies>
             <friend-packages>
                 <friend>org.netbeans.modules.cnd.indexing</friend>
+                <friend>org.netbeans.modules.java.lsp.server</friend>
                 <friend>org.netbeans.modules.java.source.base</friend>
                 <friend>org.netbeans.modules.java.sourceui</friend>
                 <friend>org.netbeans.modules.jumpto</friend>
diff --git a/ide/projectui/nbproject/project.xml b/ide/projectui/nbproject/project.xml
index e426523..7fe0cc7 100644
--- a/ide/projectui/nbproject/project.xml
+++ b/ide/projectui/nbproject/project.xml
@@ -40,15 +40,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.40</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.netbeans.api.progress.nb</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>1.40</specification-version>
+                        <specification-version>1.57.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java
index 0c4ec5e..7d4bf8a 100644
--- a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java
+++ b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java
@@ -59,7 +59,6 @@
 import org.netbeans.api.annotations.common.NonNull;
 import org.netbeans.api.annotations.common.NullAllowed;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.api.project.FileOwnerQuery;
 import org.netbeans.api.project.Project;
 import org.netbeans.api.project.ProjectInformation;
@@ -337,7 +336,7 @@
             action = a;
             currentFiles = Utilities.actionsGlobalContext().lookupResult(FileObject.class);
             currentFiles.addLookupListener(WeakListeners.create(LookupListener.class, this, currentFiles));
-            progress = ProgressHandleFactory.createHandle(CAP_Opening_Projects());
+            progress = ProgressHandle.createHandle(CAP_Opening_Projects());
         }
 
         final boolean waitFinished(long timeout) {
@@ -658,7 +657,7 @@
                 }
             }
             final Cancellation cancellation = new Cancellation();
-            final ProgressHandle handle = ProgressHandleFactory.createHandle(CAP_Opening_Projects(), cancellation);
+            final ProgressHandle handle = ProgressHandle.createHandle(CAP_Opening_Projects(), cancellation);
             handle.start();
             handle.progress(projects[0].getProjectDirectory().getNameExt());
             OPENING_RP.post(new Runnable() {
diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java
index 04eb24b..3ed3214 100644
--- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java
+++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java
@@ -46,7 +46,6 @@
 import java.util.prefs.Preferences;
 import javax.swing.SwingUtilities;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.api.project.FileOwnerQuery;
 import org.netbeans.api.project.Project;
 import org.netbeans.api.project.ProjectManager;
@@ -563,7 +562,7 @@
         } else {
             handleLabel = Group_open_handle(NONE_GROUP);
         }
-        final ProgressHandle h = ProgressHandleFactory.createHandle(handleLabel);
+        final ProgressHandle h = ProgressHandle.createHandle(handleLabel);
         try {
             h.start(200);
             ProjectUtilities.WaitCursor.show();
diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/zip/ExportZIP.java b/ide/projectui/src/org/netbeans/modules/project/ui/zip/ExportZIP.java
index 5a31f4c..47a8410 100644
--- a/ide/projectui/src/org/netbeans/modules/project/ui/zip/ExportZIP.java
+++ b/ide/projectui/src/org/netbeans/modules/project/ui/zip/ExportZIP.java
@@ -24,7 +24,6 @@
 import java.awt.event.ActionListener;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -51,7 +50,6 @@
 import javax.swing.event.DocumentEvent;
 import javax.swing.event.DocumentListener;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.api.project.FileOwnerQuery;
 import org.netbeans.api.project.Project;
 import org.netbeans.api.project.ui.OpenProjects;
@@ -135,7 +133,7 @@
     })
     private static boolean build(File root, File zip) throws IOException {
         final AtomicBoolean canceled = new AtomicBoolean();
-        ProgressHandle handle = ProgressHandleFactory.createHandle(MSG_building(zip.getName()), new Cancellable() {
+        ProgressHandle handle = ProgressHandle.createHandle(MSG_building(zip.getName()), new Cancellable() {
             @Override public boolean cancel() {
                 return canceled.compareAndSet(false, true);
             }
diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/zip/ImportZIP.java b/ide/projectui/src/org/netbeans/modules/project/ui/zip/ImportZIP.java
index 5bbe7cf..de83bb2 100644
--- a/ide/projectui/src/org/netbeans/modules/project/ui/zip/ImportZIP.java
+++ b/ide/projectui/src/org/netbeans/modules/project/ui/zip/ImportZIP.java
@@ -45,7 +45,6 @@
 import javax.swing.JPanel;
 import javax.swing.filechooser.FileNameExtensionFilter;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.api.project.FileOwnerQuery;
 import org.netbeans.api.project.Project;
 import org.netbeans.api.project.ProjectManager;
@@ -130,7 +129,7 @@
     private static void unpackAndOpen(File zip, File root) throws IOException {
         final AtomicBoolean canceled = new AtomicBoolean();
         List<Project> projects = new ArrayList<Project>();
-        ProgressHandle handle = ProgressHandleFactory.createHandle(MSG_unpacking(zip.getName()), new Cancellable() {
+        ProgressHandle handle = ProgressHandle.createHandle(MSG_unpacking(zip.getName()), new Cancellable() {
             @Override public boolean cancel() {
                 return canceled.compareAndSet(false, true);
             }
diff --git a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/AccessorImpl.java b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/AccessorImpl.java
index b61b8bc..d356f0b 100644
--- a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/AccessorImpl.java
+++ b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/AccessorImpl.java
@@ -19,11 +19,13 @@
 package org.netbeans.modules.refactoring.api;
 
 import java.util.Collection;
+import java.util.List;
 import org.netbeans.modules.refactoring.api.impl.APIAccessor;
 import org.netbeans.modules.refactoring.api.impl.SPIAccessor;
 import org.netbeans.modules.refactoring.spi.GuardedBlockHandler;
 import org.netbeans.modules.refactoring.spi.ProblemDetailsImplementation;
 import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
+import org.netbeans.modules.refactoring.spi.Transaction;
 import org.netbeans.modules.refactoring.spi.ui.FiltersDescription;
 
 /**
@@ -87,5 +89,15 @@
     public boolean isFinished(RefactoringSession session) {
         return session.isFinished();
     }
+
+    @Override
+    public List<Transaction> getCommits(RefactoringSession session) {
+        return session.getCommits();
+    }
+
+    @Override
+    public List<RefactoringElementImplementation> getFileChanges(RefactoringSession session) {
+        return session.getFileChanges();
+    }
     
 }
diff --git a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/RefactoringSession.java b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/RefactoringSession.java
index 22a8a06..64ffcfa 100644
--- a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/RefactoringSession.java
+++ b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/RefactoringSession.java
@@ -188,7 +188,7 @@
         }
         return null;
     }
-    
+
     private class ProgressL implements ProgressListener {
 
         private float progressStep;
@@ -383,6 +383,14 @@
         }
     }
 
+
+    List<Transaction> getCommits() {
+        return SPIAccessor.DEFAULT.getCommits(bag);
+    }
+
+    List<RefactoringElementImplementation> getFileChanges() {
+        return SPIAccessor.DEFAULT.getFileChanges(bag);
+    }
     
     private class ElementsCollection extends AbstractCollection<RefactoringElement> {
         @Override
diff --git a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/APIAccessor.java b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/APIAccessor.java
index c96c6a2..abe8ad9 100644
--- a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/APIAccessor.java
+++ b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/APIAccessor.java
@@ -19,6 +19,7 @@
 package org.netbeans.modules.refactoring.api.impl;
 
 import java.util.Collection;
+import java.util.List;
 import org.netbeans.modules.refactoring.api.AbstractRefactoring;
 import org.netbeans.modules.refactoring.api.Problem;
 import org.netbeans.modules.refactoring.api.ProblemDetails;
@@ -26,7 +27,9 @@
 import org.netbeans.modules.refactoring.api.RefactoringSession;
 import org.netbeans.modules.refactoring.spi.GuardedBlockHandler;
 import org.netbeans.modules.refactoring.spi.ProblemDetailsImplementation;
+import org.netbeans.modules.refactoring.spi.RefactoringCommit;
 import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
+import org.netbeans.modules.refactoring.spi.Transaction;
 import org.netbeans.modules.refactoring.spi.ui.FiltersDescription;
 
 /**
@@ -56,5 +59,7 @@
     public abstract FiltersDescription getFiltersDescription(AbstractRefactoring refactoring);
     public abstract void resetFiltersDescription(AbstractRefactoring refactoring);
     public abstract boolean isFinished(RefactoringSession session);
+    public abstract List<Transaction> getCommits(RefactoringSession session);
+    public abstract List<RefactoringElementImplementation> getFileChanges(RefactoringSession session);
 
 }
diff --git a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/SPIAccessor.java b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/SPIAccessor.java
index c40b3cd..9240a03 100644
--- a/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/SPIAccessor.java
+++ b/ide/refactoring.api/src/org/netbeans/modules/refactoring/api/impl/SPIAccessor.java
@@ -22,6 +22,8 @@
 import java.util.Collection;
 import java.util.List;
 import org.netbeans.modules.refactoring.api.RefactoringSession;
+import org.netbeans.modules.refactoring.spi.ModificationResult;
+import org.netbeans.modules.refactoring.spi.RefactoringCommit;
 import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
 import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
 import org.netbeans.modules.refactoring.spi.SimpleRefactoringElementImplementation;
@@ -52,5 +54,6 @@
     public abstract boolean hasChangesInReadOnlyFiles(RefactoringElementsBag bag);
     public abstract void check(Transaction commit, boolean undo);
     public abstract void sum(Transaction commit);
+    public abstract Collection<? extends ModificationResult> getTransactions(RefactoringCommit c);
     
 }
diff --git a/ide/refactoring.api/src/org/netbeans/modules/refactoring/plugins/FileRenamePlugin.java b/ide/refactoring.api/src/org/netbeans/modules/refactoring/plugins/FileRenamePlugin.java
index fce8666..7a5a78f 100644
--- a/ide/refactoring.api/src/org/netbeans/modules/refactoring/plugins/FileRenamePlugin.java
+++ b/ide/refactoring.api/src/org/netbeans/modules/refactoring/plugins/FileRenamePlugin.java
@@ -72,7 +72,7 @@
     public void cancelRequest() {
     }
     
-    private class RenameFile extends SimpleRefactoringElementImplementation {
+    public class RenameFile extends SimpleRefactoringElementImplementation {
         
         private FileObject fo;
         public RenameFile(FileObject fo, RefactoringElementsBag bag) {
diff --git a/ide/refactoring.api/src/org/netbeans/modules/refactoring/spi/AccessorImpl.java b/ide/refactoring.api/src/org/netbeans/modules/refactoring/spi/AccessorImpl.java
index 875afe7..db11ab4 100644
--- a/ide/refactoring.api/src/org/netbeans/modules/refactoring/spi/AccessorImpl.java
+++ b/ide/refactoring.api/src/org/netbeans/modules/refactoring/spi/AccessorImpl.java
@@ -80,4 +80,10 @@
             ((RefactoringCommit) commit).sum();
         }
     }
+
+    @Override
+    public Collection<? extends ModificationResult> getTransactions(RefactoringCommit c) {
+        return c.results;
+    }
+    
 }
diff --git a/ide/spi.debugger.ui/apichanges.xml b/ide/spi.debugger.ui/apichanges.xml
index 0703882..90170b2 100644
--- a/ide/spi.debugger.ui/apichanges.xml
+++ b/ide/spi.debugger.ui/apichanges.xml
@@ -261,6 +261,21 @@
         <class package="org.netbeans.spi.debugger.ui" name="DebuggingView"/>
     </change>
 
+    <change id="DVFrameSourceMimeType">
+        <api name="DebuggerCoreSPI"/>
+        <summary>Source MIME type added to DVFrame.</summary>
+        <version major="2" minor="67"/>
+        <date day="30" month="11" year="2020"/>
+        <author login="mentlicher"/>
+        <compatibility binary="compatible" source="compatible" addition="yes" semantic="compatible"/>
+        <description>
+            <p>
+                <code>DebuggingView.DVFrame</code> provides source MIME type.
+            </p>
+        </description>
+        <class package="org.netbeans.spi.debugger.ui" name="DebuggingView"/>
+    </change>
+
 </changes>
 
   <!-- Now the surrounding HTML text and document structure: -->
diff --git a/ide/spi.debugger.ui/manifest.mf b/ide/spi.debugger.ui/manifest.mf
index c410f70..414b804 100644
--- a/ide/spi.debugger.ui/manifest.mf
+++ b/ide/spi.debugger.ui/manifest.mf
@@ -2,6 +2,6 @@
 OpenIDE-Module: org.netbeans.spi.debugger.ui/1
 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/debugger/ui/Bundle.properties
 OpenIDE-Module-Layer: org/netbeans/modules/debugger/resources/mf-layer.xml
-OpenIDE-Module-Specification-Version: 2.65
+OpenIDE-Module-Specification-Version: 2.65.1
 OpenIDE-Module-Provides: org.netbeans.spi.debugger.ui
 OpenIDE-Module-Install: org/netbeans/modules/debugger/ui/DebuggerModule.class
diff --git a/ide/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/DebuggingView.java b/ide/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/DebuggingView.java
index 263297d..dd085bf 100644
--- a/ide/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/DebuggingView.java
+++ b/ide/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/DebuggingView.java
@@ -477,13 +477,23 @@
         void makeCurrent();
 
         /**
-         * Gen URI of the source file associated with this frame, if any.
+         * Get URI of the source file associated with this frame, if any.
          * @return a source URI, or <code>null</code> if the file is unknown.
          * @since 2.65
          */
         URI getSourceURI();
 
         /**
+         * Get the source MIME type, if known.
+         * @return the source MIME type, or <code>null</code> if the source, or
+         * its MIME type is unknown.
+         * @since 2.67
+         */
+        default String getSourceMimeType() {
+            return null;
+        }
+
+        /**
          * Line location of the frame in the source code at {@link #getSourceURI()}.
          *
          * @return the line number, or <code>-1</code> if the line is unknown
diff --git a/java/debugger.jpda.truffle/nbproject/project.xml b/java/debugger.jpda.truffle/nbproject/project.xml
index aff8c0d..f731b8d 100644
--- a/java/debugger.jpda.truffle/nbproject/project.xml
+++ b/java/debugger.jpda.truffle/nbproject/project.xml
@@ -58,7 +58,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>2</release-version>
-                        <specification-version>1.98</specification-version>
+                        <specification-version>1.117.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
@@ -130,7 +130,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>2.65</specification-version>
+                        <specification-version>2.65.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/FirstSourceURLProvider.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/FirstSourceURLProvider.java
index 2340344..d769294 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/FirstSourceURLProvider.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/FirstSourceURLProvider.java
@@ -52,26 +52,27 @@
     @Override
     public String getURL(String relativePath, boolean global) {
         if (TRUFFLE_ACCESSOR_PATH.equals(relativePath)) {
-            JPDAThread currentThread = debugger.getCurrentThread();
-            CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(currentThread);
-            if (currentPCInfo != null) {
-                return currentPCInfo.getSourcePosition().getSource().getUrl().toExternalForm();
-            }
+            return getCurrentURL();
         }
         return null;
     }
     
     public String getURL(JPDAClassType clazz, String stratum) {
         if (TRUFFLE_ACCESSOR_CLASS_NAME.equals(clazz.getName())) {
-            JPDAThread currentThread = debugger.getCurrentThread();
-            CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(currentThread);
-            if (currentPCInfo != null) {
-                Source source = currentPCInfo.getSourcePosition().getSource();
-                if (source != null) {
-                    URL url = source.getUrl();
-                    if (url != null) {
-                        return url.toExternalForm();
-                    }
+            return getCurrentURL();
+        }
+        return null;
+    }
+
+    private String getCurrentURL() {
+        JPDAThread currentThread = debugger.getCurrentThread();
+        CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentGuestPCInfo(currentThread);
+        if (currentPCInfo != null) {
+            Source source = currentPCInfo.getSourcePosition().getSource();
+            if (source != null) {
+                URL url = source.getUrl();
+                if (url != null) {
+                    return url.toExternalForm();
                 }
             }
         }
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/CurrentPCInfo.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/CurrentPCInfo.java
index c1dcc60..d9e4904 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/CurrentPCInfo.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/CurrentPCInfo.java
@@ -19,6 +19,7 @@
 
 package org.netbeans.modules.debugger.jpda.truffle.access;
 
+import com.sun.jdi.AbsentInformationException;
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeSupport;
 import java.lang.ref.Reference;
@@ -27,6 +28,7 @@
 
 import org.netbeans.api.debugger.jpda.JPDAThread;
 import org.netbeans.api.debugger.jpda.LocalVariable;
+import org.netbeans.modules.debugger.jpda.JPDADebuggerImpl;
 import org.netbeans.modules.debugger.jpda.truffle.ast.TruffleNode;
 import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
 import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackInfo;
@@ -93,6 +95,13 @@
     }
 
     public void setSelectedStackFrame(TruffleStackFrame selectedStackFrame) {
+        if (selectedStackFrame != null) {
+            ((JPDADebuggerImpl) selectedStackFrame.getDebugger()).setCurrentCallStackFrame(null);
+//            try {
+//                selectedStackFrame.getThread().getCallStack(0, 1)[0].makeCurrent();
+//            } catch (AbsentInformationException ex) {}
+            selectedStackFrame.getDebugger().getSession().setCurrentLanguage(TruffleStrataProvider.TRUFFLE_STRATUM);
+        }
         TruffleStackFrame old = this.selectedStackFrame;
         this.selectedStackFrame = selectedStackFrame;
         if (old != selectedStackFrame) {
@@ -101,6 +110,9 @@
     }
     
     public TruffleNode getAST(TruffleStackFrame frame) {
+        if (frame == null) {
+            return null;
+        }
         return truffleNodes.apply(frame.getDepth());
     }
     
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/ExecutionHaltedInfo.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/ExecutionHaltedInfo.java
index 9a7787d..e45aff4 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/ExecutionHaltedInfo.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/ExecutionHaltedInfo.java
@@ -22,6 +22,7 @@
 import org.netbeans.api.debugger.jpda.Field;
 import org.netbeans.api.debugger.jpda.LocalVariable;
 import org.netbeans.api.debugger.jpda.ObjectVariable;
+import org.netbeans.api.debugger.jpda.Variable;
 
 /**
  * Halted information from the backend <code>JPDATruffleAccessor.executionHalted()</code>.
@@ -33,22 +34,24 @@
     final boolean haltedBefore;
     final ObjectVariable returnValue;
     final ObjectVariable frameInfo;
+    final boolean supportsJavaFrames;
     final ObjectVariable[] breakpointsHit;
     final ObjectVariable[] breakpointConditionExceptions;
     final LocalVariable stepCmd;
     
-    private ExecutionHaltedInfo(LocalVariable[] vars) {
+    private ExecutionHaltedInfo(Variable[] vars) {
         this.debugManager = (ObjectVariable) vars[0];
         this.sourcePositions = (ObjectVariable) vars[1];
         this.haltedBefore = (Boolean) vars[2].createMirrorObject();
         this.returnValue = (ObjectVariable) vars[3];
         this.frameInfo = (ObjectVariable) vars[4];
-        this.breakpointsHit = getObjectArray((ObjectVariable) vars[5]);
-        this.breakpointConditionExceptions = getObjectArray((ObjectVariable) vars[6]);
-        this.stepCmd = vars[7];
+        this.supportsJavaFrames = (Boolean) vars[5].createMirrorObject();
+        this.breakpointsHit = vars.length > 6 ? getObjectArray((ObjectVariable) vars[6]) : null;
+        this.breakpointConditionExceptions = vars.length > 7 ? getObjectArray((ObjectVariable) vars[7]) : null;
+        this.stepCmd = vars.length > 8 ? (LocalVariable) vars[8] : null;
     }
     
-    static ExecutionHaltedInfo get(LocalVariable[] vars) {
+    static ExecutionHaltedInfo get(Variable[] vars) {
         return new ExecutionHaltedInfo(vars);
     }
     
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleAccess.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleAccess.java
index de1ebc8..6560dd5 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleAccess.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleAccess.java
@@ -22,18 +22,29 @@
 import com.sun.jdi.AbsentInformationException;
 import com.sun.jdi.ClassType;
 import com.sun.jdi.InvocationException;
+import com.sun.jdi.Method;
+import com.sun.jdi.ObjectReference;
 import com.sun.jdi.StringReference;
+import com.sun.jdi.ThreadReference;
+import com.sun.jdi.Value;
+import com.sun.jdi.event.ClassPrepareEvent;
+import com.sun.jdi.event.Event;
+import com.sun.jdi.event.EventSet;
+import com.sun.jdi.event.LocatableEvent;
+import com.sun.jdi.request.EventRequest;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyVetoException;
 import java.io.InvalidObjectException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.WeakHashMap;
 import java.util.concurrent.locks.Lock;
+import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -46,15 +57,28 @@
 import org.netbeans.api.debugger.jpda.JPDAClassType;
 import org.netbeans.api.debugger.jpda.JPDADebugger;
 import org.netbeans.api.debugger.jpda.JPDAThread;
+import org.netbeans.api.debugger.jpda.LineBreakpoint;
 import org.netbeans.api.debugger.jpda.LocalVariable;
 import org.netbeans.api.debugger.jpda.MethodBreakpoint;
 import org.netbeans.api.debugger.jpda.ObjectVariable;
 import org.netbeans.api.debugger.jpda.Variable;
 import org.netbeans.api.debugger.jpda.event.JPDABreakpointEvent;
 import org.netbeans.api.debugger.jpda.event.JPDABreakpointListener;
+import org.netbeans.modules.debugger.jpda.JPDADebuggerImpl;
 
 import org.netbeans.modules.debugger.jpda.expr.InvocationExceptionTranslated;
 import org.netbeans.modules.debugger.jpda.expr.JDIVariable;
+import org.netbeans.modules.debugger.jpda.jdi.ClassTypeWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.IllegalThreadStateExceptionWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.InternalExceptionWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.ObjectCollectedExceptionWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.ThreadReferenceWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.VMDisconnectedExceptionWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.event.ClassPrepareEventWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.event.EventSetWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.event.EventWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.event.LocatableEventWrapper;
+import org.netbeans.modules.debugger.jpda.jdi.request.EventRequestWrapper;
 import org.netbeans.modules.debugger.jpda.models.JPDAClassTypeImpl;
 import org.netbeans.modules.debugger.jpda.models.JPDAThreadImpl;
 import org.netbeans.modules.debugger.jpda.truffle.LanguageName;
@@ -69,6 +93,7 @@
 import org.netbeans.modules.debugger.jpda.truffle.vars.impl.TruffleScope;
 import org.netbeans.modules.debugger.jpda.truffle.vars.impl.TruffleStackVariable;
 import org.netbeans.modules.debugger.jpda.truffle.vars.TruffleVariable;
+import org.netbeans.modules.debugger.jpda.util.Executor;
 import org.netbeans.modules.debugger.jpda.util.WeakHashMapActive;
 import org.openide.util.Exceptions;
 
@@ -89,7 +114,9 @@
     private static final String VAR_FRAME = "frame";                            // NOI18N
     private static final String VAR_SRC_ID = "id";                              // NOI18N
     private static final String VAR_SRC_URI = "uri";                            // NOI18N
+    private static final String VAR_SRC_MIMETYPE = "mimeType";                  // NOI18N
     private static final String VAR_SRC_NAME = "name";                          // NOI18N
+    private static final String VAR_SRC_HOST_METHOD = "hostMethodName";         // NOI18N
     private static final String VAR_SRC_PATH = "path";                          // NOI18N
     private static final String VAR_SRC_SOURCESECTION = "sourceSection";        // NOI18N
     private static final String VAR_SRC_CODE = "code";
@@ -102,12 +129,15 @@
     private static final String METHOD_GET_VARIABLES_SGN = "(Lcom/oracle/truffle/api/debug/DebugStackFrame;)[Ljava/lang/Object;";  // NOI18N
     private static final String METHOD_GET_SCOPE_VARIABLES = "getScopeVariables";// NOI18N
     private static final String METHOD_GET_SCOPE_VARIABLES_SGN = "(Lcom/oracle/truffle/api/debug/DebugScope;)[Ljava/lang/Object;"; // NOI18N
+    private static final String METHOD_SUSPEND_HERE = "suspendHere";            // NOI18N
+    private static final String METHOD_SUSPEND_HERE_SGN = "()[Ljava/lang/Object;";// NOI18N
     private static final String METHOD_SET_UNWIND = "setUnwind";// NOI18N
     private static final String METHOD_SET_UNWIND_SGN = "(I)Z"; // NOI18N
     private static final String METHOD_GET_AST = "getTruffleAST";               // NOI18N
     private static final String METHOD_GET_AST_SGN = "(I)[Ljava/lang/Object;";
     
     private static final Map<JPDAThread, ThreadInfo> currentPCInfos = new WeakHashMap<>();
+    private static final Map<JPDAThread, ThreadInfo> suspendHerePCInfos = new WeakHashMap<>();
     private static final PropertyChangeListener threadResumeListener = new ThreadResumeListener();
     
     private static final TruffleAccess DEFAULT = new TruffleAccess();
@@ -147,6 +177,14 @@
     }
     
     public static CurrentPCInfo getCurrentPCInfo(JPDAThread thread) {
+        CurrentPCInfo cpi = getCurrentGuestPCInfo(thread);
+        if (cpi == null) {
+            cpi = getCurrentSuspendHereInfo(thread);
+        }
+        return cpi;
+    }
+
+    public static CurrentPCInfo getCurrentGuestPCInfo(JPDAThread thread) {
         ThreadInfo info;
         synchronized (currentPCInfos) {
             info = currentPCInfos.get(thread);
@@ -172,6 +210,142 @@
         return null;
     }
 
+    public static CurrentPCInfo getCurrentSuspendHereInfo(JPDAThread thread) {
+        synchronized (suspendHerePCInfos) {
+            ThreadInfo info = suspendHerePCInfos.get(thread);
+            if (info != null) {
+                return info.cpi;
+            }
+        }
+        return null;
+    }
+
+    public static CurrentPCInfo getSuspendHere(JPDAThread thread) {
+        if (getCurrentGuestPCInfo(thread) != null) {
+            // Suspended in the guest language already.
+            return null;
+        }
+        ThreadInfo info;
+        synchronized (suspendHerePCInfos) {
+            info = suspendHerePCInfos.get(thread);
+            if (info != null) {
+                if (info.cpi != null) {
+                    return info.cpi;
+                } else if (info.noCpi) {
+                    return null;
+                }
+            }
+            if (info == null) {
+                info = new ThreadInfo();
+                suspendHerePCInfos.put(thread, info);
+            }
+        }
+        synchronized (info) {
+            if (info.cpi != null) {
+                return info.cpi;
+            }
+            if (info.noCpi) {
+                return null;
+            }
+            JPDADebugger debugger = ((JPDAThreadImpl) thread).getDebugger();
+            ClassType debugAccessorClass = TruffleDebugManager.getDebugAccessorClass(debugger);
+            if (debugAccessorClass == null) {
+                info.noCpi = true;
+                return null;
+            }
+            ObjectVariable haltInfo = invokeSuspendHere(debugAccessorClass, thread);
+            if (haltInfo == null) {
+                info.noCpi = true;
+                return null;
+            }
+            CurrentPCInfo cpi = getCurrentPosition(debugger, thread, ((ObjectVariable) haltInfo).getFields(0, Integer.MAX_VALUE));
+            cpi.setSelectedStackFrame(null);
+            info.cpi = cpi;
+            return cpi;
+        }
+    }
+
+    private static ObjectVariable invokeSuspendHere(ClassType debugAccessorClass, JPDAThread thread) {
+        JPDAThreadImpl threadImpl = (JPDAThreadImpl) thread;
+        ThreadReference tr = threadImpl.getThreadReference();
+        try {
+            Method suspendHereMethod = ClassTypeWrapper.concreteMethodByName(debugAccessorClass, METHOD_SUSPEND_HERE, METHOD_SUSPEND_HERE_SGN);
+            JPDADebuggerImpl debugger = threadImpl.getDebugger();
+            Value haltInfo;
+            Lock writeLock = threadImpl.accessLock.writeLock();
+            Function<EventSet, Boolean> breakpointEventInterceptor = null;
+            try {
+                writeLock.lock();
+                breakpointEventInterceptor = skipSuspendedEventClearLeakingReferences(debugger, thread);
+                haltInfo = ClassTypeWrapper.invokeMethod(debugAccessorClass, tr, suspendHereMethod, Collections.emptyList(), ObjectReference.INVOKE_SINGLE_THREADED);
+            } finally {
+                ((JPDADebuggerImpl) debugger).getOperator().removeEventInterceptor(breakpointEventInterceptor);
+                writeLock.unlock();
+            }
+            if (haltInfo instanceof ObjectReference) {
+                return (ObjectVariable) debugger.getVariable(haltInfo);
+            }
+        } catch (InvocationException ex) {
+            Exceptions.printStackTrace(ex);
+        } catch (Exception ex) {
+            LOG.log(Level.CONFIG, "Invoking " + METHOD_SUSPEND_HERE, ex);
+        }
+        return null;
+    }
+
+    private static Function<EventSet, Boolean> skipSuspendedEventClearLeakingReferences(JPDADebugger debugger, JPDAThread thread) {
+        ThreadReference tr = ((JPDAThreadImpl) thread).getThreadReference();
+        MethodBreakpoint clearLeakingReferencesBreakpoint = MethodBreakpoint.create("com.oracle.truffle.api.debug.SuspendedEvent", "clearLeakingReferences");
+        clearLeakingReferencesBreakpoint.setBreakpointType(MethodBreakpoint.TYPE_METHOD_ENTRY);
+        clearLeakingReferencesBreakpoint.setThreadFilters(debugger, new JPDAThread[] { thread });
+        clearLeakingReferencesBreakpoint.setHidden(true);
+        Function<EventSet, Boolean> breakpointEventInterceptor = eventSet -> {
+            ThreadReference etr = null;
+            try {
+                for (Event e: eventSet) {
+                    if (e instanceof ClassPrepareEvent) {
+                        etr = ClassPrepareEventWrapper.thread((ClassPrepareEvent) e);
+                    } else if (e instanceof LocatableEvent) {
+                        etr = LocatableEventWrapper.thread((LocatableEvent) e);
+                    }
+                }
+                if (tr.equals(etr)) {
+                    boolean resume = true;
+                    for (Event e: eventSet) {
+                        EventRequest r = EventWrapper.request(e);
+                        Executor exec = (r != null) ? (Executor) EventRequestWrapper.getProperty (r, "executor") : null;
+                        resume = resume & exec.exec(e);
+                    }
+                    if (resume) {
+                        try {
+                            EventSetWrapper.resume(eventSet);
+                        } catch (IllegalThreadStateExceptionWrapper | ObjectCollectedExceptionWrapper ex) {
+                            Exceptions.printStackTrace(ex);
+                        }
+                    }
+                    return true;
+                } else {
+                    return false;
+                }
+            } catch (InternalExceptionWrapper | VMDisconnectedExceptionWrapper ex) {
+                return false;
+            }
+        };
+        clearLeakingReferencesBreakpoint.addJPDABreakpointListener(event -> {
+            DebuggerManager.getDebuggerManager().removeBreakpoint(clearLeakingReferencesBreakpoint);
+            try {
+                ThreadReferenceWrapper.forceEarlyReturn(tr, tr.virtualMachine().mirrorOfVoid());
+            } catch (Exception ex) {
+                Exceptions.printStackTrace(ex);
+            }
+            event.resume();
+            ((JPDADebuggerImpl) debugger).getOperator().removeEventInterceptor(breakpointEventInterceptor);
+        });
+        ((JPDADebuggerImpl) debugger).getOperator().addEventInterceptor(breakpointEventInterceptor);
+        DebuggerManager.getDebuggerManager().addBreakpoint(clearLeakingReferencesBreakpoint);
+        return breakpointEventInterceptor;
+    }
+
     @Override
     public void breakpointReached(JPDABreakpointEvent event) {
         Object bp = event.getSource();
@@ -213,12 +387,20 @@
         }
     }
 
-    private CurrentPCInfo getCurrentPosition(JPDADebugger debugger, JPDAThread thread) {
+    private static CurrentPCInfo getCurrentPosition(JPDADebugger debugger, JPDAThread thread) {
         try {
             CallStackFrame csf = thread.getCallStack(0, 1)[0];
             LocalVariable[] localVariables = csf.getLocalVariables();
-            ExecutionHaltedInfo haltedInfo = ExecutionHaltedInfo.get(localVariables);
-            //JPDAClassType debugAccessor = TruffleDebugManager.getDebugAccessorJPDAClass(debugger);
+            return getCurrentPosition(debugger, thread, localVariables);
+        } catch (AbsentInformationException | IllegalStateException ex) {
+            Exceptions.printStackTrace(ex);
+            return null;
+        }
+    }
+
+    private static CurrentPCInfo getCurrentPosition(JPDADebugger debugger, JPDAThread thread, Variable[] haltVars) {
+        try {
+            ExecutionHaltedInfo haltedInfo = ExecutionHaltedInfo.get(haltVars);
             ObjectVariable sourcePositionVar = haltedInfo.sourcePositions;
             SourcePosition sp = getSourcePosition(debugger, sourcePositionVar);
             
@@ -230,11 +412,11 @@
             String topFrameDescription = (String) frameInfoVar.getField(VAR_TOP_FRAME).createMirrorObject();
             ObjectVariable thisObject = null;// TODO: (ObjectVariable) frameInfoVar.getField("thisObject");
             TruffleStackFrame topFrame = new TruffleStackFrame(debugger, thread, 0, frame, topFrameDescription, null/*code*/, scopes, thisObject, true);
-            TruffleStackInfo stack = new TruffleStackInfo(debugger, thread, stackTrace);
+            TruffleStackInfo stack = new TruffleStackInfo(debugger, thread, stackTrace, haltedInfo.supportsJavaFrames);
             return new CurrentPCInfo(haltedInfo.stepCmd, thread, sp, scopes, topFrame, stack, depth -> {
                 return getTruffleAST(debugger, (JPDAThreadImpl) thread, depth, sp, stack);
             });
-        } catch (AbsentInformationException | IllegalStateException ex) {
+        } catch (IllegalStateException ex) {
             Exceptions.printStackTrace(ex);
             return null;
         }
@@ -306,10 +488,12 @@
         Source src = Source.getExistingSource(debugger, id);
         if (src == null) {
             String name = (String) sourcePositionVar.getField(VAR_SRC_NAME).createMirrorObject();
+            String hostMethodName = (String) sourcePositionVar.getField(VAR_SRC_HOST_METHOD).createMirrorObject();
             String path = (String) sourcePositionVar.getField(VAR_SRC_PATH).createMirrorObject();
             URI uri = (URI) sourcePositionVar.getField(VAR_SRC_URI).createMirrorObject();
+            String mimeType = (String) sourcePositionVar.getField(VAR_SRC_MIMETYPE).createMirrorObject();
             StringReference codeRef = (StringReference) ((JDIVariable) sourcePositionVar.getField(VAR_SRC_CODE)).getJDIValue();
-            src = Source.getSource(debugger, id, name, path, uri, codeRef);
+            src = Source.getSource(debugger, id, name, hostMethodName, path, uri, mimeType, codeRef);
         }
         return new SourcePosition(debugger, id, src, sourceSection);
     }
@@ -406,8 +590,10 @@
         }
         int sourceId;
         String sourceName;
+        String hostMethodName;
         String sourcePath;
         URI sourceURI;
+        String mimeType;
         String sourceSection;
         try {
             int i1 = 0;
@@ -418,6 +604,9 @@
             sourceName = sourceDef.substring(i1, i2);
             i1 = i2 + 1;
             i2 = sourceDef.indexOf('\n', i1);
+            hostMethodName = sourceDef.substring(i1, i2);
+            i1 = i2 + 1;
+            i2 = sourceDef.indexOf('\n', i1);
             sourcePath = sourceDef.substring(i1, i2);
             i1 = i2 + 1;
             i2 = sourceDef.indexOf('\n', i1);
@@ -428,6 +617,9 @@
             }
             i1 = i2 + 1;
             i2 = sourceDef.indexOf('\n', i1);
+            mimeType = sourceDef.substring(i1, i2);
+            i1 = i2 + 1;
+            i2 = sourceDef.indexOf('\n', i1);
             if (i2 < 0) {
                 i2 = sourceDef.length();
             }
@@ -435,7 +627,7 @@
         } catch (IndexOutOfBoundsException ioob) {
             throw new IllegalStateException("var source definition='"+sourceDef+"'", ioob);
         }
-        Source src = Source.getSource(debugger, sourceId, sourceName, sourcePath, sourceURI, codeRef);
+        Source src = Source.getSource(debugger, sourceId, sourceName, hostMethodName, sourcePath, sourceURI, mimeType, codeRef);
         return new SourcePosition(debugger, sourceId, src, sourceSection);
     }
     
@@ -556,6 +748,7 @@
 
     private static final class ThreadInfo {
         volatile CurrentPCInfo cpi;
+        volatile boolean noCpi;
     }
 
     private static final class ThreadResumeListener implements PropertyChangeListener {
@@ -565,13 +758,19 @@
             JPDAThreadImpl t = (JPDAThreadImpl) evt.getSource();
             if (!(Boolean) evt.getNewValue() && !t.isMethodInvoking()) { // not suspended, resumed
                 synchronized (currentPCInfos) {
-                    ThreadInfo info = currentPCInfos.get(t);
-                    if (info != null) {
-                        info.cpi = null;
-                    }
+                    clear(currentPCInfos.get(t));
+                }
+                synchronized (suspendHerePCInfos) {
+                    clear(suspendHerePCInfos.get(t));
                 }
             }
         }
 
+        private void clear(ThreadInfo info) {
+            if (info != null) {
+                info.cpi = null;
+                info.noCpi = false;
+            }
+        }
     }
 }
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleEval.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleEval.java
index 08c0e9c..7f1bf07 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleEval.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleEval.java
@@ -26,7 +26,9 @@
 import org.netbeans.api.debugger.jpda.JPDAThread;
 import org.netbeans.api.debugger.jpda.ObjectVariable;
 import org.netbeans.api.debugger.jpda.Variable;
+import org.netbeans.modules.debugger.jpda.expr.InvocationExceptionTranslated;
 import org.netbeans.modules.debugger.jpda.truffle.TruffleDebugManager;
+import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
 import org.openide.util.Exceptions;
 import org.openide.util.NbBundle;
 
@@ -48,7 +50,11 @@
         if (currentPCInfo == null) {
             throw new InvalidExpressionException(Bundle.MSG_NoSuspend());
         }
-        ObjectVariable stackFrameInstance = currentPCInfo.getSelectedStackFrame().getStackFrameInstance();
+        TruffleStackFrame selectedStackFrame = currentPCInfo.getSelectedStackFrame();
+        if (selectedStackFrame == null) {
+            throw new InvalidExpressionException(Bundle.MSG_NoSuspend());
+        }
+        ObjectVariable stackFrameInstance = selectedStackFrame.getStackFrameInstance();
         JPDAClassType debugAccessor = TruffleDebugManager.getDebugAccessorJPDAClass(debugger);
         try {
             Variable mirrorExpression = debugger.createMirrorVar(expression);
@@ -58,6 +64,13 @@
                     new Variable[] { stackFrameInstance,
                                      mirrorExpression });
             return valueVar;
+        } catch (InvalidExpressionException ex) {
+            Throwable targetException = ex.getTargetException();
+            if (targetException instanceof InvocationExceptionTranslated) {
+                // We do not want to prepend Java exception message:
+                ((InvocationExceptionTranslated) targetException).resetInvocationMessage();
+            }
+            throw ex;
         } catch (InvalidObjectException | NoSuchMethodException ex) {
             try {
                 return debugger.createMirrorVar(ex.getLocalizedMessage());
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleStrataProvider.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleStrataProvider.java
index 8a50fed..464b610 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleStrataProvider.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/access/TruffleStrataProvider.java
@@ -61,7 +61,7 @@
     @Override
     public int getStrataLineNumber(CallStackFrameImpl csf, String stratum) {
         if (TRUFFLE_STRATUM.equals(stratum) && isInTruffleAccessPoint(csf)) {
-            CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(csf.getThread());
+            CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentGuestPCInfo(csf.getThread());
             if (currentPCInfo != null) {
                 return currentPCInfo.getSourcePosition().getStartLine();
             }
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/PauseInGraalScriptActionProvider.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/PauseInGraalScriptActionProvider.java
index 4dfc2f6..3e695ef 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/PauseInGraalScriptActionProvider.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/PauseInGraalScriptActionProvider.java
@@ -95,7 +95,7 @@
         setEnabled(NAME, hasServiceClass);
         if (hasServiceClass) {
             JPDAThread currentThread = debugger.getCurrentThread();
-            if (currentThread != null && TruffleAccess.getCurrentPCInfo(currentThread) != null) {
+            if (currentThread != null && TruffleAccess.getCurrentGuestPCInfo(currentThread) != null) {
                 suspendState = false;
             }
             updateActionState(true, suspendState);
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/StepActionProvider.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/StepActionProvider.java
index 2ca211a..51a99f9 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/StepActionProvider.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/actions/StepActionProvider.java
@@ -73,8 +73,8 @@
             ActionsManager.ACTION_CONTINUE
     })));
 
-    private static final String STEP2JAVA_CLASS = "com.oracle.truffle.polyglot.HostMethodDesc$SingleMethod$MHBase";
-    private static final String STEP2JAVA_METHOD = "invokeHandle";
+    public static final String STEP2JAVA_CLASS = "com.oracle.truffle.polyglot.HostMethodDesc$SingleMethod$MHBase";
+    public static final String STEP2JAVA_METHOD = "invokeHandle";
 
     public StepActionProvider (ContextProvider lookupProvider) {
         super (
@@ -92,7 +92,7 @@
                 i.next (),
                 (debuggerState == JPDADebugger.STATE_STOPPED) &&
                 (currentThread != null) &&
-                (TruffleAccess.getCurrentPCInfo(currentThread) != null)
+                (TruffleAccess.getCurrentGuestPCInfo(currentThread) != null)
             );
         }
     }
@@ -102,7 +102,7 @@
         LOG.fine("doAction("+action+")");
         JPDADebuggerImpl debugger = getDebuggerImpl();
         JPDAThread currentThread = debugger.getCurrentThread();
-        CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(currentThread);
+        CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentGuestPCInfo(currentThread);
         int stepCmd = 0;
         if (ActionsManager.ACTION_CONTINUE.equals(action)) {
             stepCmd = 0;
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/breakpoints/impl/TruffleBreakpointsHandler.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/breakpoints/impl/TruffleBreakpointsHandler.java
index 532bba9..0c244e2 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/breakpoints/impl/TruffleBreakpointsHandler.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/breakpoints/impl/TruffleBreakpointsHandler.java
@@ -21,6 +21,7 @@
 
 import com.sun.jdi.AbsentInformationException;
 import com.sun.jdi.ArrayReference;
+import com.sun.jdi.BooleanValue;
 import com.sun.jdi.ClassNotLoadedException;
 import com.sun.jdi.ClassType;
 import com.sun.jdi.IncompatibleThreadStateException;
@@ -192,6 +193,12 @@
             if (bp.isEnabled()) {
                 bpImpl = setLineBreakpoint(debugManager, t, uri, bp.getLineNumber(),
                                            getIgnoreCount(bp), bp.getCondition());
+                // Find out whether the breakpoint was resolved already during the submission:
+                try {
+                    updateResolved(bp, bpImpl, t.getThreadReference());
+                } catch (Exception ex) {
+                    Exceptions.printStackTrace(Exceptions.attachMessage(ex, "Testing resolved breakpoint at "+uri+":"+bp.getLineNumber()));
+                }
             } else {
                 bpImpl = null;
             }
@@ -210,6 +217,15 @@
         }
     }
 
+    private void updateResolved(JSLineBreakpoint breakpoint, ObjectReference bp, ThreadReference tr) throws InternalExceptionWrapper, VMDisconnectedExceptionWrapper, ClassNotPreparedExceptionWrapper, InvalidTypeException, ClassNotLoadedException, IncompatibleThreadStateException, InvocationException, ObjectCollectedExceptionWrapper {
+        ClassType breakpointClass = (ClassType) bp.referenceType();
+        Method isResolvedMethod = ClassTypeWrapper.concreteMethodByName(breakpointClass, "isResolved", "()Z");
+        BooleanValue isResolvedValue = (BooleanValue) ObjectReferenceWrapper.invokeMethod(bp, tr, isResolvedMethod, Collections.emptyList(), ObjectReference.INVOKE_SINGLE_THREADED);
+        if (isResolvedValue.value()) {
+            JSBreakpointStatus.setValid(breakpoint, "resolved");
+        }
+    }
+
     private static int getIgnoreCount(JSLineBreakpoint bp) {
         int ignoreCount = 0;
         if (Breakpoint.HIT_COUNT_FILTERING_STYLE.GREATER.equals(bp.getHitCountFilteringStyle())) {
@@ -301,10 +317,16 @@
                                     ObjectReference.INVOKE_SINGLE_THREADED);
                             ret.disableCollection();
                             bpRef[0] = ret;
+                            // Find out whether the breakpoint was resolved already during the submission:
+                            for (Value v : ret.getValues()) {
+                                if (v instanceof ObjectReference) {
+                                    updateResolved(bp, (ObjectReference) v, tr);
+                                }
+                            }
                         } catch (InvalidTypeException | ClassNotLoadedException |
                                  IncompatibleThreadStateException | UnsupportedOperationExceptionWrapper |
                                  InternalExceptionWrapper | VMDisconnectedExceptionWrapper |
-                                 ObjectCollectedExceptionWrapper ex) {
+                                 ObjectCollectedExceptionWrapper | ClassNotPreparedExceptionWrapper ex) {
                             Exceptions.printStackTrace(Exceptions.attachMessage(ex, "Setting breakpoint to "+uri+":"+line));
                         } finally {
                             persistents.collect();
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackFrame.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackFrame.java
index ac74193..3b3f91b 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackFrame.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackFrame.java
@@ -56,13 +56,17 @@
     
     private final int    sourceId;
     private final String sourceName;
+    private final String hostClassName;
+    private final String hostMethodName;
     private final String sourcePath;
     private final URI    sourceURI;
+    private final String mimeType;
     private final String sourceSection;
     private final StringReference codeRef;
     private TruffleScope[] scopes;
     private final ObjectVariable thisObject;
     private final boolean isInternal;
+    private final boolean isHost;
     
     public TruffleStackFrame(JPDADebugger debugger, JPDAThread thread, int depth,
                              ObjectVariable frameInstance,
@@ -90,6 +94,9 @@
             methodName = frameDefinition.substring(i1, i2);
             i1 = i2 + 1;
             i2 = frameDefinition.indexOf('\n', i1);
+            isHost = Boolean.valueOf(frameDefinition.substring(i1, i2));
+            i1 = i2 + 1;
+            i2 = frameDefinition.indexOf('\n', i1);
             language = LanguageName.parse(frameDefinition.substring(i1, i2));
             i1 = i2 + 1;
             i2 = frameDefinition.indexOf('\n', i1);
@@ -105,12 +112,21 @@
             sourcePath = frameDefinition.substring(i1, i2);
             i1 = i2 + 1;
             i2 = frameDefinition.indexOf('\n', i1);
+            hostClassName = frameDefinition.substring(i1, i2);
+            i1 = i2 + 1;
+            i2 = frameDefinition.indexOf('\n', i1);
+            hostMethodName = frameDefinition.substring(i1, i2);
+            i1 = i2 + 1;
+            i2 = frameDefinition.indexOf('\n', i1);
             try {
                 sourceURI = new URI(frameDefinition.substring(i1, i2));
             } catch (URISyntaxException usex) {
                 throw new IllegalStateException("Bad URI: "+frameDefinition.substring(i1, i2), usex);
             }
             i1 = i2 + 1;
+            i2 = frameDefinition.indexOf('\n', i1);
+            mimeType = frameDefinition.substring(i1, i2);
+            i1 = i2 + 1;
             if (includeInternal) {
                 i2 = frameDefinition.indexOf('\n', i1);
                 sourceSection = frameDefinition.substring(i1, i2);
@@ -140,6 +156,14 @@
         return depth;
     }
     
+    public String getHostClassName() {
+        return hostClassName;
+    }
+
+    public String getHostMethodName() {
+        return hostMethodName;
+    }
+
     public String getMethodName() {
         return methodName;
     }
@@ -163,7 +187,7 @@
     public SourcePosition getSourcePosition() {
         Source src = Source.getExistingSource(debugger, sourceId);
         if (src == null) {
-            src = Source.getSource(debugger, sourceId, sourceName, sourcePath, sourceURI, codeRef);
+            src = Source.getSource(debugger, sourceId, sourceName, hostMethodName, sourcePath, sourceURI, mimeType, codeRef);
         }
         SourcePosition sp = new SourcePosition(debugger, sourceId, src, sourceSection);
         return sp;
@@ -184,7 +208,7 @@
         if (depth > 0) {
             boolean unwindScheduled = TruffleAccess.unwind(debugger, thread, depth - 1);
             if (unwindScheduled) {
-                CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(thread);
+                CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentGuestPCInfo(thread);
                 try {
                     currentPCInfo.getStepCommandVar().setFromMirrorObject(-1);
                     thread.resume();
@@ -203,4 +227,7 @@
         return isInternal;
     }
     
+    public boolean isHost() {
+        return isHost;
+    }
 }
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackInfo.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackInfo.java
index 0502462..deb0ffb 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackInfo.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/TruffleStackInfo.java
@@ -41,19 +41,25 @@
 public final class TruffleStackInfo {
     
     private static final String METHOD_GET_FRAMES_INFO = "getFramesInfo";       // NOI18N
-    private static final String METHOD_GET_FRAMES_INFO_SIG = "([Lcom/oracle/truffle/api/debug/DebugStackFrame;Z)[Ljava/lang/Object;";   // NOI18N
+    private static final String METHOD_GET_FRAMES_INFO_SIG = "([Lcom/oracle/truffle/api/debug/DebugStackFrame;ZZ)[Ljava/lang/Object;";   // NOI18N
     
     private final JPDADebugger debugger;
     private final JPDAThread thread;
     private final ObjectVariable stackTrace;
+    private final boolean supportsJavaFrames;
     private TruffleStackFrame[] stackFrames;
     private boolean includedInternalFrames;
     private boolean areInternalFrames;
 
     public TruffleStackInfo(JPDADebugger debugger, JPDAThread thread, ObjectVariable stackTrace) {
+        this(debugger, thread, stackTrace, false);
+    }
+
+    public TruffleStackInfo(JPDADebugger debugger, JPDAThread thread, ObjectVariable stackTrace, boolean supportsJavaFrames) {
         this.debugger = debugger;
         this.thread = thread;
         this.stackTrace = stackTrace;
+        this.supportsJavaFrames = supportsJavaFrames;
     }
 
     public TruffleStackFrame[] getStackFrames(boolean includeInternal) {
@@ -67,15 +73,21 @@
     public boolean hasInternalFrames() {
         return areInternalFrames;
     }
-        
+
+    public boolean hasJavaFrames() {
+        return supportsJavaFrames;
+    }
+
     private TruffleStackFrame[] loadStackFrames(boolean includeInternal) {
         JPDAClassType debugAccessor = TruffleDebugManager.getDebugAccessorJPDAClass(debugger);
         try {
             Variable internalVar = debugger.createMirrorVar(includeInternal, true);
+            Variable javaFramesVar = debugger.createMirrorVar(supportsJavaFrames, true);
             Variable framesVar = debugAccessor.invokeMethod(METHOD_GET_FRAMES_INFO,
                                                             METHOD_GET_FRAMES_INFO_SIG,
                                                             new Variable[] { stackTrace,
-                                                                             internalVar });
+                                                                             internalVar,
+                                                                             javaFramesVar });
             Field[] framesInfos = ((ObjectVariable) framesVar).getFields(0, Integer.MAX_VALUE);
             String framesDesc = (String) framesInfos[0].createMirrorObject();
             Field[] codes = ((ObjectVariable) framesInfos[1]).getFields(0, Integer.MAX_VALUE);
@@ -88,7 +100,8 @@
             int i2;
             int depth = 1;
             List<TruffleStackFrame> truffleFrames = new ArrayList<>();
-            while ((i2 = framesDesc.indexOf("\n\n", i1)) > 0) {
+            String frameSeparator = "\n\t\n";
+            while ((i2 = framesDesc.indexOf(frameSeparator, i1)) > 0) {
                 StringReference codeRef = (StringReference) ((JDIVariable) codes[depth-1]).getJDIValue();
                 ObjectVariable frameInstance = (ObjectVariable) stackTrace.getFields(0, Integer.MAX_VALUE)[depth - 1];
                 TruffleStackFrame tsf = new TruffleStackFrame(
@@ -98,7 +111,7 @@
                 if (includeInternal && tsf.isInternal()) {
                     areInternalFrames = true;
                 }
-                i1 = i2 + 2;
+                i1 = i2 + frameSeparator.length();
                 depth++;
             }
             return truffleFrames.toArray(new TruffleStackFrame[truffleFrames.size()]);
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleActionsProvider.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleActionsProvider.java
index dc0acf1..324fbfd 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleActionsProvider.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleActionsProvider.java
@@ -19,15 +19,26 @@
 
 package org.netbeans.modules.debugger.jpda.truffle.frames.models;
 
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
 import javax.swing.Action;
 import javax.swing.SwingUtilities;
+import org.netbeans.api.debugger.jpda.CallStackFrame;
+import org.netbeans.api.debugger.jpda.JPDAThread;
 
 import org.netbeans.modules.debugger.jpda.EditorContextBridge;
+import org.netbeans.modules.debugger.jpda.JPDADebuggerImpl;
 import org.netbeans.modules.debugger.jpda.truffle.access.CurrentPCInfo;
 import org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess;
 import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
 import org.netbeans.modules.debugger.jpda.truffle.options.TruffleOptions;
 import org.netbeans.modules.debugger.jpda.truffle.source.SourcePosition;
+import org.netbeans.modules.debugger.jpda.util.WeakCacheMap;
 
 import org.netbeans.spi.debugger.ContextProvider;
 import org.netbeans.spi.debugger.DebuggerServiceRegistration;
@@ -39,6 +50,7 @@
 
 import org.openide.util.NbBundle;
 import org.openide.util.RequestProcessor;
+import org.openide.util.WeakListeners;
 
 @DebuggerServiceRegistration(path="netbeans-JPDASession/DebuggingView",
                              types=NodeActionsProviderFilter.class,
@@ -50,26 +62,40 @@
     private final Action POP_TO_HERE_ACTION;
     private final Action SHOW_INTERNAL_ACTION;
     private final Action HIDE_INTERNAL_ACTION;
+    private final Action SHOW_HOST_ACTION;
+    private final Action HIDE_HOST_ACTION;
+    private final RequestProcessor requestProcessor;
+
+    private static final Map<DebuggingView.DVThread, Boolean> SHOWING_ALL_HOST_FRAMES = Collections.synchronizedMap(new WeakHashMap<>());
+    private static final PropertyChangeSupport SHOWING_ALL_HOST_FRAMES_CHANGE = new PropertyChangeSupport(new Object());
     
     public DebuggingTruffleActionsProvider(ContextProvider lookupProvider) {
-        RequestProcessor requestProcessor = lookupProvider.lookupFirst(null, RequestProcessor.class);
+        requestProcessor = lookupProvider.lookupFirst(null, RequestProcessor.class);
         MAKE_CURRENT_ACTION = createMAKE_CURRENT_ACTION(requestProcessor);
         GO_TO_SOURCE_ACTION = createGO_TO_SOURCE_ACTION(requestProcessor);
         POP_TO_HERE_ACTION = createPOP_TO_HERE_ACTION(requestProcessor);
         SHOW_INTERNAL_ACTION = createSHOW_INTERNAL_ACTION();
         HIDE_INTERNAL_ACTION = createHIDE_INTERNAL_ACTION();
+        SHOW_HOST_ACTION = createSHOW_HOST_ACTION();
+        HIDE_HOST_ACTION = createHIDE_HOST_ACTION();
     }
 
     @Override
     public void performDefaultAction(NodeActionsProvider original, Object node) throws UnknownTypeException {
         if (node instanceof TruffleStackFrame) {
-            TruffleStackFrame f = (TruffleStackFrame) node;
-            CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(f.getThread());
+            requestProcessor.post(() ->{
+                TruffleStackFrame f = (TruffleStackFrame) node;
+                CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(f.getThread());
+                if (currentPCInfo != null) {
+                    currentPCInfo.setSelectedStackFrame(f);
+                    goToSource(f);
+                }
+            });
+        } else if (node instanceof CallStackFrame) {
+            CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(((CallStackFrame) node).getThread());
             if (currentPCInfo != null) {
-                currentPCInfo.setSelectedStackFrame(f);
-                goToSource(f);
+                currentPCInfo.setSelectedStackFrame(null);
             }
-        } else {
             original.performDefaultAction(node);
         }
     }
@@ -96,20 +122,60 @@
             }
             return actions;*/
         } else {
-            return original.getActions(node);
+            Action[] actions = original.getActions(node);
+            if (node instanceof DebuggingView.DVThread && hasGuestInfo(node)) {
+                Action showAction;
+                if (!isShowAllHostFrames((DebuggingView.DVThread) node)) {
+                    showAction = SHOW_HOST_ACTION;
+                } else {
+                    showAction = HIDE_HOST_ACTION;
+                }
+                int pos = actions.length;
+                actions = Arrays.copyOf(actions, pos + 2);
+                actions[pos++] = null;
+                actions[pos] = showAction;
+            }
+            return actions;
         }
     }
     
+    private static boolean hasGuestInfo(Object node) {
+        JPDAThread thread = ((WeakCacheMap.KeyedValue<JPDAThread>) node).getKey();
+        return TruffleAccess.getCurrentPCInfo(thread) != null;
+    }
+    
+    static boolean isShowAllHostFrames(DebuggingView.DVThread thread) {
+        Boolean is = SHOWING_ALL_HOST_FRAMES.get(thread);
+        return Boolean.TRUE.equals(is);
+    }
+    
+    static void onShowAllHostFramesChange(PropertyChangeListener listener) {
+        SHOWING_ALL_HOST_FRAMES_CHANGE.addPropertyChangeListener(WeakListeners.propertyChange(listener, SHOWING_ALL_HOST_FRAMES_CHANGE));
+    }
+    
     private static void goToSource(final TruffleStackFrame f) {
         final SourcePosition sourcePosition = f.getSourcePosition();
         SwingUtilities.invokeLater (new Runnable () {
             @Override
             public void run () {
-                EditorContextBridge.getContext().showSource (
-                    sourcePosition.getSource().getUrl().toExternalForm(),
-                    sourcePosition.getStartLine(),
-                    f.getDebugger()
-                );
+                if (sourcePosition.getSource().getHostMethodName() == null) {
+                    EditorContextBridge.getContext().showSource (
+                        sourcePosition.getSource().getUrl().toExternalForm(),
+                        sourcePosition.getStartLine(),
+                        f.getDebugger()
+                    );
+                } else {
+                    String path = sourcePosition.getSource().getPath();
+                    if (path != null) {
+                        path = path.replace (File.separatorChar, '/');
+                        String url = ((JPDADebuggerImpl) f.getDebugger()).getEngineContext().getURL(path, true);
+                        EditorContextBridge.getContext().showSource (
+                            url,
+                            sourcePosition.getStartLine(),
+                            f.getDebugger()
+                        );
+                    }
+                }
             }
         });
     }
@@ -125,12 +191,7 @@
                     TruffleStackFrame f = (TruffleStackFrame) node;
                     CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(f.getThread());
                     if (currentPCInfo != null) {
-                        TruffleStackFrame topFrame = currentPCInfo.getTopFrame();
-                        if (topFrame == null) {
-                            return f.getDepth() > 0;
-                        } else {
-                            return f != topFrame;
-                        }
+                        return f != currentPCInfo.getSelectedStackFrame();
                     }
                 }
                 return false;
@@ -230,6 +291,56 @@
         );
     }
 
+    @NbBundle.Messages("CTL_StackFrameAction_ShowHost_Label=Show All Host Frames")
+    static final Action createSHOW_HOST_ACTION() {
+        return Models.createAction (
+            Bundle.CTL_StackFrameAction_ShowHost_Label(),
+            new Models.ActionPerformer () {
+                @Override
+                public boolean isEnabled (Object node) {
+                    if (!(node instanceof DebuggingView.DVThread)) {
+                        return false;
+                    }
+                    return !isShowAllHostFrames((DebuggingView.DVThread) node);
+                }
+
+                @Override
+                public void perform (final Object[] nodes) {
+                    for (Object node : nodes) {
+                        SHOWING_ALL_HOST_FRAMES.put((DebuggingView.DVThread) node, Boolean.TRUE);
+                    }
+                    SHOWING_ALL_HOST_FRAMES_CHANGE.firePropertyChange("allHostFrames", false, true);
+                }
+            },
+            Models.MULTISELECTION_TYPE_ALL
+        );
+    }
+
+    @NbBundle.Messages("CTL_StackFrameAction_HideHost_Label=Show Graal Guest Frames")
+    static final Action createHIDE_HOST_ACTION() {
+        return Models.createAction (
+            Bundle.CTL_StackFrameAction_HideHost_Label(),
+            new Models.ActionPerformer () {
+                @Override
+                public boolean isEnabled (Object node) {
+                    if (!(node instanceof DebuggingView.DVThread)) {
+                        return false;
+                    }
+                    return isShowAllHostFrames((DebuggingView.DVThread) node);
+                }
+
+                @Override
+                public void perform (final Object[] nodes) {
+                    for (Object node : nodes) {
+                        SHOWING_ALL_HOST_FRAMES.remove((DebuggingView.DVThread) node);
+                    }
+                    SHOWING_ALL_HOST_FRAMES_CHANGE.firePropertyChange("allHostFrames", true, false);
+                }
+            },
+            Models.MULTISELECTION_TYPE_ALL
+        );
+    }
+
     @NbBundle.Messages("CTL_StackFrameAction_PopToHere_Label=Pop To Here")
     private Action createPOP_TO_HERE_ACTION(RequestProcessor requestProcessor) {
         return Models.createAction (
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleTreeModel.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleTreeModel.java
index 7e85cd9..af95f4b 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleTreeModel.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingTruffleTreeModel.java
@@ -31,7 +31,8 @@
 import org.netbeans.api.debugger.jpda.JPDAThread;
 import org.netbeans.modules.debugger.jpda.truffle.access.CurrentPCInfo;
 import org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess;
-import org.netbeans.modules.debugger.jpda.truffle.access.TruffleStrataProvider;
+import static org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess.BASIC_CLASS_NAME;
+import org.netbeans.modules.debugger.jpda.truffle.actions.StepActionProvider;
 import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
 import org.netbeans.modules.debugger.jpda.truffle.options.TruffleOptions;
 import org.netbeans.modules.debugger.jpda.ui.debugging.JPDADVFrame;
@@ -47,15 +48,15 @@
 import org.netbeans.spi.viewmodel.TreeModelFilter;
 import org.netbeans.spi.viewmodel.UnknownTypeException;
 
-@DebuggerServiceRegistration(path="netbeans-JPDASession/"+TruffleStrataProvider.TRUFFLE_STRATUM+"/DebuggingView",
-                             types={ TreeModelFilter.class })
+@DebuggerServiceRegistration(path="netbeans-JPDASession/DebuggingView",
+                             types={ TreeModelFilter.class }, position=25000)
 public class DebuggingTruffleTreeModel implements TreeModelFilter {
     
     private static final Predicate<String> PREDICATE1 = Pattern.compile("^((com|org)\\.\\p{Alpha}*\\.truffle|(com|org)(\\.graalvm|\\.truffleruby))\\..*$").asPredicate();
     private static final String FILTER1 = "com.[A-z]*.truffle.";                     // NOI18N
     private static final String FILTER2 = "com.oracle.graal.";                  // NOI18N
     private static final String FILTER3 = "org.netbeans.modules.debugger.jpda.backend.";    // NOI18N
-    
+
     private final JPDADebugger debugger;
     private final List<ModelListener> listeners = new ArrayList<>();
     private final PropertyChangeListener propListenerHolder;    // Not to have the listener collected
@@ -73,6 +74,7 @@
             }
         };
         TruffleOptions.onLanguageDeveloperModeChange(propListenerHolder);
+        DebuggingTruffleActionsProvider.onShowAllHostFramesChange(propListenerHolder);
     }
 
     @Override
@@ -83,12 +85,25 @@
     @Override
     public Object[] getChildren(TreeModel original, Object parent, int from, int to) throws UnknownTypeException {
         Object[] children = original.getChildren(parent, from, to);
-        if (parent instanceof DebuggingView.DVThread && children.length > 0) {
-            CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(((WeakCacheMap.KeyedValue<JPDAThread>) parent).getKey());
+        if (parent instanceof DebuggingView.DVThread && children.length > 0 &&
+                !DebuggingTruffleActionsProvider.isShowAllHostFrames((DebuggingView.DVThread) parent)) {
+
+            JPDAThread thread = ((WeakCacheMap.KeyedValue<JPDAThread>) parent).getKey();
+            CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentGuestPCInfo(thread);
+            boolean inGuest = isInGuest(children[0]);
+            boolean haveTopHostFrames = false;
+            if (currentPCInfo == null) {
+                currentPCInfo = inGuest ? TruffleAccess.getCurrentSuspendHereInfo(thread) : TruffleAccess.getSuspendHere(thread);
+                haveTopHostFrames = true;
+            }
             if (currentPCInfo != null) {
                 boolean showInternalFrames = TruffleOptions.isLanguageDeveloperMode();
                 TruffleStackFrame[] stackFrames = currentPCInfo.getStack().getStackFrames(showInternalFrames);
-                children = filterAndAppend(children, stackFrames, currentPCInfo.getTopFrame());
+                if (inGuest && !currentPCInfo.getStack().hasJavaFrames()) {
+                    children = filterAndAppend(children, stackFrames, currentPCInfo.getTopFrame());
+                } else {
+                    children = mergeFrames(children, stackFrames, currentPCInfo.getTopFrame(), haveTopHostFrames);
+                }
             }
         }
         return children;
@@ -122,18 +137,27 @@
         }
     }
 
+    private static boolean isInGuest(Object child) {
+        if (child instanceof CallStackFrame) {
+            CallStackFrame csf = (CallStackFrame) child;
+            return BASIC_CLASS_NAME.equals(csf.getClassName());
+        } else {
+            return false;
+        }
+    }
+
     private static Object[] filterAndAppend(Object[] children, TruffleStackFrame[] stackFrames,
-                                     TruffleStackFrame topFrame) {
+                                    TruffleStackFrame topFrame) {
         List<Object> newChildren = new ArrayList<>(children.length);
         //newChildren.addAll(Arrays.asList(children));
         for (Object ch : children) {
-            if (ch instanceof CallStackFrame) {
+                    if (ch instanceof CallStackFrame) {
                 String className = ((CallStackFrame) ch).getClassName();
                 if (PREDICATE1.test(className) ||
                     className.startsWith(FILTER2) ||
                     className.startsWith(FILTER3)) {
                     
-                    continue;
+                        continue;
                 }
             }
             newChildren.add(ch);
@@ -146,6 +170,61 @@
         return newChildren.toArray();
     }
 
+    private static TruffleStackFrame[] join(TruffleStackFrame[] stackFrames,
+                                            TruffleStackFrame topFrame) {
+        TruffleStackFrame[] joined = new TruffleStackFrame[stackFrames.length + 1];
+        joined[0] = topFrame;
+        System.arraycopy(stackFrames, 0, joined, 1, stackFrames.length);
+        return joined;
+    }
+
+    static Object[] mergeFrames(Object[] children, TruffleStackFrame[] stackFrames,
+                                TruffleStackFrame topFrame, boolean haveTopHostFrames) {
+        List<Object> newChildren = new ArrayList<>(children.length);
+        stackFrames = join(stackFrames, topFrame);
+        int chi = 0;
+        if (haveTopHostFrames) {
+            for (; chi < children.length; chi++) {
+                Object ch = children[chi];
+                if (ch instanceof CallStackFrame) {
+                    CallStackFrame csf = (CallStackFrame) ch;
+                    if (StepActionProvider.STEP2JAVA_CLASS.equals(csf.getClassName()) &&
+                            StepActionProvider.STEP2JAVA_METHOD.equals(csf.getMethodName())) {
+                        break;
+                    }
+                }
+                newChildren.add(ch);
+            }
+        }
+        for (TruffleStackFrame tframe : stackFrames) {
+            if (tframe.isHost()) {
+                for (; chi < children.length; chi++) {
+                    Object ch = children[chi];
+                    if (ch instanceof CallStackFrame) {
+                        CallStackFrame csf = (CallStackFrame) ch;
+                        String className = csf.getClassName();
+                        if (className.startsWith(FILTER3)) {
+                            continue;
+                        }
+                        if (equalFrames(csf, tframe)) {
+                            newChildren.add(csf);
+                            chi++;
+                            break;
+                        }
+                    } else {
+                        newChildren.add(ch);
+                    }
+                }
+            } else {
+                newChildren.add(tframe);
+            }
+        }
+        for (; chi < children.length; chi++) {
+            newChildren.add(children[chi]);
+        }
+        return newChildren.toArray();
+    }
+
     static List<DVFrame> filterAndAppend(JPDADVThread thread, List<DVFrame> children,
                                          TruffleStackFrame[] stackFrames,
                                          TruffleStackFrame topFrame) {
@@ -157,8 +236,8 @@
                     className.startsWith(FILTER2) ||
                     className.startsWith(FILTER3)) {
                     
-                    continue;
-                }
+                        continue;
+                    }
             }
             newChildren.add(ch);
         }
@@ -170,4 +249,58 @@
         return Collections.unmodifiableList(newChildren);
     }
 
+    static List<DVFrame> mergeFrames(JPDADVThread thread, List<DVFrame> children,
+                                     TruffleStackFrame[] stackFrames,
+                                     TruffleStackFrame topFrame, boolean haveTopHostFrames) {
+        List<DVFrame> newChildren = new ArrayList<>(children.size());
+        stackFrames = join(stackFrames, topFrame);
+        int chi = 0;
+        if (haveTopHostFrames) {
+            for (; chi < children.size(); chi++) {
+                DVFrame ch = children.get(chi);
+                CallStackFrame csf = ((JPDADVFrame) ch).getCallStackFrame();
+                if (StepActionProvider.STEP2JAVA_CLASS.equals(csf.getClassName()) &&
+                        StepActionProvider.STEP2JAVA_METHOD.equals(csf.getMethodName())) {
+                    break;
+                }
+                newChildren.add(ch);
+            }
+        }
+        for (TruffleStackFrame tframe : stackFrames) {
+            if (tframe.isHost()) {
+                for (; chi < children.size(); chi++) {
+                    DVFrame ch = children.get(chi);
+                    CallStackFrame csf = ((JPDADVFrame) ch).getCallStackFrame();
+                    String className = csf.getClassName();
+                    if (className.startsWith(FILTER3)) {
+                        continue;
+                    }
+                    if (equalFrames(csf, tframe)) {
+                        newChildren.add(ch);
+                        chi++;
+                        break;
+                    }
+                }
+            } else {
+                newChildren.add(new TruffleDVFrame(thread, tframe));
+            }
+        }
+        for (; chi < children.size(); chi++) {
+            newChildren.add(children.get(chi));
+        }
+        return Collections.unmodifiableList(newChildren);
+    }
+
+    private static boolean equalFrames(CallStackFrame csf, TruffleStackFrame tframe) {
+        if (!csf.getClassName().equals(tframe.getHostClassName())) {
+            return false;
+        }
+        if (!csf.getMethodName().equals(tframe.getHostMethodName())) {
+            return false;
+        }
+        int linej = csf.getLineNumber(null);
+        int linet = tframe.getSourcePosition().getStartLine();
+        return (linej == linet || linej == 0);
+    }
+    
 }
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingViewTruffleSupport.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingViewTruffleSupport.java
index b55c647..7391ae6 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingViewTruffleSupport.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/DebuggingViewTruffleSupport.java
@@ -21,11 +21,15 @@
 import java.util.Collections;
 import java.util.List;
 
+import org.netbeans.api.debugger.jpda.CallStackFrame;
+import org.netbeans.api.debugger.jpda.JPDAThread;
 import org.netbeans.modules.debugger.jpda.truffle.access.CurrentPCInfo;
 import org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess;
+import static org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess.BASIC_CLASS_NAME;
 import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
 import org.netbeans.modules.debugger.jpda.truffle.options.TruffleOptions;
 import org.netbeans.modules.debugger.jpda.ui.debugging.DebuggingViewSupportImpl;
+import org.netbeans.modules.debugger.jpda.ui.debugging.JPDADVFrame;
 import org.netbeans.modules.debugger.jpda.ui.debugging.JPDADVThread;
 import org.netbeans.spi.debugger.ContextProvider;
 import org.netbeans.spi.debugger.ui.DebuggingView;
@@ -55,11 +59,26 @@
     @Override
     protected List<DVFrame> getFrames(JPDADVThread thread, int from, int to) {
         List<DVFrame> frames = super.getFrames(thread, 0, Integer.MAX_VALUE);
-        CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(thread.getKey());
+        if (frames.isEmpty()) {
+            return frames;
+        }
+        JPDAThread jt = thread.getKey();
+        CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentGuestPCInfo(jt);
+        boolean inGuest = isInGuest(frames.get(0));
+        boolean haveTopHostFrames = false;
+        if (currentPCInfo == null) {
+            currentPCInfo = inGuest ? TruffleAccess.getCurrentSuspendHereInfo(jt) : TruffleAccess.getSuspendHere(jt);
+            haveTopHostFrames = true;
+        }
         if (currentPCInfo != null) {
             boolean showInternalFrames = TruffleOptions.isLanguageDeveloperMode();
             TruffleStackFrame[] stackFrames = currentPCInfo.getStack().getStackFrames(showInternalFrames);
-            frames = DebuggingTruffleTreeModel.filterAndAppend(thread, frames, stackFrames, currentPCInfo.getTopFrame());
+            
+            if (inGuest && !currentPCInfo.getStack().hasJavaFrames()) {
+                frames = DebuggingTruffleTreeModel.filterAndAppend(thread, frames, stackFrames, currentPCInfo.getTopFrame());
+            } else {
+                frames = DebuggingTruffleTreeModel.mergeFrames(thread, frames, stackFrames, currentPCInfo.getTopFrame(), haveTopHostFrames);
+            }
         }
         if (from >= frames.size()) {
             return Collections.emptyList();
@@ -68,6 +87,11 @@
         return frames.subList(from, to);
     }
 
+    private static boolean isInGuest(DVFrame child) {
+        CallStackFrame csf = ((JPDADVFrame) child).getCallStackFrame();
+        return BASIC_CLASS_NAME.equals(csf.getClassName());
+    }
+
     @Override
     public String getDisplayName(DVFrame frame) {
         if (frame instanceof TruffleDVFrame) {
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/TruffleDVFrame.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/TruffleDVFrame.java
index a194c63..15cf338 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/TruffleDVFrame.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/frames/models/TruffleDVFrame.java
@@ -19,9 +19,11 @@
 package org.netbeans.modules.debugger.jpda.truffle.frames.models;
 
 import java.net.URI;
+import java.net.URISyntaxException;
 import org.netbeans.modules.debugger.jpda.truffle.access.CurrentPCInfo;
 import org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess;
 import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
+import org.netbeans.modules.debugger.jpda.truffle.source.Source;
 import org.netbeans.spi.debugger.ui.DebuggingView.DVFrame;
 import org.netbeans.spi.debugger.ui.DebuggingView.DVThread;
 
@@ -63,7 +65,22 @@
 
     @Override
     public URI getSourceURI() {
-        return truffleFrame.getSourcePosition().getSource().getURI();
+        Source source = truffleFrame.getSourcePosition().getSource();
+        URI uri = source.getURI();
+        if (uri != null && "file".equalsIgnoreCase(uri.getScheme())) {
+            return uri;
+        }
+        try {
+            return source.getUrl().toURI();
+        } catch (URISyntaxException ex) {
+            return null;
+        }
+    }
+
+    @Override
+    public String getSourceMimeType() {
+        Source source = truffleFrame.getSourcePosition().getSource();
+        return source.getMimeType();
     }
 
     @Override
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/source/Source.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/source/Source.java
index 426642f..25289f2 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/source/Source.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/source/Source.java
@@ -49,33 +49,41 @@
 
     private final StringReference codeRef;
     private final String name;
+    private final String hostMethodName;
+    private final String path;
     private final URI uri;          // The original source URI
     private final URL url;          // The source
+    private final String mimeType;
     private final long hash;
     private String content;
     
-    private Source(JPDADebugger jpda, String name, URI uri, long hash, StringReference codeRef) {
+    private Source(JPDADebugger jpda, String name, String hostMethodName, String path, URI uri, String mimeType, long hash, StringReference codeRef) {
         this.name = name;
+        this.hostMethodName = hostMethodName;
+        this.path = path;
         this.codeRef = codeRef;
         URL url = null;
-        if (uri == null || !"file".equalsIgnoreCase(uri.getScheme())) {
-            try {
-                url = SourceFilesCache.get(jpda).getSourceFile(name, hash, uri, getContent());
-            } catch (IOException ex) {
-                Exceptions.printStackTrace(ex);
+        if (hostMethodName == null) {
+            if (uri == null || !"file".equalsIgnoreCase(uri.getScheme())) {
+                try {
+                    url = SourceFilesCache.get(jpda).getSourceFile(name, hash, uri, getContent());
+                } catch (IOException ex) {
+                    Exceptions.printStackTrace(ex);
+                }
+            } else {
+                uri = SourceBinaryTranslator.binary2Source(uri);
             }
-        } else {
-            uri = SourceBinaryTranslator.binary2Source(uri);
-        }
-        if (url == null) {
-            try {
-                url = uri.toURL();
-            } catch (MalformedURLException muex) {
-                Exceptions.printStackTrace(muex);
+            if (url == null) {
+                try {
+                    url = uri.toURL();
+                } catch (MalformedURLException muex) {
+                    Exceptions.printStackTrace(muex);
+                }
             }
         }
         this.url = url;
         this.uri = uri;
+        this.mimeType = mimeType;
         this.hash = hash;
     }
     
@@ -119,6 +127,25 @@
                                    String path,
                                    URI uri,
                                    StringReference codeRef) {
+        return getSource(debugger, id, name, path, uri, null, codeRef);
+    }
+
+    public static Source getSource(JPDADebugger debugger, long id,
+                                   String name,
+                                   String path,
+                                   URI uri,
+                                   String mimeType,
+                                   StringReference codeRef) {
+        return getSource(debugger, id, name, null, path, uri, null, codeRef);
+    }
+
+    public static Source getSource(JPDADebugger debugger, long id,
+                                   String name,
+                                   String hostMethodName,
+                                   String path,
+                                   URI uri,
+                                   String mimeType,
+                                   StringReference codeRef) {
         synchronized (KNOWN_SOURCES) {
             Map<Long, Source> dbgSources = KNOWN_SOURCES.get(debugger);
             if (dbgSources != null) {
@@ -128,23 +155,27 @@
                 }
             }
         }
-        return getTheSource(debugger, id, name, path, uri, codeRef);
+        return getTheSource(debugger, id, name, hostMethodName, path, uri, mimeType, codeRef);
     }
     
     private static Source getTheSource(JPDADebugger debugger, long id,
                                        String name,
+                                       String hostMethodName,
                                        String path,
                                        URI uri,
+                                       String mimeType,
                                        StringReference codeRef) {
         
-        Source src = new Source(debugger, name, uri, id, codeRef);
-        synchronized (KNOWN_SOURCES) {
-            Map<Long, Source> dbgSources = KNOWN_SOURCES.get(debugger);
-            if (dbgSources == null) {
-                dbgSources = new HashMap<>();
-                KNOWN_SOURCES.put(debugger, dbgSources);
+        Source src = new Source(debugger, name, hostMethodName, path, uri, mimeType, id, codeRef);
+        if (id >= 0) {
+            synchronized (KNOWN_SOURCES) {
+                Map<Long, Source> dbgSources = KNOWN_SOURCES.get(debugger);
+                if (dbgSources == null) {
+                    dbgSources = new HashMap<>();
+                    KNOWN_SOURCES.put(debugger, dbgSources);
+                }
+                dbgSources.put(id, src);
             }
-            dbgSources.put(id, src);
         }
         return src;
     }
@@ -153,6 +184,14 @@
         return name;
     }
 
+    public String getHostMethodName() {
+        return hostMethodName;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
     public URL getUrl() {
         return url;
     }
@@ -161,6 +200,10 @@
         return uri;
     }
     
+    public String getMimeType() {
+        return mimeType;
+    }
+    
     public long getHash() {
         return hash;
     }
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/impl/TruffleVariableImpl.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/impl/TruffleVariableImpl.java
index 163a3bd..0b9d202 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/impl/TruffleVariableImpl.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/impl/TruffleVariableImpl.java
@@ -29,6 +29,7 @@
 import org.netbeans.modules.debugger.jpda.truffle.LanguageName;
 import org.netbeans.modules.debugger.jpda.truffle.access.CurrentPCInfo;
 import org.netbeans.modules.debugger.jpda.truffle.access.TruffleAccess;
+import org.netbeans.modules.debugger.jpda.truffle.frames.TruffleStackFrame;
 import org.netbeans.modules.debugger.jpda.truffle.source.SourcePosition;
 import org.netbeans.modules.debugger.jpda.truffle.vars.TruffleVariable;
 import org.openide.util.Exceptions;
@@ -209,8 +210,9 @@
 
     static ObjectVariable setValue(JPDADebugger debugger, ObjectVariable guestObject, String newExpression) {
         CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(debugger.getCurrentThread());
-        if (currentPCInfo != null) {
-            ObjectVariable selectedFrame = currentPCInfo.getSelectedStackFrame().getStackFrameInstance();
+        TruffleStackFrame selectedStackFrame;
+        if (currentPCInfo != null && (selectedStackFrame = currentPCInfo.getSelectedStackFrame()) != null) {
+            ObjectVariable selectedFrame = selectedStackFrame.getStackFrameInstance();
             try {
                 Variable retVar = guestObject.invokeMethod(METHOD_SET_VALUE, METHOD_SET_VALUE_SIG,
                                                              new Variable[] { selectedFrame, debugger.createMirrorVar(newExpression) });
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleLocalVariablesTreeModel.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleLocalVariablesTreeModel.java
index 81c304d..bcdd633 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleLocalVariablesTreeModel.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleLocalVariablesTreeModel.java
@@ -52,7 +52,8 @@
     public Object[] getChildren(TreeModel original, Object parent, int from, int to) throws UnknownTypeException {
         if (parent == original.getRoot()) {
             CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(getDebugger().getCurrentThread());
-            if (currentPCInfo != null) {
+            TruffleStackFrame selectedStackFrame;
+            if (currentPCInfo != null && (selectedStackFrame = currentPCInfo.getSelectedStackFrame()) != null) {
                 synchronized (cpisListening) {
                     if (!cpisListening.contains(currentPCInfo)) {
                         currentPCInfo.addPropertyChangeListener(
@@ -60,7 +61,6 @@
                         cpisListening.add(currentPCInfo);
                     }
                 }
-                TruffleStackFrame selectedStackFrame = currentPCInfo.getSelectedStackFrame();
                 TruffleScope[] scopes = selectedStackFrame.getScopes();
                 if (scopes.length == 0) {
                     return new Object[] {};
diff --git a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleVariablesTableModel.java b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleVariablesTableModel.java
index d776869..15eb018 100644
--- a/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleVariablesTableModel.java
+++ b/java/debugger.jpda.truffle/src/org/netbeans/modules/debugger/jpda/truffle/vars/models/TruffleVariablesTableModel.java
@@ -153,7 +153,7 @@
                         CurrentPCInfo currentPCInfo = TruffleAccess.getCurrentPCInfo(debugger.getCurrentThread());
                         if (currentPCInfo != null) {
                             TruffleStackFrame selectedStackFrame = currentPCInfo.getSelectedStackFrame();
-                            frameLanguage = selectedStackFrame.getLanguage();
+                            frameLanguage = selectedStackFrame != null ? selectedStackFrame.getLanguage() : LanguageName.NONE;
                         }
                         LanguageName valueLanguage = tv.getLanguage();
                         if (!LanguageName.NONE.equals(valueLanguage) && !frameLanguage.equals(valueLanguage)) {
diff --git a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/DebuggerVisualizer.java b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/DebuggerVisualizer.java
index 47c2b05..6d148e4 100644
--- a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/DebuggerVisualizer.java
+++ b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/DebuggerVisualizer.java
@@ -21,9 +21,12 @@
 
 import com.oracle.truffle.api.CallTarget;
 import com.oracle.truffle.api.RootCallTarget;
+import com.oracle.truffle.api.debug.DebugStackFrame;
 import com.oracle.truffle.api.nodes.RootNode;
 import com.oracle.truffle.api.source.SourceSection;
 
+import java.lang.reflect.Method;
+
 /**
  *
  * @author Martin
@@ -47,12 +50,24 @@
     }
     
     /** &lt;File name&gt;:&lt;line number&gt; */
-    static String getSourceLocation(SourceSection ss) {
-        if (ss == null) {
-            //System.err.println("No source section for node "+n);
-            return "unknown";
+    static String getSourceLocation(DebugStackFrame sf, boolean isHost) {
+        if (!isHost) {
+            SourceSection ss = sf.getSourceSection();
+            if (ss == null) {
+                //System.err.println("No source section for node "+n);
+                return "unknown";
+            }
+            return ss.getSource().getName() + ":" + ss.getStartLine();
+        } else {
+            try {
+                Method getHostTraceElementMethod = DebugStackFrame.class.getMethod("getHostTraceElement");
+                StackTraceElement ste = (StackTraceElement) getHostTraceElementMethod.invoke(sf);
+                return ste.getFileName() + ":" + ste.getLineNumber();
+            } catch (Exception ex) {
+                LangErrors.exception("getHostTraceElement", ex);
+                return null;
+            }
         }
-        return ss.getSource().getName() + ":" + ss.getStartLine();
     }
 
 }
diff --git a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/FrameInfo.java b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/FrameInfo.java
index 61c7167..03c4e41 100644
--- a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/FrameInfo.java
+++ b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/FrameInfo.java
@@ -33,15 +33,16 @@
  * @author martin
  */
 final class FrameInfo {
+
     final DebugStackFrame frame;  // the top frame instance
     final DebugStackFrame[] stackTrace; // All but the top frame
     final String topFrame;
     final Object[] topVariables;
     // TODO: final Object[] thisObjects;
 
-    FrameInfo(DebugStackFrame topStackFrame, Iterable<DebugStackFrame> stackFrames) {
+    FrameInfo(DebugStackFrame topStackFrame, Iterable<DebugStackFrame> stackFrames, boolean supportsJavaFrames) {
         SourceSection topSS = topStackFrame.getSourceSection();
-        SourcePosition position = new SourcePosition(topSS);
+        SourcePosition position = new SourcePosition(topSS, topStackFrame.getLanguage());
         ArrayList<DebugStackFrame> stackFramesArray = new ArrayList<>();
         for (DebugStackFrame sf : stackFrames) {
             if (sf == topStackFrame) {
@@ -49,7 +50,8 @@
             }
             SourceSection ss = sf.getSourceSection();
             // Ignore frames without sources:
-            if (ss == null || ss.getSource() == null) {
+            boolean isHost = supportsJavaFrames && isHost(sf);
+            if (!isHost && (ss == null || ss.getSource() == null)) {
                 continue;
             }
             stackFramesArray.add(sf);
@@ -57,12 +59,14 @@
         frame = topStackFrame;
         stackTrace = stackFramesArray.toArray(new DebugStackFrame[stackFramesArray.size()]);
         LanguageInfo sfLang = topStackFrame.getLanguage();
-        topFrame = topStackFrame.getName() + "\n" +
+        boolean isHost = supportsJavaFrames && isHost(topStackFrame);
+        topFrame = topStackFrame.getName() + "\n" + isHost + "\n" +
                    ((sfLang != null) ? sfLang.getId() + " " + sfLang.getName() : "") + "\n" +
-                   DebuggerVisualizer.getSourceLocation(topSS) + "\n" +
+                   DebuggerVisualizer.getSourceLocation(topStackFrame, isHost) + "\n" +
                    position.id + "\n" + position.name + "\n" + position.path + "\n" +
-                   position.uri.toString() + "\n" + position.sourceSection +/* "," + position.startColumn + "," +
-                   position.endLine + "," + position.endColumn +*/ "\n" + isInternal(topStackFrame);
+                   position.hostClassName + "\n" + position.hostMethodName + "\n" +
+                   position.uri.toString() + "\n" + position.mimeType + "\n" + position.sourceSection + "\n" +
+                   isInternal(topStackFrame);
         topVariables = JPDATruffleAccessor.getVariables(topStackFrame);
     }
     
@@ -90,4 +94,21 @@
         return isInternal;
     }
     
+    static boolean isHost(DebugStackFrame sf) {
+        try {
+            Method isHostMethod = DebugStackFrame.class.getMethod("isHost");
+            return (Boolean) isHostMethod.invoke(sf);
+        } catch (Exception ex) {
+            return false;
+        }
+    }
+
+    static StackTraceElement getHostTraceElement(DebugStackFrame sf) {
+        try {
+            Method getHostTraceElementMethod = DebugStackFrame.class.getMethod("getHostTraceElement");
+            return (StackTraceElement) getHostTraceElementMethod.invoke(sf);
+        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
 }
diff --git a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/GuestObject.java b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/GuestObject.java
index 8b159a1..dedb852 100644
--- a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/GuestObject.java
+++ b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/GuestObject.java
@@ -145,7 +145,7 @@
             SourceSection sourceLocation = value.getSourceLocation();
             //System.err.println("\nSOURCE of "+value.getName()+" is: "+sourceLocation);
             if (sourceLocation != null && sourceLocation.isAvailable()) {
-                sp = new SourcePosition(sourceLocation);
+                sp = new SourcePosition(sourceLocation, value.getOriginalLanguage());
             }
         } catch (ThreadDeath td) {
             throw td;
@@ -159,7 +159,7 @@
                 SourceSection sourceLocation = metaObject.getSourceLocation();
                 //System.err.println("\nSOURCE of metaobject "+metaObject+" is: "+sourceLocation);
                 if (sourceLocation != null && sourceLocation.isAvailable()) {
-                    sp = new SourcePosition(sourceLocation);
+                    sp = new SourcePosition(sourceLocation, value.getOriginalLanguage());
                 }
             } catch (ThreadDeath td) {
                 throw td;
diff --git a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleAccessor.java b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleAccessor.java
index 2de2dd0..134d880 100644
--- a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleAccessor.java
+++ b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleAccessor.java
@@ -155,6 +155,7 @@
                                boolean haltedBefore,
                                DebugValue returnValue,
                                FrameInfo frameInfo,
+                               boolean supportsJavaFrames,
                                Breakpoint[] breakpointsHit,
                                Throwable[] breakpointConditionExceptions,
                                int stepCmd) {
@@ -193,11 +194,31 @@
     }
     
     /**
+     * Tries to suspend immediately at the current location of the current execution thread.
+     * When called from a suspension in a Java code, it reveals the guest code execution.
+     *
+     * @return the halted info, the array corresponds to the arguments of
+     * {@link #executionHalted(JPDATruffleDebugManager, SourcePosition, boolean, DebugValue, FrameInfo, boolean, Breakpoint[], Throwable[], int)},
+     * or <code>null</code>, when the suspend wasn't successful.
+     */
+    static Object[] suspendHere() {
+        synchronized (debugManagers) {
+            for (JPDATruffleDebugManager tdm : debugManagers.values()) {
+                Object[] haltedInfo = tdm.suspendHere();
+                if (haltedInfo != null) {
+                    return haltedInfo;
+                }
+            }
+        }
+        return null;
+    }
+    
+    /**
      * @param frames The array of stack frame infos
      * @return An array of two elements: a String of frame information and
      * an array of code contents.
      */
-    static Object[] getFramesInfo(DebugStackFrame[] frames, boolean includeInternal) {
+    static Object[] getFramesInfo(DebugStackFrame[] frames, boolean includeInternal, boolean supportsJavaFrames) {
         trace("getFramesInfo({0})",includeInternal);
         int n = frames.length;
         StringBuilder frameInfos = new StringBuilder();
@@ -211,17 +232,20 @@
             if (!includeInternal && isInternal) {
                 continue;
             }
+            boolean isHost = supportsJavaFrames && FrameInfo.isHost(sf);
             String sfName = sf.getName();
             if (sfName == null) {
                 sfName = "";
             }
             frameInfos.append(sfName);
             frameInfos.append('\n');
+            frameInfos.append(isHost);
+            frameInfos.append('\n');
             LanguageInfo sfLang = sf.getLanguage();
             String sfLangId = (sfLang != null) ? sfLang.getId() + " " + sfLang.getName() : "";
             frameInfos.append(sfLangId);
             frameInfos.append('\n');
-            frameInfos.append(DebuggerVisualizer.getSourceLocation(sf.getSourceSection()));
+            frameInfos.append(DebuggerVisualizer.getSourceLocation(sf, isHost));
             frameInfos.append('\n');
             /*if (fi.getCallNode() == null) {
                 /* frames with null call nodes are filtered out by JPDATruffleDebugManager.FrameInfo
@@ -231,14 +255,20 @@
                 System.err.println("frameInfos = "+frameInfos);
                 *//*
             }*/
-            SourcePosition position = new SourcePosition(sf.getSourceSection());
+            SourcePosition position;
+            if (isHost) {
+                StackTraceElement ste = FrameInfo.getHostTraceElement(sf);
+                position = new SourcePosition(ste);
+            } else {
+                position = new SourcePosition(sf.getSourceSection(), sf.getLanguage());
+            }
             frameInfos.append(createPositionIdentificationString(position));
             if (includeInternal) {
                 frameInfos.append('\n');
                 frameInfos.append(isInternal);
             }
             
-            frameInfos.append("\n\n");
+            frameInfos.append("\n\t\n");
             
             codes[j] = position.code;
             j++;
@@ -259,8 +289,14 @@
         str.append('\n');
         str.append(position.path);
         str.append('\n');
+        str.append(position.hostClassName);
+        str.append('\n');
+        str.append(position.hostMethodName);
+        str.append('\n');
         str.append(position.uri.toString());
         str.append('\n');
+        str.append(position.mimeType);
+        str.append('\n');
         str.append(position.sourceSection);
         return str.toString();
     }
diff --git a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleDebugManager.java b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleDebugManager.java
index f3fd297..36b4a06 100644
--- a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleDebugManager.java
+++ b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/JPDATruffleDebugManager.java
@@ -26,9 +26,12 @@
 import com.oracle.truffle.api.debug.SuspendedCallback;
 import com.oracle.truffle.api.debug.SuspendedEvent;
 import com.oracle.truffle.api.debug.SuspensionFilter;
+import com.oracle.truffle.api.nodes.Node;
 
 import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 
 /**
  *
@@ -39,17 +42,35 @@
     private final Reference<Debugger> debugger;
     private final Reference<DebuggerSession> session;
     private final ThreadLocal<SuspendedEvent> suspendedEvents = new ThreadLocal<>();
+    private final ThreadLocal<Object[]> suspendHere = new ThreadLocal<>();
+    private final boolean supportsJavaFrames;
 
     public JPDATruffleDebugManager(Debugger debugger, boolean includeInternal, boolean doStepInto) {
         this.debugger = new WeakReference<>(debugger);
         DebuggerSession debuggerSession = debugger.startSession(this);
         debuggerSession.setSteppingFilter(createSteppingFilter(includeInternal));
+        supportsJavaFrames = supportsJavaFrames(debuggerSession);
         if (doStepInto) {
             debuggerSession.suspendNextExecution();
         }
         this.session = new WeakReference<>(debuggerSession);
     }
 
+    private static boolean supportsJavaFrames(DebuggerSession debuggerSession) {
+        try {
+            Method setShowHostStackFramesMethod = DebuggerSession.class.getMethod("setShowHostStackFrames", Boolean.TYPE);
+            try {
+                setShowHostStackFramesMethod.invoke(debuggerSession, true);
+                return true;
+            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+                LangErrors.exception("setShowHostStackFrames", ex);
+                return false;
+            }
+        } catch (NoSuchMethodException | SecurityException ex) {
+            return false;
+        }
+    }
+
     static SuspensionFilter createSteppingFilter(boolean includeInternal) {
         return SuspensionFilter.newBuilder().ignoreLanguageContextInitialization(true).includeInternal(includeInternal).build();
     }
@@ -99,6 +120,19 @@
     @Override
     public void onSuspend(SuspendedEvent event) {
         JPDATruffleAccessor.trace("JPDATruffleDebugManager.onSuspend({0})", event);
+        if (suspendHere.get() != null) {
+            // A special 'suspendHere':
+            SourcePosition position = new SourcePosition(event.getSourceSection(), event.getTopStackFrame().getLanguage());
+            Object[] haltInfo = new Object[]{
+                this, position,
+                event.getSuspendAnchor() == SuspendAnchor.BEFORE,
+                event.getReturnValue(),
+                new FrameInfo(event.getTopStackFrame(), event.getStackFrames(), supportsJavaFrames),
+                supportsJavaFrames
+            };
+            suspendHere.set(haltInfo);
+            return ;
+        }
         Breakpoint[] breakpointsHit = new Breakpoint[event.getBreakpoints().size()];
         breakpointsHit = event.getBreakpoints().toArray(breakpointsHit);
         Throwable[] breakpointConditionExceptions = new Throwable[breakpointsHit.length];
@@ -107,12 +141,13 @@
         }
         suspendedEvents.set(event);
         try {
-            SourcePosition position = new SourcePosition(event.getSourceSection());
+            SourcePosition position = new SourcePosition(event.getSourceSection(), event.getTopStackFrame().getLanguage());
             int stepCmd = JPDATruffleAccessor.executionHalted(
                     this, position,
                     event.getSuspendAnchor() == SuspendAnchor.BEFORE,
                     event.getReturnValue(),
-                    new FrameInfo(event.getTopStackFrame(), event.getStackFrames()),
+                    new FrameInfo(event.getTopStackFrame(), event.getStackFrames(), supportsJavaFrames),
+                    supportsJavaFrames,
                     breakpointsHit,
                     breakpointConditionExceptions,
                     0);
@@ -133,5 +168,28 @@
             suspendedEvents.remove();
         }
     }
+
+    Object[] suspendHere() {
+        DebuggerSession debuggerSession = session.get();
+        if (debuggerSession == null) {
+            return null;
+        }
+        try {
+            Method suspendHereMethod = DebuggerSession.class.getMethod("suspendHere", Node.class);
+            try {
+                suspendHere.set(new Object[]{});
+                boolean success = (Boolean) suspendHereMethod.invoke(debuggerSession, new Object[]{null});
+                if (success) {
+                    return suspendHere.get();
+                }
+            } finally {
+                suspendHere.remove();
+            }
+        } catch (NoSuchMethodException ex) {
+        } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+            LangErrors.exception("suspendHere", ex);
+        }
+        return null;
+    }
     
 }
diff --git a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/SourcePosition.java b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/SourcePosition.java
index d4321b0..1e93a19 100644
--- a/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/SourcePosition.java
+++ b/java/debugger.jpda.truffle/truffle-backend/org/netbeans/modules/debugger/jpda/backend/truffle/SourcePosition.java
@@ -19,6 +19,7 @@
 
 package org.netbeans.modules.debugger.jpda.backend.truffle;
 
+import com.oracle.truffle.api.nodes.LanguageInfo;
 import com.oracle.truffle.api.source.Source;
 import com.oracle.truffle.api.source.SourceSection;
 
@@ -37,15 +38,20 @@
 
     final long id;
     final String name;
+    final String hostClassName;
+    final String hostMethodName;
     final String path;
     final String sourceSection;
     final String code;
     final URI uri;
+    final String mimeType;
 
-    public SourcePosition(SourceSection sourceSection) {
+    public SourcePosition(SourceSection sourceSection, LanguageInfo languageInfo) {
         Source source = sourceSection.getSource();
         this.id = getId(source);
         this.name = source.getName();
+        this.hostClassName = null;
+        this.hostMethodName = null;
         String sourcePath = source.getPath();
         if (sourcePath == null) {
             sourcePath = name;
@@ -54,6 +60,27 @@
         this.sourceSection = sourceSection.getStartLine() + "," + sourceSection.getStartColumn() + "," + sourceSection.getEndLine() + "," + sourceSection.getEndColumn();
         this.code = source.getCharacters().toString();
         this.uri = source.getURI();
+        this.mimeType = findMIMEType(source, languageInfo);
+    }
+
+    public SourcePosition(StackTraceElement ste) {
+        this.id = -1;
+        this.name = ste.getFileName() != null ? ste.getFileName() : ste.getClassName();
+        this.hostClassName = ste.getClassName();
+        this.hostMethodName = ste.getMethodName();
+        this.path = ste.getFileName();
+        this.sourceSection = ste.getLineNumber() + "," + 0 + "," + ste.getLineNumber() + "," + 0;
+        this.code = null;
+        this.uri = URI.create("");
+        this.mimeType = null;
+    }
+
+    private String findMIMEType(Source source, LanguageInfo languageInfo) {
+        String mimeType = source.getMimeType();
+        if (mimeType == null && languageInfo != null) {
+            mimeType = languageInfo.getDefaultMimeType();
+        }
+        return mimeType;
     }
 
     private static synchronized long getId(Source s) {
diff --git a/java/debugger.jpda/nbproject/project.properties b/java/debugger.jpda/nbproject/project.properties
index e94b671..6e859b4 100644
--- a/java/debugger.jpda/nbproject/project.properties
+++ b/java/debugger.jpda/nbproject/project.properties
@@ -22,7 +22,7 @@
 javac.source=1.8
 javadoc.arch=${basedir}/arch.xml
 requires.nb.javac=true
-spec.version.base=1.117.0
+spec.version.base=1.117.1
 test-unit-sys-prop.test.dir.src=${basedir}/test/unit/src/
 test-unit-sys-prop.netbeans.user=${basedir}/work/nb_user_dir
 test.unit.cp.extra=../java.source.nbjavac/build/test-nb-javac/cluster/modules/org-netbeans-modules-java-source-nbjavac-test.jar
diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/JPDADebuggerImpl.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/JPDADebuggerImpl.java
index 0444242..7dc72e9 100644
--- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/JPDADebuggerImpl.java
+++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/JPDADebuggerImpl.java
@@ -173,7 +173,6 @@
     private AbstractDICookie            attachingCookie;
     private JavaEngineProvider          javaEngineProvider;
     private Set<String>                 languages;
-    private String                      lastStratumn;
     private ContextProvider             lookupProvider;
     private ObjectTranslation           threadsTranslation;
     private ObjectTranslation           localsTranslation;
@@ -2387,12 +2386,9 @@
                 languages.add (language);
             }
         } // for
-        if ( (stratum != null) &&
-             (!stratum.equals (lastStratumn))
-        ) {
+        if (stratum != null) {
             javaEngineProvider.getSession ().setCurrentLanguage (stratum);
         }
-        lastStratumn = stratum;
     }
 
     private Set<JSR45DebuggerEngineProvider> jsr45EngineProviders;
diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/expr/InvocationExceptionTranslated.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/expr/InvocationExceptionTranslated.java
index d0f824d..5610df2 100644
--- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/expr/InvocationExceptionTranslated.java
+++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/expr/InvocationExceptionTranslated.java
@@ -95,6 +95,10 @@
         this.createdAt = new Throwable().fillInStackTrace();
     }
     
+    public void resetInvocationMessage() {
+        this.invocationMessage = null;
+    }
+
     public void setPreferredThread(JPDAThreadImpl preferredThread) {
         this.preferredThread = preferredThread;
     }
diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/util/Operator.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/util/Operator.java
index c5cff2b..1b74b1c 100644
--- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/util/Operator.java
+++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/util/Operator.java
@@ -27,6 +27,7 @@
 import com.sun.jdi.request.StepRequest;
 import com.sun.jdi.ThreadReference;
 import java.beans.PropertyChangeEvent;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -37,6 +38,7 @@
 import java.util.WeakHashMap;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReadWriteLock;
+import java.util.function.Function;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import org.netbeans.api.debugger.DebuggerManager;
@@ -107,6 +109,7 @@
     private final List<EventSet> parallelEvents = new LinkedList<EventSet>();
     private boolean           haveParallelEventsToProcess = false;
     private final Map<EventSet, Set<ThreadReference>> threadsResumedForEvents = new WeakHashMap<EventSet, Set<ThreadReference>>();
+    private final List<Function<EventSet, Boolean>> eventsInterceptors = new LinkedList<>();
     private final LoopControl loopControl;
 
     /**
@@ -262,7 +265,22 @@
     }
 
     private boolean processEvents(EventSet eventSet, Executor starter,
-                                  SuspendControllersSupport scs, SuspendCount suspendCount) throws InternalExceptionWrapper, VMDisconnectedExceptionWrapper, ObjectCollectedExceptionWrapper, IllegalThreadStateExceptionWrapper {                 
+                                  SuspendControllersSupport scs, SuspendCount suspendCount) throws InternalExceptionWrapper, VMDisconnectedExceptionWrapper, ObjectCollectedExceptionWrapper, IllegalThreadStateExceptionWrapper {
+        List<Function<EventSet, Boolean>> interceptors;
+        synchronized (eventsInterceptors) {
+            if (eventsInterceptors.isEmpty()) {
+                interceptors = null;
+            } else {
+                interceptors = new ArrayList<>(eventsInterceptors);
+            }
+        }
+        if (interceptors != null) {
+            for (Function<EventSet, Boolean> interceptor : interceptors) {
+                if (interceptor.apply(eventSet)) {
+                    return true;
+                }
+            }
+        }
         boolean silent = eventSet.size() > 0;
         for (Event e: eventSet) {
             EventRequest r = EventWrapper.request(e);
@@ -751,6 +769,28 @@
     }
 
     /**
+     * Add an interceptor for JDWP events. Use when absolutely necessary only,
+     * for instance when events that are crucial to process come during an ongoing
+     * method invocation.
+     * @param eventsInterceptor a function that gets the event and returns <code>true</code>
+     * when it intercepted and processed the event.
+     */
+    public void addEventInterceptor(Function<EventSet, Boolean> eventsInterceptor) {
+        synchronized (eventsInterceptors) {
+            eventsInterceptors.add(eventsInterceptor);
+        }
+    }
+
+    /**
+     * Remove an interceptor for JDWP events.
+     */
+    public void removeEventInterceptor(Function<EventSet, Boolean> eventsInterceptor) {
+        synchronized (eventsInterceptors) {
+            eventsInterceptors.remove(eventsInterceptor);
+        }
+    }
+
+    /**
      * Stop the operator thread.
      */
     public void stop() {
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 f50ccdb..0d83bbf 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
@@ -201,7 +201,7 @@
                     Context resolved = resolveContext(controller, doc, offset, goToSource, false);
 
                     if (resolved == null) {
-                        target[0] = new GoToTarget(-1, -1, null, null, null, false);
+                        target[0] = new GoToTarget(-1, -1, null, null, null, null, false);
                         return;
                     }
                     
@@ -210,7 +210,7 @@
                         if (url != null) {
                             HtmlBrowser.URLDisplayer.getDefault().showURL(url);
                         } else {
-                            target[0] = new GoToTarget(-1, -1, null, null, null, false);
+                            target[0] = new GoToTarget(-1, -1, null, null, null, null, false);
                         }
                     } else {
                         target[0] = computeGoToTarget(controller, resolved, offset);
@@ -255,7 +255,7 @@
             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, -1, null, null, null, false);
+                    return new GoToTarget(-1, -1, null, null, null, null, false);
                 } else {
                     long endPos = controller.getTrees().getSourcePositions().getEndPosition(controller.getCompilationUnit(), tree);
                     //#71272: it is necessary to translate the offset:
@@ -263,17 +263,20 @@
                                           controller.getSnapshot().getOriginalOffset((int) endPos),
                                           null,
                                           null,
+                                          null,
                                           controller.getElementUtilities().getElementName(resolved.resolved, false).toString(),
                                           true);
                 }
             } else {
-                return new GoToTarget(-1, -1, null, null, null, false);
+                return new GoToTarget(-1, -1, null, null, null, null, false);
             }
         } else {
+            TypeElement te = resolved.resolved != null ? controller.getElementUtilities().outermostTypeElement(resolved.resolved) : null;
             return new GoToTarget(-1,
                                   -1,
                                   controller.getClasspathInfo(),
                                   ElementHandle.create(resolved.resolved),
+                                  te != null ? te.getQualifiedName().toString().replace('.', '/') + ".class" : null,
                                   controller.getElementUtilities().getElementName(resolved.resolved, false).toString(),
                                   true);
         }
@@ -284,14 +287,16 @@
         public final int endPos;
         public final ClasspathInfo cpInfo;
         public final ElementHandle elementToOpen;
+        public final String resourceName;
         public final String displayNameForError;
         public final boolean success;
 
-        public GoToTarget(int offsetToOpen, int endPos, ClasspathInfo cpInfo, ElementHandle elementToOpen, String displayNameForError, boolean success) {
+        public GoToTarget(int offsetToOpen, int endPos, ClasspathInfo cpInfo, ElementHandle elementToOpen, String resourceName, String displayNameForError, boolean success) {
             this.offsetToOpen = offsetToOpen;
             this.endPos = endPos;
             this.cpInfo = cpInfo;
             this.elementToOpen = elementToOpen;
+            this.resourceName = resourceName;
             this.displayNameForError = displayNameForError;
             this.success = success;
         }
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/DelegateMethodGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/DelegateMethodGenerator.java
index c4b3277..6b95763 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/DelegateMethodGenerator.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/DelegateMethodGenerator.java
@@ -299,7 +299,7 @@
         return ElementNode.Description.create(descriptions);
     }
     
-    static void generateDelegatingMethods(WorkingCopy wc, TreePath path, VariableElement delegate, Iterable<? extends ExecutableElement> methods, int offset) {
+    public static void generateDelegatingMethods(WorkingCopy wc, TreePath path, VariableElement delegate, Iterable<? extends ExecutableElement> methods, int offset) {
         assert TreeUtilities.CLASS_TREE_KINDS.contains(path.getLeaf().getKind());
         TypeElement te = (TypeElement)wc.getTrees().getElement(path);
         if (te != null) {
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java
index 08ebbac..8795d63 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java
@@ -393,8 +393,8 @@
 
         generateEqualsAndHashCode(wc, path, e, h, -1);
     }
-    
-    static void generateEqualsAndHashCode(WorkingCopy wc, TreePath path, Iterable<? extends VariableElement> equalsFields, Iterable<? extends VariableElement> hashCodeFields, int offset) {
+
+    public static void generateEqualsAndHashCode(WorkingCopy wc, TreePath path, Iterable<? extends VariableElement> equalsFields, Iterable<? extends VariableElement> hashCodeFields, int offset) {
         assert TreeUtilities.CLASS_TREE_KINDS.contains(path.getLeaf().getKind());
         TypeElement te = (TypeElement)wc.getTrees().getElement(path);
         if (te != null) {
@@ -419,7 +419,7 @@
                 members.add(createEqualsMethod(wc, equalsFields, dt, scope));
             }
             wc.rewrite(nue, GeneratorUtils.insertClassMembers(wc, nue, members, offset));
-        }        
+        }
     }
 
     private static MethodTree createEqualsMethod(WorkingCopy wc, Iterable<? extends VariableElement> equalsFields, DeclaredType type, Scope scope) {
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/LoggerGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/LoggerGenerator.java
index bf91dcd..7b11f99 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/LoggerGenerator.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/LoggerGenerator.java
@@ -152,7 +152,7 @@
         }
     }
 
-    private static VariableTree createLoggerField(TreeMaker make, ClassTree cls, CharSequence name, Set<Modifier> mods) {
+    public static VariableTree createLoggerField(TreeMaker make, ClassTree cls, CharSequence name, Set<Modifier> mods) {
         ModifiersTree modifiers = make.Modifiers(mods, Collections.<AnnotationTree>emptyList());
         final List<ExpressionTree> none = Collections.<ExpressionTree>emptyList();
         IdentifierTree className = make.Identifier(cls.getSimpleName());
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java
index e0ccca1..c855d3c 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java
@@ -209,7 +209,7 @@
         }
     }
 
-    private static MethodTree createToStringMethod(WorkingCopy wc, Iterable<? extends VariableElement> fields, String typeName, boolean useStringBuilder) {
+    public static MethodTree createToStringMethod(WorkingCopy wc, Iterable<? extends VariableElement> fields, String typeName, boolean useStringBuilder) {
         TreeMaker make = wc.getTreeMaker();
         Set<Modifier> mods = EnumSet.of(Modifier.PUBLIC);
         List<AnnotationTree> annotations = new LinkedList<>();
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateClassFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateClassFix.java
index 86e7245..fe031d7 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateClassFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateClassFix.java
@@ -21,6 +21,7 @@
 import com.sun.source.tree.ClassTree;
 import com.sun.source.tree.CompilationUnitTree;
 import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.ImportTree;
 import com.sun.source.tree.MethodTree;
 import com.sun.source.tree.ModifiersTree;
 import com.sun.source.tree.Tree;
@@ -57,7 +58,6 @@
 import org.netbeans.modules.java.hints.infrastructure.ErrorHintsProvider;
 import org.netbeans.spi.editor.hints.ChangeInfo;
 import org.netbeans.spi.editor.hints.EnhancedFix;
-import org.netbeans.spi.editor.hints.Fix;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileUtil;
 import org.openide.loaders.DataFolder;
@@ -68,18 +68,20 @@
  *
  * @author Jan lahoda
  */
-public abstract class CreateClassFix implements EnhancedFix {
+public abstract class CreateClassFix extends CreateFixBase implements EnhancedFix {
     
+    protected ClasspathInfo cpInfo;
     protected Set<Modifier> modifiers;
     protected List<TypeMirrorHandle> argumentTypes; //if a specific constructor should be created
     protected List<String> argumentNames; //dtto.
     private Integer prio;
-    private List<TypeMirrorHandle> superTypes;
+    protected List<TypeMirrorHandle> superTypes;
     protected ElementKind kind;
-    private int numTypeParameters;
+    protected int numTypeParameters;
     protected List<? extends TypeMirror> argumentTypeMirrors;
     
     public CreateClassFix(CompilationInfo info, Set<Modifier> modifiers, List<? extends TypeMirror> argumentTypes, List<String> argumentNames, TypeMirror superType, ElementKind kind, int numTypeParameters) {
+        this.cpInfo = info.getClasspathInfo();
         this.modifiers = modifiers;
         
         if (argumentTypes != null && argumentNames != null) {
@@ -267,6 +269,7 @@
             this.rootName = rootName;
         }
 
+        @Override
         public String getText() {
             if (argumentNames == null || argumentNames.isEmpty()) {
                 return NbBundle.getMessage(CreateClassFix.class, "FIX_CreateClassInPackage", simpleName, packageName, valueForBundle(kind), rootName);
@@ -292,6 +295,85 @@
             }
         }
 
+        @Override
+        public ModificationResult getModificationResult() throws IOException {
+            //use the original cp-info so it is "sure" that the target can be resolved:
+            JavaSource js = JavaSource.create(cpInfo);
+            return js.runModificationTask(new Task<WorkingCopy>() {
+                public void run(final WorkingCopy working) throws IOException {
+                    working.toPhase(Phase.RESOLVED);
+                    TreeMaker make = working.getTreeMaker();
+                    List<Tree> members = new ArrayList<>();
+                    if (argumentNames != null) {
+                        List<VariableTree>         argTypes = new ArrayList<VariableTree>();
+                        Iterator<TypeMirrorHandle> typeIt   = argumentTypes.iterator();
+                        Iterator<String>           nameIt   = argumentNames.iterator();
+                        while (typeIt.hasNext() && nameIt.hasNext()) {
+                            TypeMirrorHandle tmh = typeIt.next();
+                            String           argName = nameIt.next();
+                            argTypes.add(make.Variable(make.Modifiers(EnumSet.noneOf(Modifier.class)), argName, make.Type(tmh.resolve(working)), null));
+                        }
+                        members.add(make.Method(make.Modifiers(EnumSet.of(Modifier.PUBLIC/*!!!*/)), "<init>", null, Collections.<TypeParameterTree>emptyList(), argTypes, Collections.<ExpressionTree>emptyList(), "{}" /*XXX*/, null)); // NOI18N
+                    }
+                    Tree extendsClause = null;
+                    List<Tree> implementsClause = Collections.<Tree>emptyList();
+                    if (superTypes != null) {
+                        DeclaredType extendsType = null;
+                        List<DeclaredType> implementsTypes = new LinkedList<DeclaredType>();
+                        for (TypeMirrorHandle h : superTypes) {
+                            TypeMirror tm = h.resolve(working);
+                            if (tm == null) {
+                                continue;
+                            }
+                            if (tm.getKind() != TypeKind.DECLARED) {
+                                continue;
+                            }
+                            DeclaredType dt = (DeclaredType) tm;
+                            if (dt.asElement().getKind().isClass()) {
+                                extendsType = dt;
+                            } else {
+                                implementsTypes.add(dt);
+                            }
+                        }
+                        if (extendsType != null && !"java.lang.Object".equals(((TypeElement) extendsType.asElement()).getQualifiedName().toString())) { // NOI18N
+                            extendsClause = make.Type(extendsType);
+                        }
+                        if (!implementsTypes.isEmpty()) {
+                            implementsClause = new LinkedList<Tree>();
+                            for (DeclaredType dt : implementsTypes) {
+                                implementsClause.add(make.Type(dt));
+                            }
+                        }
+                    }
+                    ModifiersTree nueModifiers = make.Modifiers(modifiers);
+                    List<TypeParameterTree> typeParameters = new LinkedList<TypeParameterTree>();
+                    for (int cntr = 0; cntr < numTypeParameters; cntr++) {
+                        typeParameters.add(make.TypeParameter(numTypeParameters == 1 ? "T" : "T" + cntr, Collections.<ExpressionTree>emptyList())); // NOI18N
+                    }
+                    ClassTree source;
+                    switch (kind) {
+                        case CLASS:
+                            source = make.Class(nueModifiers, simpleName, typeParameters, extendsClause, implementsClause, members);
+                            break;
+                        case INTERFACE:
+                            source = make.Interface(nueModifiers, simpleName, typeParameters, implementsClause, members);
+                            break;
+                        case ANNOTATION_TYPE:
+                            source = make.AnnotationType(nueModifiers, simpleName, members);
+                            break;
+                        case ENUM:
+                            source = make.Enum(nueModifiers, simpleName, implementsClause, members);
+                            break;
+                        default:
+                            throw new IllegalStateException();
+                    }
+                    CompilationUnitTree cut = make.CompilationUnit(targetSourceRoot, packageName.replace('.', '/') + '/' + simpleName + ".java", Collections.<ImportTree>emptyList(), Collections.singletonList(source));
+                    working.rewrite(null, cut);
+                }
+            });
+        }
+
+        @Override
         public ChangeInfo implement() throws IOException {
             FileObject pack = FileUtil.createFolder(targetSourceRoot, packageName.replace('.', '/')); // NOI18N
             FileObject classTemplate/*???*/ = FileUtil.getConfigFile(template(kind));
@@ -383,7 +465,6 @@
 
         private FileObject targetFile;
         private ElementHandle<TypeElement> target;
-        private ClasspathInfo cpInfo;
         private String name;
         private String inFQN;
         
@@ -396,15 +477,23 @@
             this.targetFile = targetFile;
         }
             
+        @Override
         public String getText() {
             return NbBundle.getMessage(CreateClassFix.class, "FIX_CreateInnerClass", name, inFQN, valueForBundle(kind));
         }
 
+        @Override
         public ChangeInfo implement() throws Exception {
+            ModificationResult diff = getModificationResult();
+            return Utilities.commitAndComputeChangeInfo(targetFile, diff, null);
+        }
+
+        @Override
+        public ModificationResult getModificationResult() throws IOException {
             //use the original cp-info so it is "sure" that the target can be resolved:
             JavaSource js = JavaSource.create(cpInfo, targetFile);
             
-            ModificationResult diff = js.runModificationTask(new Task<WorkingCopy>() {
+            return js.runModificationTask(new Task<WorkingCopy>() {
 
                 public void run(final WorkingCopy working) throws IOException {
                     working.toPhase(Phase.RESOLVED);
@@ -431,8 +520,6 @@
                     working.rewrite(targetTree.getLeaf(), GeneratorUtilities.get(working).insertClassMember((ClassTree)targetTree.getLeaf(), innerClass));
                 }
             });
-            
-            return Utilities.commitAndComputeChangeInfo(targetFile, diff, null);
         }
         
         public String toDebugString(CompilationInfo info) {
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateEnumConstant.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateEnumConstant.java
index b8af510..191c771 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateEnumConstant.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateEnumConstant.java
@@ -57,7 +57,7 @@
  * 
  * @author Max Sauer
  */
-class CreateEnumConstant implements Fix {
+class CreateEnumConstant extends CreateFixBase implements Fix {
 
     private String name;
     private String inFQN;
@@ -85,14 +85,22 @@
         }
     }
 
+    @Override
     public String getText() {
         return NbBundle.getMessage(CreateEnumConstant.class, "LBL_FIX_Create_Enum_Constant", name, inFQN);
     }
 
+    @Override
     public ChangeInfo implement() throws Exception {
+        ModificationResult diff = getModificationResult();
+        return Utilities.commitAndComputeChangeInfo(targetFile, diff, null);
+    }
+
+    @Override
+    public ModificationResult getModificationResult() throws IOException {
         JavaSource js = JavaSource.create(cpInfo, targetFile);
 
-        ModificationResult diff = js.runModificationTask(new Task<WorkingCopy>() {
+        return js.runModificationTask(new Task<WorkingCopy>() {
             public void run(final WorkingCopy working) throws IOException {
                 working.toPhase(Phase.RESOLVED);
                 TypeElement targetType = target.resolve(working);
@@ -132,8 +140,6 @@
                 working.rewrite(targetTree, enumm);
             }
         });
-
-        return Utilities.commitAndComputeChangeInfo(targetFile, diff, null);
     }
 
     String toDebugString(CompilationInfo info) {
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFieldFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFieldFix.java
index 0cda238..2e84ce7 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFieldFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFieldFix.java
@@ -41,7 +41,6 @@
 import org.netbeans.modules.java.hints.infrastructure.ErrorHintsProvider;
 import org.netbeans.spi.editor.hints.ChangeInfo;
 import org.netbeans.spi.editor.hints.EnhancedFix;
-import org.netbeans.spi.editor.hints.Fix;
 import org.openide.filesystems.FileObject;
 import org.openide.util.NbBundle;
 
@@ -49,7 +48,7 @@
  *
  * @author Jan Lahoda
  */
-public final class CreateFieldFix implements EnhancedFix {
+public final class CreateFieldFix extends CreateFixBase implements EnhancedFix {
     
     private FileObject targetFile;
     private ElementHandle<TypeElement> target;
@@ -75,15 +74,27 @@
         this.proposedType = proposedType == null ? null : TypeMirrorHandle.create(proposedType);
         this.remote = !org.openide.util.Utilities.compareObjects(info.getFileObject(), targetFile);
     }
-    
+
+    @Override
     public String getText() {
         return NbBundle.getMessage(CreateFieldFix.class, "LBL_FIX_Create_Field", name, inFQN);        
     }
-    
+
+    @Override
     public ChangeInfo implement() throws IOException {
+        ModificationResult diff = getModificationResult();
+        ChangeInfo ci = Utilities.commitAndComputeChangeInfo(targetFile, diff, null);
+        if (remote) {
+            return ci;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public ModificationResult getModificationResult() throws IOException {
         //use the original cp-info so it is "sure" that the proposedType can be resolved:
         JavaSource js = JavaSource.create(cpInfo, targetFile);
-        
         ModificationResult diff = js.runModificationTask(new Task<WorkingCopy>() {
             public void run(final WorkingCopy working) throws IOException {
                 working.toPhase(Phase.RESOLVED);
@@ -115,16 +126,9 @@
                 working.rewrite(targetTree, decl);
             }
         });
-        
-        ChangeInfo ci = Utilities.commitAndComputeChangeInfo(targetFile, diff, null);
-        
-        if (remote) {
-            return ci;
-        } else {
-            return null;
-        }
+        return diff;
     }
-    
+
     String toDebugString(CompilationInfo info) {
         return "CreateFieldFix:" + name + ":" + target.getQualifiedName() + ":" + proposedType.resolve(info).toString() + ":" + modifiers; // NOI18N
     }
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFixBase.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFixBase.java
new file mode 100644
index 0000000..ec8b160
--- /dev/null
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateFixBase.java
@@ -0,0 +1,32 @@
+/*
+ * 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.hints.errors;
+
+import java.io.IOException;
+import org.netbeans.api.java.source.ModificationResult;
+import org.netbeans.spi.editor.hints.Fix;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+public abstract class CreateFixBase implements Fix {
+
+    public abstract ModificationResult getModificationResult() throws IOException;
+}
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateMethodFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateMethodFix.java
index bf92eac..89a09b2 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateMethodFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateMethodFix.java
@@ -67,7 +67,7 @@
  *
  * @author Jan lahoda
  */
-public final class CreateMethodFix implements Fix {
+public final class CreateMethodFix extends CreateFixBase implements Fix {
     
     private FileObject targetFile;
     private ElementHandle<TypeElement> target;
@@ -134,7 +134,8 @@
         
         this.methodDisplayName = methodDisplayName.toString();
     }
-    
+
+    @Override
     public String getText() {
         if(target.getKind() == ElementKind.ANNOTATION_TYPE)
             return NbBundle.getMessage(CreateMethodFix.class, "LBL_FIX_Create_Annotation_Element", methodDisplayName, inFQN );
@@ -144,17 +145,25 @@
             return NbBundle.getMessage(CreateMethodFix.class, "LBL_FIX_Create_Constructor", methodDisplayName, inFQN );
         }
     }
+
+    // tag used for selection
+    final String methodBodyTag = "mbody"; //NOI18N
     
+    @Override
     public ChangeInfo implement() throws IOException {
+        ModificationResult diff = getModificationResult();
+        return Utilities.commitAndComputeChangeInfo(targetFile, diff, methodBodyTag);
+    }
+
+    @Override
+    public ModificationResult getModificationResult() throws IOException {
         //use the original cp-info so it is "sure" that the proposedType can be resolved:
         JavaSource js = JavaSource.create(cpInfo, targetFile);
         if (js == null) {
             return null;
         }
-        // tag used for selection
-        final String methodBodyTag = "mbody"; //NOI18N
         
-        ModificationResult diff = js.runModificationTask(new Task<WorkingCopy>() {
+        return js.runModificationTask(new Task<WorkingCopy>() {
             public void run(final WorkingCopy working) throws IOException {
                 working.toPhase(Phase.RESOLVED);
                 TypeElement targetType = target.resolve(working);
@@ -236,8 +245,6 @@
                 working.rewrite(targetTree.getLeaf(), decl);
             }
         });
-        
-        return Utilities.commitAndComputeChangeInfo(targetFile, diff, methodBodyTag);
     }
     
     private void addArguments(CompilationInfo info, StringBuilder value) {
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceExpressionBasedMethodFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceExpressionBasedMethodFix.java
index 4f4369d..452ee25 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceExpressionBasedMethodFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceExpressionBasedMethodFix.java
@@ -22,12 +22,13 @@
 import com.sun.source.tree.ExpressionTree;
 import com.sun.source.tree.MethodTree;
 import com.sun.source.tree.ModifiersTree;
-import com.sun.source.tree.Scope;
 import com.sun.source.tree.StatementTree;
 import com.sun.source.tree.Tree;
 import com.sun.source.tree.TypeParameterTree;
 import com.sun.source.tree.VariableTree;
 import com.sun.source.util.TreePath;
+import java.awt.GraphicsEnvironment;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -47,6 +48,7 @@
 import javax.swing.text.Document;
 import org.netbeans.api.java.source.CompilationInfo;
 import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.ModificationResult;
 import org.netbeans.api.java.source.Task;
 import org.netbeans.api.java.source.TreeMaker;
 import org.netbeans.api.java.source.TreePathHandle;
@@ -147,7 +149,7 @@
         allIfaces.set(allInterfaces);
         return targets;
     }
-    
+
     private final TypeMirrorHandle     returnType;
     private final List<TreePathHandle> parameters;
     private final Set<TypeMirrorHandle> thrownTypes;
@@ -192,7 +194,26 @@
         final TargetDescription target = panel.getSelectedTarget();
         final MemberSearchResult searchResult = val.getResult();
         final boolean redoReferences = panel.isRefactorExisting();
-        js.runModificationTask(new Task<WorkingCopy>() {
+        getModificationResult(name, target, replaceOther, access, redoReferences, searchResult).commit();
+        return null;
+    }
+
+    @Override
+    public ModificationResult getModificationResult() throws IOException {
+        ModificationResult result = null;
+        int counter = 0;
+        do {
+            try {
+                result = getModificationResult("method" + (counter != 0 ? String.valueOf(counter) : ""), targets.iterator().next(), true, EnumSet.of(Modifier.PRIVATE), false, null);
+            } catch (Exception e) {
+                counter++;
+            }
+        } while (result == null && counter < 10);
+        return result;
+    }
+
+    private ModificationResult getModificationResult(final String name, final TargetDescription target, final boolean replaceOther, final Set<Modifier> access, final boolean redoReferences, final MemberSearchResult searchResult) throws IOException {
+        return js.runModificationTask(new Task<WorkingCopy>() {
             public void run(WorkingCopy copy) throws Exception {
                 copy.toPhase(JavaSource.Phase.RESOLVED);
                 TreePath expression = IntroduceExpressionBasedMethodFix.this.handle.resolve(copy);
@@ -211,6 +232,7 @@
                 returnType = Utilities.convertIfAnonymous(Utilities.resolveTypeForDeclaration(copy, returnType));
                 final TreeMaker make = copy.getTreeMaker();
                 Tree returnTypeTree = make.Type(returnType);
+                copy.tag(returnTypeTree, TYPE_TAG);
                 List<VariableElement> parameters = IntroduceHint.resolveVariables(copy, IntroduceExpressionBasedMethodFix.this.parameters);
                 List<ExpressionTree> realArguments = IntroduceHint.realArguments(make, parameters);
                 ExpressionTree invocation = make.MethodInvocation(Collections.<ExpressionTree>emptyList(), make.Identifier(name), realArguments);
@@ -243,11 +265,11 @@
                     //handle duplicates
                     Document doc = copy.getDocument();
                     Pattern p = Pattern.createPatternWithRemappableVariables(expression, parameters, true);
-                    for (Occurrence desc : Matcher.create(copy).setCancel(new AtomicBoolean()).match(p)) {
+                    for (Occurrence desc : Matcher.create(copy).setSearchRoot(pathToClass).setCancel(new AtomicBoolean()).match(p)) {
                         TreePath firstLeaf = desc.getOccurrenceRoot();
                         int startOff = (int) copy.getTrees().getSourcePositions().getStartPosition(copy.getCompilationUnit(), firstLeaf.getLeaf());
                         int endOff = (int) copy.getTrees().getSourcePositions().getEndPosition(copy.getCompilationUnit(), firstLeaf.getLeaf());
-                        if (!IntroduceHint.shouldReplaceDuplicate(doc, startOff, endOff)) {
+                        if (!GraphicsEnvironment.isHeadless() && !IntroduceHint.shouldReplaceDuplicate(doc, startOff, endOff)) {
                             continue;
                         }
                         //XXX:
@@ -282,13 +304,12 @@
                 
                 if (redoReferences) {
                     new ReferenceTransformer(
-                        copy, ElementKind.METHOD,  searchResult,
-                        name, 
-                        targetType).scan(pathToClass, null);
+                            copy, ElementKind.METHOD,  searchResult,
+                            name,
+                            targetType).scan(pathToClass, null);
                 }
             }
-        }).commit();
-        return null;
+        });
     }
-    
+
 }
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFieldFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFieldFix.java
index cc36bbc..21d0fd0c 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFieldFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFieldFix.java
@@ -48,6 +48,7 @@
 import javax.swing.JButton;
 import javax.swing.text.BadLocationException;
 import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.ModificationResult;
 import org.netbeans.api.java.source.SourceUtils;
 import org.netbeans.api.java.source.Task;
 import org.netbeans.api.java.source.TreeMaker;
@@ -132,6 +133,14 @@
         return pathToClass;
     }
 
+    private TreePath findOutermostClass(WorkingCopy copy, TreePath resolved) {
+        TreePath pathToClass = resolved;
+        while (pathToClass != null && (!TreeUtilities.CLASS_TREE_KINDS.contains(pathToClass.getLeaf().getKind()) || pathToClass.getParentPath().getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT)) {
+            pathToClass = pathToClass.getParentPath();
+        }
+        return pathToClass;
+    }
+
     @Override
     public ChangeInfo implement() throws IOException, BadLocationException {
         JButton btnOk = new JButton(NbBundle.getMessage(IntroduceHint.class, "LBL_Ok"));
@@ -157,7 +166,12 @@
                 panel.isRefactorExisting())).commit();
         return null;
     }
-    
+
+    @Override
+    public ModificationResult getModificationResult() throws IOException {
+        return js.runModificationTask(new Worker(guessedName, permitDuplicates, false, EnumSet.of(Modifier.PRIVATE), IntroduceFieldPanel.INIT_FIELD, null, false));
+    }
+
     /**
      * The actual modification. Some javac related data are recorded in fields, inner class prevents
      * unintentional leak if someone keeps a reference to the Fix
@@ -294,7 +308,7 @@
             allNewUses.add(resolved.getLeaf());
             Collection<TreePath> duplicates = new ArrayList<>();
             if (replaceAll) {
-                for (TreePath p : SourceUtils.computeDuplicates(parameter, resolved, new TreePath(parameter.getCompilationUnit()), new AtomicBoolean())) {
+                for (TreePath p : SourceUtils.computeDuplicates(parameter, resolved, findOutermostClass(parameter, resolved), new AtomicBoolean())) {
                     if (variableRewrite) {
                         IntroduceHint.removeFromParent(parameter, p);
                     } else {
@@ -317,7 +331,9 @@
             VariableTree field;
             expressionStatementRewrite = parentTree.getKind() == Tree.Kind.EXPRESSION_STATEMENT;
             if (!variableRewrite) {
-                field = make.Variable(modsTree, name, make.Type(tm), initializeIn == IntroduceFieldPanel.INIT_FIELD ? expression : null);
+                Tree varType = make.Type(tm);
+                field = make.Variable(modsTree, name, varType, initializeIn == IntroduceFieldPanel.INIT_FIELD ? expression : null);
+                parameter.tag(varType, TYPE_TAG);
                 if (!expressionStatementRewrite) {
                     Tree nueParent = parameter.getTreeUtilities().translate(parentTree, Collections.singletonMap(resolved.getLeaf(), make.Identifier(name)));
                     parameter.rewrite(parentTree, nueParent);
@@ -326,8 +342,9 @@
                     toRemoveFromParent = resolved.getParentPath();
                 }
             } else {
-                VariableTree originalVar = (VariableTree) original;
-                field = make.Variable(modsTree, name, originalVar.getType(), initializeIn == IntroduceFieldPanel.INIT_FIELD ? expression : null);
+                Tree originalVarType = ((VariableTree) original).getType();
+                field = make.Variable(modsTree, name, originalVarType, initializeIn == IntroduceFieldPanel.INIT_FIELD ? expression : null);
+                parameter.tag(originalVarType, TYPE_TAG);
                 toRemoveFromParent = resolved;
             }
             nueClass = IntroduceHint.insertField(parameter, (ClassTree) pathToClass.getLeaf(), field, allNewUses, offset);
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFixBase.java b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFixBase.java
index 22de63d..181affd 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFixBase.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceFixBase.java
@@ -18,7 +18,9 @@
  */
 package org.netbeans.modules.java.hints.introduce;
 
+import java.io.IOException;
 import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.ModificationResult;
 import org.netbeans.api.java.source.TreePathHandle;
 import org.netbeans.spi.editor.hints.Fix;
 
@@ -28,8 +30,10 @@
  * @author sdedic
  */
 public abstract class IntroduceFixBase implements Fix {
+
+    protected static final String TYPE_TAG = "typeTag";
     protected final JavaSource js;
-    protected final TreePathHandle  handle; 
+    protected final TreePathHandle  handle;
     protected final int duplicatesCount;
     protected final int offset;
     protected boolean targetIsInterface;
@@ -45,4 +49,10 @@
         this.targetIsInterface = f;
     }
 
+    public abstract ModificationResult getModificationResult() throws IOException;
+
+    public int getNameOffset(ModificationResult result) {
+        int[] span = result.getSpan(TYPE_TAG);
+        return span != null ? span[1] + 1 : -1;
+    }
 }
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceMethodFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceMethodFix.java
index 043f682..0e4fc7c 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceMethodFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceMethodFix.java
@@ -29,13 +29,13 @@
 import com.sun.source.tree.ModifiersTree;
 import com.sun.source.tree.PrimitiveTypeTree;
 import com.sun.source.tree.ReturnTree;
-import com.sun.source.tree.Scope;
 import com.sun.source.tree.StatementTree;
 import com.sun.source.tree.Tree;
 import com.sun.source.tree.TypeParameterTree;
 import com.sun.source.tree.VariableTree;
 import com.sun.source.util.SourcePositions;
 import com.sun.source.util.TreePath;
+import java.awt.GraphicsEnvironment;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -59,18 +59,14 @@
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
-import javax.lang.model.type.DeclaredType;
-import javax.lang.model.type.ExecutableType;
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
 import javax.swing.JButton;
 import javax.swing.text.Document;
-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.ElementUtilities.ElementAcceptor;
 import org.netbeans.api.java.source.GeneratorUtilities;
 import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.ModificationResult;
 import org.netbeans.api.java.source.Task;
 import org.netbeans.api.java.source.TreeMaker;
 import org.netbeans.api.java.source.TreePathHandle;
@@ -440,7 +436,21 @@
         js.runModificationTask(new TaskImpl(access, name, target, replaceOther, val.getResult(), redoReferences)).commit();
         return null;
     }
-    
+
+    @Override
+    public ModificationResult getModificationResult() throws IOException {
+        ModificationResult result = null;
+        int counter = 0;
+        do {
+            try {
+                result = js.runModificationTask(new TaskImpl(EnumSet.of(Modifier.PRIVATE), "method" + (counter != 0 ? String.valueOf(counter) : ""), targets.iterator().next(), true, null, false));
+            } catch (Exception e) {
+                counter++;
+            }
+        } while (result == null && counter < 10);
+        return result;
+    }
+
     static class OccurrencePositionComparator implements Comparator<Occurrence> {
         final CompilationUnitTree cut;
         final SourcePositions positions;
@@ -569,7 +579,8 @@
             }
             ModifiersTree mods = make.Modifiers(modifiers);
             MethodTree method = make.Method(mods, name, returnTypeTree, typeVars, formalArguments, thrown, make.Block(methodStatements, false), null);
-            
+            copy.tag(returnTypeTree, TYPE_TAG);
+
             return method;
         }
         /**
@@ -824,7 +835,7 @@
                     statementsPaths.add(new TreePath(firstStatement.getParentPath(), t));
                 }
                 Pattern p = Pattern.createPatternWithRemappableVariables(statementsPaths, parameters, true);
-                List<? extends Occurrence> occurrences = new ArrayList<Occurrence>(Matcher.create(copy).setCancel(new AtomicBoolean()).match(p));
+                List<? extends Occurrence> occurrences = new ArrayList<Occurrence>(Matcher.create(copy).setSearchRoot(pathToClass).setCancel(new AtomicBoolean()).match(p));
                 Collections.sort(occurrences, new OccurrencePositionComparator(copy.getCompilationUnit(), copy.getTrees().getSourcePositions()));
                 for (Occurrence desc :occurrences ) {
                     TreePath firstLeaf = desc.getOccurrenceRoot();
@@ -862,7 +873,7 @@
                     int startOff = (int) copy.getTrees().getSourcePositions().getStartPosition(copy.getCompilationUnit(), firstSt);
                     int endOff = (int) copy.getTrees().getSourcePositions().getEndPosition(copy.getCompilationUnit(), lastSt);
                     
-                    if (usedAfter || !IntroduceHint.shouldReplaceDuplicate(doc, startOff, endOff)) {
+                    if (usedAfter || !GraphicsEnvironment.isHeadless() && !IntroduceHint.shouldReplaceDuplicate(doc, startOff, endOff)) {
                         continue;
                     }
                     List<StatementTree> newStatements = new LinkedList<StatementTree>();
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceVariableFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceVariableFix.java
index a227018..6e40a51 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceVariableFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/IntroduceVariableFix.java
@@ -19,7 +19,6 @@
 package org.netbeans.modules.java.hints.introduce;
 
 import com.sun.source.tree.ExpressionTree;
-import com.sun.source.tree.LambdaExpressionTree;
 import com.sun.source.tree.ModifiersTree;
 import com.sun.source.tree.StatementTree;
 import com.sun.source.tree.Tree;
@@ -29,7 +28,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
-import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
@@ -41,9 +39,9 @@
 import javax.swing.JButton;
 import javax.swing.text.BadLocationException;
 import org.netbeans.api.java.source.CompilationInfo;
-import org.netbeans.api.java.source.ElementHandle;
 import org.netbeans.api.java.source.GeneratorUtilities;
 import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.ModificationResult;
 import org.netbeans.api.java.source.SourceUtils;
 import org.netbeans.api.java.source.Task;
 import org.netbeans.api.java.source.TreeMaker;
@@ -127,9 +125,9 @@
     public ChangeInfo implement() throws IOException, BadLocationException {
         JButton btnOk = new JButton(NbBundle.getMessage(IntroduceHint.class, "LBL_Ok"));
         JButton btnCancel = new JButton(NbBundle.getMessage(IntroduceHint.class, "LBL_Cancel"));
-        IntroduceFieldPanel panel = new IntroduceFieldPanel(guessedName, null, duplicatesCount, 
-                true, handle.getKind() == Tree.Kind.VARIABLE, 
-                IntroduceFieldPanel.VARIABLE, 
+        IntroduceFieldPanel panel = new IntroduceFieldPanel(guessedName, null, duplicatesCount,
+                true, handle.getKind() == Tree.Kind.VARIABLE,
+                IntroduceFieldPanel.VARIABLE,
                 "introduceVariable", btnOk);
         String caption = NbBundle.getMessage(IntroduceHint.class, "CAP_" + getKeyExt()); //NOI18N
         DialogDescriptor dd = new DialogDescriptor(panel, caption, true, new Object[]{btnOk, btnCancel}, btnOk, DialogDescriptor.DEFAULT_ALIGN, null, null);
@@ -145,7 +143,17 @@
         final boolean replaceAll = panel.isReplaceAll();
         final boolean declareFinal = panel.isDeclareFinal();
         final MemberSearchResult search = val.getLastResult();
-        js.runModificationTask(new Task<WorkingCopy>() {
+        getModificationResult(replaceAll, name, declareFinal, refactor, search).commit();
+        return null;
+    }
+
+    @Override
+    public ModificationResult getModificationResult() throws IOException {
+        return getModificationResult(true, guessedName, false, false, null);
+    }
+
+    private ModificationResult getModificationResult(final boolean replaceAll, final String name, final boolean declareFinal, final boolean refactor, final MemberSearchResult search) throws IOException {
+        return js.runModificationTask(new Task<WorkingCopy>() {
             public void run(WorkingCopy parameter) throws Exception {
                 parameter.toPhase(JavaSource.Phase.RESOLVED);
                 TreePath resolved = handle.resolve(parameter);
@@ -158,7 +166,7 @@
                 }
                 tm = Utilities.convertIfAnonymous(Utilities.resolveTypeForDeclaration(parameter, tm));
                 if (!Utilities.isValidType(tm)) {
-                    return; // TODO... 
+                    return; // TODO...
                 }
                 Element targetEl = null;
                 TreePath targetPath = null;
@@ -212,7 +220,9 @@
                 List<StatementTree> nueStatements;
                 GeneratorUtilities.get(parameter).importComments(IntroduceHint.getStatementOrBlock(statement).getLeaf(), parameter.getCompilationUnit());
                 mods = make.Modifiers(declareFinal ? EnumSet.of(Modifier.FINAL) : EnumSet.noneOf(Modifier.class));
-                VariableTree newVariable = make.Variable(mods, name, make.Type(tm), expression);
+                Tree varType = make.Type(tm);
+                VariableTree newVariable = make.Variable(mods, name, varType, expression);
+                parameter.tag(varType, TYPE_TAG);
                 nueStatements = new ArrayList<>();
                 nueStatements.add(make.asReplacementOf(newVariable, resolved.getLeaf(), true));
                 if (expressionStatement) {
@@ -220,20 +230,19 @@
                 } else {
                     Utilities.insertStatement(parameter, statement, nueStatements, null);
                 }
-                
+
                 if (!expressionStatement) {
                     Tree origParent = resolved.getParentPath().getLeaf();
-                    Tree newParent = parameter.getTreeUtilities().translate(origParent, Collections.singletonMap(resolved.getLeaf(), 
+                    Tree newParent = parameter.getTreeUtilities().translate(origParent, Collections.singletonMap(resolved.getLeaf(),
                             make.asNew(make.Identifier(name))));
                     parameter.rewrite(origParent, newParent);
                 }
-                
+
                 if (refactor) {
-                    new ReferenceTransformer(parameter, ElementKind.LOCAL_VARIABLE, 
+                    new ReferenceTransformer(parameter, ElementKind.LOCAL_VARIABLE,
                             search, name, targetEl).scan(statement.getParentPath(), null);
                 }
             }
-        }).commit();
-        return null;
+        });
     }
 }
diff --git a/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/DefaultSourceLevelQueryImpl.java b/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/DefaultSourceLevelQueryImpl.java
index e600684..ea3a48f 100644
--- a/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/DefaultSourceLevelQueryImpl.java
+++ b/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/DefaultSourceLevelQueryImpl.java
@@ -205,7 +205,7 @@
                     .map((xt) -> xt.toString())
                     .orElse(pkg);
             }
-        } catch (IOException ioe) {
+        } catch (Exception e) {
             //TODO: Log & pass
         }
         return pkg;
diff --git a/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/SourceJavadocAttacherUtil.java b/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/SourceJavadocAttacherUtil.java
index 2b3d632..1c2dcc9 100644
--- a/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/SourceJavadocAttacherUtil.java
+++ b/java/java.j2seplatform/src/org/netbeans/modules/java/j2seplatform/queries/SourceJavadocAttacherUtil.java
@@ -18,14 +18,17 @@
  */
 package org.netbeans.modules.java.j2seplatform.queries;
 
+import java.awt.GraphicsEnvironment;
 import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Callable;
+import java.util.stream.Collectors;
 import javax.swing.JFileChooser;
 import javax.swing.filechooser.FileFilter;
 import org.netbeans.api.annotations.common.CheckForNull;
@@ -34,7 +37,6 @@
 import org.netbeans.api.java.queries.SourceJavadocAttacher.AttachmentListener;
 import org.netbeans.spi.java.project.support.JavadocAndSourceRootDetection;
 import org.netbeans.spi.java.queries.SourceJavadocAttacherImplementation;
-import org.netbeans.spi.java.queries.SourceJavadocAttacherImplementation.Definer;
 import org.openide.DialogDescriptor;
 import org.openide.DialogDisplayer;
 import org.openide.NotifyDescriptor;
@@ -111,23 +113,36 @@
         assert root != null;
         assert browseCall != null;
         assert convertor != null;
-        final SelectRootsPanel selectSources = new SelectRootsPanel(
-                SelectRootsPanel.SOURCES,
-                root,
-                attachedRoots,
-                browseCall,
-                convertor,
-                plugin);
-        final DialogDescriptor dd = new DialogDescriptor(selectSources, Bundle.TXT_SelectSources());
-        dd.setButtonListener(selectSources);
-        if (DialogDisplayer.getDefault().notify(dd) == DialogDescriptor.OK_OPTION) {
-            try {
-                return selectSources.getRoots();
-            } catch (Exception e) {
-                DialogDisplayer.getDefault().notify(
-                        new NotifyDescriptor.Message(
-                            Bundle.TXT_InvalidSourceRoot(),
-                            NotifyDescriptor.ERROR_MESSAGE));
+        if (GraphicsEnvironment.isHeadless()) {
+            List<? extends URI> sources = plugin.getSources(root, () -> false).stream().map(url -> {
+                try {
+                    return url.toURI();
+                } catch (URISyntaxException ex) {
+                }
+                return null;
+            }).filter(uri -> uri != null).collect(Collectors.toList());
+            if (!sources.isEmpty()) {
+                return sources;
+            }
+        } else {
+            final SelectRootsPanel selectSources = new SelectRootsPanel(
+                    SelectRootsPanel.SOURCES,
+                    root,
+                    attachedRoots,
+                    browseCall,
+                    convertor,
+                    plugin);
+            final DialogDescriptor dd = new DialogDescriptor(selectSources, Bundle.TXT_SelectSources());
+            dd.setButtonListener(selectSources);
+            if (DialogDisplayer.getDefault().notify(dd) == DialogDescriptor.OK_OPTION) {
+                try {
+                    return selectSources.getRoots();
+                } catch (Exception e) {
+                    DialogDisplayer.getDefault().notify(
+                            new NotifyDescriptor.Message(
+                                Bundle.TXT_InvalidSourceRoot(),
+                                NotifyDescriptor.ERROR_MESSAGE));
+                }
             }
         }
         return null;
diff --git a/java/java.lexer/manifest.mf b/java/java.lexer/manifest.mf
index bd61e4c..9aee443 100644
--- a/java/java.lexer/manifest.mf
+++ b/java/java.lexer/manifest.mf
@@ -1,5 +1,5 @@
 OpenIDE-Module: org.netbeans.modules.java.lexer/1
 OpenIDE-Module-Localizing-Bundle: org/netbeans/lib/java/lexer/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.45
+OpenIDE-Module-Specification-Version: 1.45.1
 OpenIDE-Module-Layer: org/netbeans/lib/java/lexer/layer.xml
 
diff --git a/java/java.lsp.server/build.xml b/java/java.lsp.server/build.xml
index 5034869..48e9311 100644
--- a/java/java.lsp.server/build.xml
+++ b/java/java.lsp.server/build.xml
@@ -129,4 +129,16 @@
 
         <zip destfile="${build.dir}/apache-netbeans-java-${vsix.version}.vsix" basedir="${build.dir}/vscode-mandatory/" update="true" />
     </target>
+    <target name="test-vscode-ext" description="Tests the Visual Studio Code extension built by 'build-vscode-ext' target.">
+        <exec executable="npm" failonerror="true" dir="vscode">
+            <arg value="--allow-same-version"/>
+            <arg value="run" />
+            <arg value="nbjavac" />
+        </exec>
+        <exec executable="npm" failonerror="true" dir="vscode">
+            <arg value="--allow-same-version"/>
+            <arg value="run" />
+            <arg value="test" />
+        </exec>
+    </target>
 </project>
diff --git a/java/java.lsp.server/external/binaries-list b/java/java.lsp.server/external/binaries-list
index 05e0a74..39d428f 100644
--- a/java/java.lsp.server/external/binaries-list
+++ b/java/java.lsp.server/external/binaries-list
@@ -15,11 +15,11 @@
 # specific language governing permissions and limitations
 # under the License.
 
-4FF29B93C50F6768C9A73040AE818E96EB2E68DB org.eclipse.lsp4j:org.eclipse.lsp4j:0.9.0
-E5F9B4D2A6DDE53FA2CEAD6F0D51CABC73E3C850 org.eclipse.lsp4j:org.eclipse.lsp4j.debug:0.9.0
-DA453B2A6C9BBD9369DE41365C59E88574EC1F0B org.eclipse.lsp4j:org.eclipse.lsp4j.generator:0.9.0
-1CF7FCE1E2F4DFA76C3B91E4C2D72EA0C6899945 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.9.0
-DAF59D5711756C35B6BF9537EB1A6EDDF52EF452 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc.debug:0.9.0
+271665493A1E35FB4C2D1DBABF19363C8AD27819 org.eclipse.lsp4j:org.eclipse.lsp4j:0.10.0
+18621C63F8D8587C0CC5EEB9CB945E49E9B5C900 org.eclipse.lsp4j:org.eclipse.lsp4j.debug:0.10.0
+ED2D6D876578B139AEA24289422435148FEF6B45 org.eclipse.lsp4j:org.eclipse.lsp4j.generator:0.10.0
+42CA44A49315A490804A3DED67163123748C1F19 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.10.0
+30A70C7AC5E722F25843669F124CDACA9772B9C8 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc.debug:0.10.0
 751B324BED8C077BF6B7B8C2055595EB8C28509D org.eclipse.xtend:org.eclipse.xtend.lib:2.19.0
 F3626A047A3ABFA47CF62D76BDEF1D246F7985F6 org.eclipse.xtend:org.eclipse.xtend.lib.macro:2.19.0
 997BA9935D0069B3F1CD2F93B56B91D24E9C60A0 org.eclipse.xtext:org.eclipse.xtext.xbase.lib:2.19.0
diff --git a/java/java.lsp.server/external/lsp4j-0.9.0-license.txt b/java/java.lsp.server/external/lsp4j-0.10.0-license.txt
similarity index 98%
rename from java/java.lsp.server/external/lsp4j-0.9.0-license.txt
rename to java/java.lsp.server/external/lsp4j-0.10.0-license.txt
index 90fb325..d90f316 100644
--- a/java/java.lsp.server/external/lsp4j-0.9.0-license.txt
+++ b/java/java.lsp.server/external/lsp4j-0.10.0-license.txt
@@ -1,10 +1,10 @@
 Name: Eclipse Language Server Protocol Library
 Origin: Eclipse
-Version: 0.9.0
+Version: 0.10.0
 License: EPL-v20
 URL: http://www.eclipse.org/
 Description: Eclipse Language Server Protocol Library
-Files: org.eclipse.lsp4j-0.9.0.jar org.eclipse.lsp4j.debug-0.9.0.jar org.eclipse.lsp4j.generator-0.9.0.jar org.eclipse.lsp4j.jsonrpc-0.9.0.jar org.eclipse.lsp4j.jsonrpc.debug-0.9.0.jar
+Files: org.eclipse.lsp4j-0.10.0.jar org.eclipse.lsp4j.debug-0.10.0.jar org.eclipse.lsp4j.generator-0.10.0.jar org.eclipse.lsp4j.jsonrpc-0.10.0.jar org.eclipse.lsp4j.jsonrpc.debug-0.10.0.jar
 
 Eclipse Public License - v 2.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.
diff --git a/java/java.lsp.server/nbcode/integration/licenseinfo.xml b/java/java.lsp.server/nbcode/integration/licenseinfo.xml
new file mode 100644
index 0000000..13f4a57
--- /dev/null
+++ b/java/java.lsp.server/nbcode/integration/licenseinfo.xml
@@ -0,0 +1,30 @@
+<?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>src/org/netbeans/modules/nbcode/integration/resources/GeneratedMethodBody.template</file>
+        <file>src/org/netbeans/modules/nbcode/integration/resources/LambdaBody.template</file>
+        <file>src/org/netbeans/modules/nbcode/integration/resources/OverriddenMethodBody.template</file>
+        <license ref="Apache-2.0-ASF" />
+        <comment type="TEMPLATE_MINIMAL_IP"/>
+    </fileset>
+</licenseinfo>
diff --git a/java/java.lsp.server/nbcode/integration/nbproject/project.xml b/java/java.lsp.server/nbcode/integration/nbproject/project.xml
index 3a36d95..78bfd93 100644
--- a/java/java.lsp.server/nbcode/integration/nbproject/project.xml
+++ b/java/java.lsp.server/nbcode/integration/nbproject/project.xml
@@ -35,6 +35,23 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.api.templates</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>1.18</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.api.progress</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.57.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.java.lsp.server</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -59,6 +76,14 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.openide.util</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>9.17</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.openide.util.lookup</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspDialogDisplayer.java b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspDialogDisplayer.java
index 98e4416..df3f217 100644
--- a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspDialogDisplayer.java
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspDialogDisplayer.java
@@ -26,6 +26,6 @@
  *
  * @author sdedic
  */
-//@ServiceProvider(service = DialogDisplayer.class, position = 1000)
+@ServiceProvider(service = DialogDisplayer.class, position = 1000)
 public class LspDialogDisplayer extends AbstractDialogDisplayer {
 }
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspProgressEnvironment.java b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspProgressEnvironment.java
new file mode 100644
index 0000000..a357eea
--- /dev/null
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/LspProgressEnvironment.java
@@ -0,0 +1,32 @@
+/*
+ * 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.nbcode.integration;
+
+import org.netbeans.modules.java.lsp.server.ui.AbstractProgressEnvironment;
+import org.netbeans.modules.progress.spi.ProgressEnvironment;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author sdedic
+ */
+@ServiceProvider(service = ProgressEnvironment.class, position = 10000)
+public class LspProgressEnvironment extends AbstractProgressEnvironment {
+    
+}
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/package-info.java b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/package-info.java
new file mode 100644
index 0000000..80fbe04
--- /dev/null
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/package-info.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+@TemplateRegistrations({
+    @TemplateRegistration(folder = "Classes/Code", position = 100, content = "resources/GeneratedMethodBody.template", scriptEngine = "freemarker", displayName = "#GeneratedMethodBody", category = "java-code-snippet"),
+    @TemplateRegistration(folder = "Classes/Code", position = 200, content = "resources/OverriddenMethodBody.template", scriptEngine = "freemarker", displayName = "#OverriddenMethodBody", category = "java-code-snippet"),
+    @TemplateRegistration(folder = "Classes/Code", position = 300, content = "resources/LambdaBody.template", scriptEngine = "freemarker", displayName = "#LambdaBody", category = "java-code-snippet")
+})
+@NbBundle.Messages({
+    "GeneratedMethodBody=Generated Method Body", "OverriddenMethodBody=Overridden Method Body", "LambdaBody=Generated Lambda Body" //NOI18N
+})
+package org.netbeans.modules.nbcode.integration;
+
+import org.netbeans.api.templates.TemplateRegistration;
+import org.netbeans.api.templates.TemplateRegistrations;
+import org.openide.util.NbBundle;
+
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/GeneratedMethodBody.template b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/GeneratedMethodBody.template
new file mode 100644
index 0000000..8e8e003
--- /dev/null
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/GeneratedMethodBody.template
@@ -0,0 +1,13 @@
+<#--
+A built-in Freemarker template (see http://freemarker.sourceforge.net) used for
+filling the body of methods generated by the IDE. When editing the template,
+the following predefined variables, that will be then expanded into
+the corresponding values, could be used together with Java expressions and
+comments:
+${method_return_type}       a return type of a created method
+${default_return_value}     a value returned by the method by default
+${method_name}              name of the created method
+${class_name}               qualified name of the enclosing class
+${simple_class_name}        simple name of the enclosing class
+-->
+throw new java.lang.UnsupportedOperationException("Not supported yet.");
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/LambdaBody.template b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/LambdaBody.template
new file mode 100644
index 0000000..f4c0584
--- /dev/null
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/LambdaBody.template
@@ -0,0 +1,14 @@
+<#--
+A built-in Freemarker template (see http://freemarker.sourceforge.net) used for
+filling the body of lambda expressions generated by the IDE. When editing
+the template, the following predefined variables, that will be then expanded
+into the corresponding values, could be used together with Java expressions and
+comments:
+${method_return_type}       a return type of the functional interface method
+${default_return_value}     a value returned by the method by default
+${method_name}              name of the functional interface method
+-->
+<#if method_return_type?? && method_return_type != "void">
+return ${default_return_value};
+<#else>
+</#if>
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/OverriddenMethodBody.template b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/OverriddenMethodBody.template
new file mode 100644
index 0000000..0122d0d
--- /dev/null
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/resources/OverriddenMethodBody.template
@@ -0,0 +1,18 @@
+<#--
+A built-in Freemarker template (see http://freemarker.sourceforge.net) used for
+filling the body of overridden methods generated by the IDE. When editing
+the template, the following predefined variables, that will be then expanded
+into the corresponding values, could be used together with Java expressions and
+comments:
+${super_method_call}        a super method call
+${method_return_type}       a return type of a created method
+${default_return_value}     a value returned by the method by default
+${method_name}              name of the created method
+${class_name}               qualified name of the enclosing class
+${simple_class_name}        simple name of the enclosing class
+-->
+<#if method_return_type?? && method_return_type != "void">
+return ${super_method_call};
+<#else>
+${super_method_call};
+</#if>
diff --git a/java/java.lsp.server/nbproject/project.properties b/java/java.lsp.server/nbproject/project.properties
index a4153f3..45081a0 100644
--- a/java/java.lsp.server/nbproject/project.properties
+++ b/java/java.lsp.server/nbproject/project.properties
@@ -17,17 +17,17 @@
 
 javac.source=1.8
 javac.compilerargs=-Xlint -Xlint:-serial
-spec.version.base=1.6.0
+spec.version.base=1.6.1
 requires.nb.javac=true
 lsp.build.dir=vscode/nbcode
 test.unit.run.cp.extra=
 cp.extra=${tools.jar}
 
-release.external/org.eclipse.lsp4j-0.9.0.jar=modules/ext/org.eclipse.lsp4j-0.9.0.jar
-release.external/org.eclipse.lsp4j.debug-0.9.0.jar=modules/ext/org.eclipse.lsp4j.debug-0.9.0.jar
-release.external/org.eclipse.lsp4j.generator-0.9.0.jar=modules/ext/org.eclipse.lsp4j.generator-0.9.0.jar
-release.external/org.eclipse.lsp4j.jsonrpc-0.9.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc-0.9.0.jar
-release.external/org.eclipse.lsp4j.jsonrpc.debug-0.9.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc.debug-0.9.0.jar
+release.external/org.eclipse.lsp4j-0.10.0.jar=modules/ext/org.eclipse.lsp4j-0.10.0.jar
+release.external/org.eclipse.lsp4j.debug-0.10.0.jar=modules/ext/org.eclipse.lsp4j.debug-0.10.0.jar
+release.external/org.eclipse.lsp4j.generator-0.10.0.jar=modules/ext/org.eclipse.lsp4j.generator-0.10.0.jar
+release.external/org.eclipse.lsp4j.jsonrpc-0.10.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc-0.10.0.jar
+release.external/org.eclipse.lsp4j.jsonrpc.debug-0.10.0.jar=modules/ext/org.eclipse.lsp4j.jsonrpc.debug-0.10.0.jar
 release.external/org.eclipse.xtend.lib-2.19.0.jar=modules/ext/org.eclipse.xtend.lib-2.19.0.jar
 release.external/org.eclipse.xtend.lib.macro-2.19.0.jar=modules/ext/org.eclipse.xtend.lib.macro-2.19.0.jar
 release.external/org.eclipse.xtext.xbase.lib-2.19.0.jar=modules/ext/org.eclipse.xtext.xbase.lib-2.19.0.jar
diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml
index 7d0d84b..f0071f2 100644
--- a/java/java.lsp.server/nbproject/project.xml
+++ b/java/java.lsp.server/nbproject/project.xml
@@ -26,12 +26,20 @@
             <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>
+                        <specification-version>2.7</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.api.annotations.common</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.34</specification-version>
+                        <specification-version>1.36</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
@@ -79,6 +87,15 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.api.progress</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.57.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.libs.flexmark</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -130,6 +147,15 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.editor.lib2</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>2.30.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.editor.mimelookup</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -173,6 +199,23 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.java.lexer</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.45.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.java.source</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <implementation-version/>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.java.source.base</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -199,6 +242,15 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.jumpto</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.64</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.lexer</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -221,7 +273,16 @@
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>9.13</specification-version>
+                        <implementation-version/>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.parsing.lucene</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>2</release-version>
+                        <specification-version>2.45.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
@@ -243,11 +304,29 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.queries</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.52</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>
+                        <implementation-version/>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.refactoring.java</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <implementation-version/>
                     </run-dependency>
                 </dependency>
                 <dependency>
@@ -265,7 +344,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>2.63</specification-version>
+                        <specification-version>2.65.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
@@ -374,14 +453,6 @@
                         <specification-version>9.14</specification-version>
                     </run-dependency>
                 </dependency>
-                <dependency>
-                    <code-name-base>com.google.gson</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>2.7</specification-version>
-                    </run-dependency>
-                </dependency>
             </module-dependencies>
             <test-dependencies>
                 <test-type>
@@ -483,24 +554,24 @@
                 <package>org.netbeans.modules.java.lsp.server.ui</package>
             </public-packages>
             <class-path-extension>
-                <runtime-relative-path>ext/org.eclipse.lsp4j.debug-0.9.0.jar</runtime-relative-path>
-                <binary-origin>external/org.eclipse.lsp4j.debug-0.9.0.jar</binary-origin>
+                <runtime-relative-path>ext/org.eclipse.lsp4j.debug-0.10.0.jar</runtime-relative-path>
+                <binary-origin>external/org.eclipse.lsp4j.debug-0.10.0.jar</binary-origin>
             </class-path-extension>
             <class-path-extension>
                 <runtime-relative-path>ext/org.eclipse.xtend.lib.macro-2.19.0.jar</runtime-relative-path>
                 <binary-origin>external/org.eclipse.xtend.lib.macro-2.19.0.jar</binary-origin>
             </class-path-extension>
             <class-path-extension>
-                <runtime-relative-path>ext/org.eclipse.lsp4j.generator-0.9.0.jar</runtime-relative-path>
-                <binary-origin>external/org.eclipse.lsp4j.generator-0.9.0.jar</binary-origin>
+                <runtime-relative-path>ext/org.eclipse.lsp4j.generator-0.10.0.jar</runtime-relative-path>
+                <binary-origin>external/org.eclipse.lsp4j.generator-0.10.0.jar</binary-origin>
             </class-path-extension>
             <class-path-extension>
-                <runtime-relative-path>ext/org.eclipse.lsp4j.jsonrpc-0.9.0.jar</runtime-relative-path>
-                <binary-origin>external/org.eclipse.lsp4j.jsonrpc-0.9.0.jar</binary-origin>
+                <runtime-relative-path>ext/org.eclipse.lsp4j.jsonrpc-0.10.0.jar</runtime-relative-path>
+                <binary-origin>external/org.eclipse.lsp4j.jsonrpc-0.10.0.jar</binary-origin>
             </class-path-extension>
             <class-path-extension>
-                <runtime-relative-path>ext/org.eclipse.lsp4j.jsonrpc.debug-0.9.0.jar</runtime-relative-path>
-                <binary-origin>external/org.eclipse.lsp4j.jsonrpc.debug-0.9.0.jar</binary-origin>
+                <runtime-relative-path>ext/org.eclipse.lsp4j.jsonrpc.debug-0.10.0.jar</runtime-relative-path>
+                <binary-origin>external/org.eclipse.lsp4j.jsonrpc.debug-0.10.0.jar</binary-origin>
             </class-path-extension>
             <class-path-extension>
                 <runtime-relative-path>ext/org.eclipse.xtend.lib-2.19.0.jar</runtime-relative-path>
@@ -511,8 +582,8 @@
                 <binary-origin>external/org.eclipse.xtext.xbase.lib-2.19.0.jar</binary-origin>
             </class-path-extension>
             <class-path-extension>
-                <runtime-relative-path>ext/org.eclipse.lsp4j-0.9.0.jar</runtime-relative-path>
-                <binary-origin>external/org.eclipse.lsp4j-0.9.0.jar</binary-origin>
+                <runtime-relative-path>ext/org.eclipse.lsp4j-0.10.0.jar</runtime-relative-path>
+                <binary-origin>external/org.eclipse.lsp4j-0.10.0.jar</binary-origin>
             </class-path-extension>
             <class-path-extension>
                 <runtime-relative-path>ext/commons-io-2.5.jar</runtime-relative-path>
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerUtils.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerUtils.java
index ab14aed..c98193b 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerUtils.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/LspServerUtils.java
@@ -85,7 +85,7 @@
     public static final void avoidClientMessageThread(Lookup context) {
         NbCodeLanguageClient client = LspServerUtils.findLspClient(context);
         if (LspServerUtils.isClientResponseThread(client)) {
-            throw new IllegalStateException("Can not block LSP server message loop. Use RequestProcessor to run the calling code, or use notifyLater()");
+            throw new IllegalStateException("Cannot block LSP server message loop. Use RequestProcessor to run the calling code, or use notifyLater()");
         }
     }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Utils.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Utils.java
new file mode 100644
index 0000000..f79bcb8
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Utils.java
@@ -0,0 +1,209 @@
+/*
+ * 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 com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.LineMap;
+import com.sun.source.tree.Tree;
+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.util.Properties;
+import javax.lang.model.element.ElementKind;
+import javax.swing.text.Document;
+import javax.swing.text.StyledDocument;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.SymbolKind;
+import org.netbeans.api.editor.document.LineDocument;
+import org.netbeans.api.editor.document.LineDocumentUtils;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.openide.cookies.EditorCookie;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.modules.Places;
+import org.openide.text.NbDocument;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author lahvac
+ */
+public class Utils {
+
+    public 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?
+        }
+    }
+
+    public static Range treeRange(CompilationInfo info, Tree tree) {
+        long start = info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), tree);
+        long end   = info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), tree);
+        if (end == (-1)) {
+            end = start;
+        }
+        return new Range(createPosition(info.getCompilationUnit(), (int) start),
+                         createPosition(info.getCompilationUnit(), (int) end));
+    }
+
+    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();
+    }
+
+    public static synchronized String toUri(FileObject file) {
+        if (FileUtil.isArchiveArtifact(file)) {
+            //VS code cannot open jar:file: URLs, workaround:
+            File cacheDir = getCacheDir();
+            cacheDir.mkdirs();
+            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();
+    }
+
+    public static synchronized FileObject fromUri(String uri) throws MalformedURLException {
+        File cacheDir = getCacheDir();
+        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());
+    }
+
+    private static File getCacheDir() {
+        return Places.getCacheSubfile("java-server");
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/DebugAdapterContext.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/DebugAdapterContext.java
index 73c6dc2..7d4c2e7 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/DebugAdapterContext.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/DebugAdapterContext.java
@@ -33,6 +33,9 @@
 
 import org.netbeans.modules.java.lsp.server.debugging.breakpoints.BreakpointsManager;
 import org.netbeans.modules.java.lsp.server.debugging.launch.NbDebugSession;
+import org.netbeans.modules.java.lsp.server.progress.LspInternalHandle;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.openide.util.Pair;
 
 public final class DebugAdapterContext {
 
@@ -49,9 +52,11 @@
     private Charset debuggeeEncoding;
     private boolean isVmStopOnEntry = false;
     private boolean isDebugMode = true;
+    private InternalHandle processExecutorHandle;
 
     private final AtomicInteger lastSourceReferenceId = new AtomicInteger(0);
-    private final Map<Integer, String> sourceReferences = new ConcurrentHashMap<>();
+    private final Map<Integer, Pair<URI, String>> sourcesById = new ConcurrentHashMap<>();
+    private final Map<URI, Integer> sourceReferences = new ConcurrentHashMap<>();
 
     private final NBConfigurationSemaphore configurationSemaphore = new NBConfigurationSemaphore();
     private final NbSourceProvider sourceProvider = new NbSourceProvider(this);
@@ -114,6 +119,25 @@
     void setClientPathsAreUri(boolean clientPathsAreUri) {
         this.clientPathsAreUri = clientPathsAreUri;
     }
+    
+    public boolean requestProcessTermination() {
+        InternalHandle ih;
+        synchronized (this) {
+            ih = processExecutorHandle;
+        }
+        if (ih != null) {
+            ((LspInternalHandle)ih).forceRequestCancel();
+            return true;
+        } else {
+            return false;
+        }
+    }
+    
+    public void setProcessExecutorHandle(InternalHandle h) {
+        synchronized (this) {
+            this.processExecutorHandle = h;
+        }
+    }
 
     public String getClientPath(String debuggerPath) {
         if (clientPathsAreUri == debuggerPathsAreUri) {
@@ -179,13 +203,27 @@
         this.sourcePaths = sourcePaths;
     }
 
-    public String getSourceUri(int sourceReference) {
-        return sourceReferences.get(sourceReference);
+    public URI getSourceUri(int sourceReference) {
+        Pair<URI, String> sourceInfo = sourcesById.get(sourceReference);
+        if (sourceInfo != null) {
+            return sourceInfo.first();
+        } else {
+            return null;
+        }
     }
 
-    public int createSourceReference(String uri) {
-        int id = lastSourceReferenceId.incrementAndGet();
-        sourceReferences.put(id, uri);
+    public String getSourceMimeType(int sourceReference) {
+        Pair<URI, String> sourceInfo = sourcesById.get(sourceReference);
+        if (sourceInfo != null) {
+            return sourceInfo.second();
+        } else {
+            return null;
+        }
+    }
+
+    public int createSourceReference(URI uri, String mimeType) {
+        int id = sourceReferences.computeIfAbsent(uri, u -> lastSourceReferenceId.incrementAndGet());
+        sourcesById.put(id, Pair.of(uri, mimeType));
         return id;
     }
 
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/Debugger.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/Debugger.java
index 81374ad..2f91d13 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/Debugger.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/Debugger.java
@@ -24,8 +24,18 @@
 import java.util.concurrent.Future;
 import org.eclipse.lsp4j.debug.launch.DSPLauncher;
 import org.eclipse.lsp4j.debug.services.IDebugProtocolClient;
+import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
 import org.eclipse.lsp4j.jsonrpc.Launcher;
+import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
+import org.eclipse.lsp4j.jsonrpc.MessageIssueException;
+import org.eclipse.lsp4j.jsonrpc.messages.Message;
+import org.netbeans.modules.java.lsp.server.progress.OperationContext;
 import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.AbstractLookup;
+import org.openide.util.lookup.InstanceContent;
+import org.openide.util.lookup.Lookups;
+import org.openide.util.lookup.ProxyLookup;
 
 /**
  *
@@ -39,7 +49,9 @@
     public static void startDebugger(InputStream in, OutputStream out) {
         final DebugAdapterContext context = new DebugAdapterContext();
         NbProtocolServer server = new NbProtocolServer(context);
-        Launcher<IDebugProtocolClient> serverLauncher = DSPLauncher.createServerLauncher(server, in, out);
+        
+        Launcher<IDebugProtocolClient> serverLauncher = DSPLauncher.createServerLauncher(
+                server, in, out, null, ConsumeWithLookup::new);
         context.setClient(serverLauncher.getRemoteProxy());
         Future<Void> runningServer = serverLauncher.startListening();
         try {
@@ -48,4 +60,41 @@
             Exceptions.printStackTrace(ex);
         }
     }
+    
+    private static class ConsumeWithLookup implements MessageConsumer {
+        private final MessageConsumer delegate;
+        private OperationContext topContext;
+
+        public ConsumeWithLookup(MessageConsumer delegate) {
+            this.delegate = delegate;
+        }
+        
+        @Override
+        public void consume(Message message) throws MessageIssueException, JsonRpcException {
+            InstanceContent ic = new InstanceContent();
+            ProxyLookup ll = new ProxyLookup(new AbstractLookup(ic), Lookup.getDefault());
+            // HACK: piggyback on LSP's client.
+            if (topContext == null) {
+                topContext = OperationContext.find(null);
+            }
+            final OperationContext ctx;
+            
+            if (topContext != null) {
+                ctx = topContext.operationContext();
+                ctx.disableCancels();
+                ic.add(ctx);
+            } else {
+                ctx = null;
+            }
+            Lookups.executeWith(ll, () -> {
+                try {
+                    delegate.consume(message);
+                } finally {
+                    if (ctx != null) {
+                        ctx.stop();
+                    }
+                }
+            });
+        }
+    }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java
index bc6793b..73d83fc 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java
@@ -18,7 +18,11 @@
  */
 package org.netbeans.modules.java.lsp.server.debugging;
 
+import java.io.File;
+import java.net.MalformedURLException;
 import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -286,11 +290,33 @@
                     stackFrame.setId(frameId);
                     stackFrame.setName(frame.getName());
                     URI sourceURI = frame.getSourceURI();
-                    if (sourceURI != null && sourceURI.getPath() != null) {
+                    if (sourceURI != null) {
                         Source source = new Source();
-                        source.setName(Paths.get(sourceURI).getFileName().toString());
-                        source.setPath(sourceURI.getPath());
-                        source.setSourceReference(0);
+                        String scheme = sourceURI.getScheme();
+                        if (null == scheme || scheme.isEmpty() || "file".equalsIgnoreCase(scheme)) {
+                            source.setName(Paths.get(sourceURI).getFileName().toString());
+                            source.setPath(sourceURI.getPath());
+                            source.setSourceReference(0);
+                        } else {
+                            int ref = context.createSourceReference(sourceURI, frame.getSourceMimeType());
+                            String path = sourceURI.getPath();
+                            if (path == null) {
+                                path = sourceURI.getSchemeSpecificPart();
+                            }
+                            if (path != null) {
+                                int sepIndex = Math.max(path.lastIndexOf('/'), path.lastIndexOf(File.separatorChar));
+                                source.setName(path.substring(sepIndex + 1));
+                                if ("jar".equalsIgnoreCase(scheme)) {
+                                    try {
+                                        path = new URI(path).getPath();
+                                    } catch (URISyntaxException ex) {
+                                        // ignore, we just tried
+                                    }
+                                }
+                                source.setPath(path);
+                            }
+                            source.setSourceReference(ref);
+                        }
                         stackFrame.setSource(source);
                     }
                     stackFrame.setLine(line);
@@ -343,10 +369,10 @@
         if (sourceReference <= 0) {
             ErrorUtilities.completeExceptionally(future, "SourceRequest: property 'sourceReference' is missing, null, or empty", ResponseErrorCode.InvalidParams);
         } else {
-            String uri = context.getSourceUri(sourceReference);
+            URI uri = context.getSourceUri(sourceReference);
             NbSourceProvider sourceProvider = context.getSourceProvider();
             SourceResponse response = new SourceResponse();
-            response.setMimeType("text/x-java"); // Set mimeType to tell clients to recognize the source contents as java source
+            response.setMimeType(context.getSourceMimeType(sourceReference));
             response.setContent(sourceProvider.getSourceContents(uri));
             future.complete(response);
         }
@@ -379,13 +405,13 @@
             String expression = args.getExpression();
             if (StringUtils.isBlank(expression)) {
                 throw ErrorUtilities.createResponseErrorException(
-                    "Failed to evaluate. Reason: Empty expression cannot be evaluated.",
+                    "Empty expression cannot be evaluated.",
                     ResponseErrorCode.InvalidParams);
             }
             NbFrame stackFrame = (NbFrame) context.getThreadsProvider().getThreadObjects().getObject(args.getFrameId());
             if (stackFrame == null) {
                 throw ErrorUtilities.createResponseErrorException(
-                    "Failed to evaluate. Reason: Unknown frame " + args.getFrameId(),
+                    "Unknown frame " + args.getFrameId(),
                     ResponseErrorCode.InvalidParams);
             }
             stackFrame.getDVFrame().makeCurrent(); // The evaluation is always performed with respect to the current frame
@@ -397,7 +423,7 @@
                 variable = debugger.evaluate(expression);
             } catch (InvalidExpressionException ex) {
                 throw ErrorUtilities.createResponseErrorException(
-                    "Failed to evaluate. Reason: " + ex.getLocalizedMessage(),
+                    ex.getLocalizedMessage(),
                     ResponseErrorCode.ParseError);
             }
             EvaluateResponse response = new EvaluateResponse();
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbSourceProvider.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbSourceProvider.java
index e4d792b..6303248 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbSourceProvider.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbSourceProvider.java
@@ -19,7 +19,13 @@
 package org.netbeans.modules.java.lsp.server.debugging;
 
 import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URISyntaxException;
+import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -67,9 +73,25 @@
         });
     }
 
-    public String getSourceContents(String arg0) {
-        LOG.log(Level.INFO, "SourceContent {0}", arg0);
-        throw new UnsupportedOperationException("Not supported yet.");
+    public String getSourceContents(URI uri) {
+        LOG.log(Level.INFO, "SourceContent {0}", uri);
+        URL url;
+        try {
+            url = uri.toURL();
+        } catch (MalformedURLException ex) {
+            return ex.getLocalizedMessage();
+        }
+        StringBuilder content = new StringBuilder();
+        char[] buffer = new char[8192];
+        try (Reader r = new InputStreamReader(url.openConnection().getInputStream())) {
+            int l;
+            while ((l = r.read(buffer)) > 0) {
+                content.append(buffer, 0, l);
+            }
+        } catch (IOException ex) {
+            return ex.getLocalizedMessage();
+        }
+        return content.toString();
     }
     
     public Source getSource(String sourceName, String debuggerURI) {
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/breakpoints/NbBreakpoint.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/breakpoints/NbBreakpoint.java
index 2cfef05..0a36344 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/breakpoints/NbBreakpoint.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/breakpoints/NbBreakpoint.java
@@ -19,6 +19,7 @@
 package org.netbeans.modules.java.lsp.server.debugging.breakpoints;
 
 import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
 import java.util.HashMap;
 import java.util.Map;
@@ -35,6 +36,7 @@
 import org.netbeans.api.debugger.jpda.LineBreakpoint;
 import org.netbeans.modules.debugger.jpda.truffle.breakpoints.TruffleLineBreakpoint;
 import org.netbeans.modules.java.lsp.server.debugging.DebugAdapterContext;
+import org.openide.filesystems.URLMapper;
 
 /**
  *
@@ -56,6 +58,15 @@
 
     public NbBreakpoint(Source source, String sourceURL, int line, int hitCount, String condition, String logMessage, DebugAdapterContext context) {
         this.source = source;
+        Integer ref = source.getSourceReference();
+        if (ref != null && ref != 0) {
+            URI uri = context.getSourceUri(ref);
+            if (uri != null) {
+                try {
+                    sourceURL = uri.toURL().toString();
+                } catch (MalformedURLException ex) {}
+            }
+        }
         this.sourceURL = sourceURL;
         this.line = line;
         this.hitCount = hitCount;
@@ -116,9 +127,9 @@
         breakpoint.addPropertyChangeListener(Breakpoint.PROP_VALIDITY, evt -> {
             updateValid(breakpoint, true);
         });
-        updateValid(breakpoint, false);
         DebuggerManager d = DebuggerManager.getDebuggerManager();
         d.addBreakpoint(breakpoint);
+        updateValid(breakpoint, false);
         this.breakpoint = breakpoint;
         return CompletableFuture.completedFuture(this);
     }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDisconnectRequestHandler.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDisconnectRequestHandler.java
index 894ece4..c7436f7 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDisconnectRequestHandler.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDisconnectRequestHandler.java
@@ -45,6 +45,8 @@
             } else {
                 debugSession.detach();
             }
+        } else {
+            context.requestProcessTermination();
         }
     }
 
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
index 14a0fa1..92ea02f 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
@@ -22,6 +22,7 @@
 import java.beans.PropertyChangeListener;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
@@ -41,6 +42,8 @@
 import org.netbeans.api.project.Project;
 import org.netbeans.modules.java.lsp.server.debugging.DebugAdapterContext;
 import org.netbeans.modules.java.lsp.server.debugging.NbSourceProvider;
+import org.netbeans.modules.java.lsp.server.progress.OperationContext;
+import org.netbeans.modules.progress.spi.InternalHandle;
 import org.netbeans.spi.project.ActionProgress;
 import org.netbeans.spi.project.ActionProvider;
 import org.openide.filesystems.FileObject;
@@ -57,6 +60,10 @@
     public abstract void preLaunch(Map<String, Object> launchArguments, DebugAdapterContext context);
 
     public abstract void postLaunch(Map<String, Object> launchArguments, DebugAdapterContext context);
+    
+    protected void notifyFinished(DebugAdapterContext ctx, boolean success) {
+        // no op.
+    }
 
     public final CompletableFuture<Void> nbLaunch(FileObject toRun, DebugAdapterContext context, boolean debug, Consumer<NbProcessConsole.ConsoleMessage> consoleMessages) {
         CompletableFuture<Void> launchFuture = new CompletableFuture<>();
@@ -100,6 +107,7 @@
                 @Override
                 public void finished(boolean success) {
                     ioContext.stop();
+                    notifyFinished(context, success);
                 }
             };
             Lookup launchCtx = new ProxyLookup(
@@ -109,6 +117,12 @@
             );
             Lookups.executeWith(launchCtx, () -> {
                 providerAndCommand.first().invokeAction(providerAndCommand.second(), Lookups.fixed(toRun, ioContext, progress));
+                
+                OperationContext ctx = OperationContext.find(Lookup.getDefault());
+                List<InternalHandle> created = ctx.getOperationHandles();
+                if (!created.isEmpty()) {
+                    context.setProcessExecutorHandle(created.get(0));
+                }
             });
         }).exceptionally((t) -> {
             launchFuture.completeExceptionally(t);
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchWithoutDebuggingDelegate.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchWithoutDebuggingDelegate.java
index 5fcc559..86f42e2 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchWithoutDebuggingDelegate.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchWithoutDebuggingDelegate.java
@@ -36,6 +36,12 @@
     }
 
     @Override
+    protected void notifyFinished(DebugAdapterContext ctx, boolean success) {
+        super.notifyFinished(ctx, success);
+        onFinishCallback.accept(ctx);
+    }
+
+    @Override
     public void postLaunch(Map<String, Object> launchArguments, DebugAdapterContext context) {
         // Do not send InitializedEvent, so that we do not get DAP requests.
         return;
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/LspInternalHandle.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/LspInternalHandle.java
new file mode 100644
index 0000000..62a5c80
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/LspInternalHandle.java
@@ -0,0 +1,219 @@
+/*
+ * 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.progress;
+
+import java.lang.reflect.Field;
+import java.text.MessageFormat;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.eclipse.lsp4j.ProgressParams;
+import org.eclipse.lsp4j.WorkDoneProgressBegin;
+import org.eclipse.lsp4j.WorkDoneProgressEnd;
+import org.eclipse.lsp4j.WorkDoneProgressNotification;
+import org.eclipse.lsp4j.WorkDoneProgressReport;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.modules.progress.spi.Controller;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.ProgressEvent;
+import org.openide.util.Cancellable;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author sdedic
+ */
+public class LspInternalHandle extends InternalHandle {
+    private static final Logger LOG = Logger.getLogger(LspInternalHandle.class.getName());
+    
+    private final NbCodeLanguageClient  lspClient;
+    private final OperationContext opContext;
+    private final Function<InternalHandle, Controller> controllerProvider;
+    
+    private CompletableFuture<Either<String, Number>> tokenPromise;
+    private int reportedPercentage;
+    
+    /**
+     * Set from the START event handler. Workaround for NETBEANS-5167 if start event is skipped.
+     */
+    private boolean started;
+    
+    private boolean explicitCancelRequest;
+    
+    private static Field controllerField;
+
+    public LspInternalHandle(OperationContext opContext, 
+            NbCodeLanguageClient  lspClient, Function<InternalHandle, Controller> controllerProvider,
+            String displayName, Cancellable cancel, boolean userInitiated) {
+        super(displayName, cancel, userInitiated);
+        this.lspClient = lspClient;
+        this.opContext = opContext;
+        this.controllerProvider = controllerProvider;
+        if (opContext != null) {
+            opContext.internalHandleCreated(this);
+        }
+    }
+
+    public void forceRequestCancel() {
+        explicitCancelRequest = true;
+        requestCancel();
+    }
+
+    @Override
+    public boolean isAllowCancel() {
+        return super.isAllowCancel() && (explicitCancelRequest || !opContext.isDisableCancels());
+    }
+
+    public OperationContext getContext() {
+        return opContext;
+    }
+    
+    private synchronized Controller findController() {
+        if (controllerField == null) {
+            try {
+                controllerField = InternalHandle.class.getDeclaredField("controller");
+                controllerField.setAccessible(true);
+            } catch (NoSuchFieldException | SecurityException ex) {
+                throw new IllegalStateException();
+            }
+        }
+        try {
+            return (Controller)controllerField.get(this);
+        } catch (IllegalArgumentException | IllegalAccessException ex) {
+            Exceptions.printStackTrace(ex);
+            return null;
+        }
+    }
+
+    @Override
+    public synchronized void start(String message, int workunits, long estimate) {
+        Controller attached = findController();
+        if (attached == null) {
+            setController(controllerProvider.apply(this));
+        }
+        super.start(message, workunits, estimate);
+    }
+
+    @Override
+    public void requestView() {
+        // no op
+    }
+
+    @Override
+    public boolean isCustomPlaced() {
+        return false;
+    }
+
+    @Override
+    public boolean isAllowView() {
+        // by default not supported.
+        return false;
+    }
+    
+    String id() {
+        return Integer.toHexString(System.identityHashCode(this));
+    }
+    
+    void sendStartMessage(ProgressEvent e) {
+        WorkDoneProgressBegin start  = new WorkDoneProgressBegin();
+        boolean determinate = getTotalUnits() > 0;
+        start.setCancellable(isAllowCancel());
+        start.setTitle(getDisplayName());
+        if (determinate) {
+            double percent = e.getPercentageDone();
+            if (percent != -1) {
+                start.setPercentage(cleverFloor(percent));
+            } else {
+                start.setPercentage(0);
+            }
+        }
+        LOG.log(Level.FINE, "Starting progress: {0}", this);
+        started = true;
+        notify(start);
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("LspProgress@").append(id()).append("[");
+        sb.append("display: ").append(getDisplayName()).
+            append(", total: ").append(getTotalUnits()).
+            append(", percent: ").append(String.format("%3.2f", getPercentageDone())).
+            append(", state: ").append(getState());
+        sb.append("]");
+        return sb.toString();
+    }
+    
+    static int cleverFloor(double percent) {
+        return (int)(percent > 90.0 ? Math.floor(percent) : Math.round(percent));
+    }
+    
+    void sendProgress(ProgressEvent e) {
+        if (!started) {
+            sendStartMessage(e);
+            return;
+        }
+        WorkDoneProgressReport report = new WorkDoneProgressReport();
+
+        double percent = e.getPercentageDone();
+        if (percent != -1) {
+            report.setPercentage(cleverFloor(percent));
+        }
+        report.setMessage(e.getMessage());
+        report.setCancellable(isAllowCancel());
+        notify(report);
+    }
+    
+    Either<String, Number> token() {
+        if (tokenPromise == null || tokenPromise.isCompletedExceptionally()) {
+            return null;
+        } else {
+            return tokenPromise.getNow(null);
+        }
+    }
+    
+    void sendFinish(ProgressEvent e) {
+        WorkDoneProgressEnd end = new WorkDoneProgressEnd();
+        end.setMessage(e.getMessage());
+        notify(end);
+        opContext.removeHandle(token(), this);
+    }
+    
+    CompletableFuture<Either<String, Number>> findProgressToken() {
+        if (tokenPromise != null) {
+            return tokenPromise;
+        }
+        return tokenPromise = opContext.acquireOrObtainToken(this);
+    }
+
+    void notify(WorkDoneProgressNotification msg) {
+        findProgressToken().thenAccept(token -> {
+            LOG.log(Level.FINER, () -> 
+                    MessageFormat.format("Sending progress {0}, msg: {1}", 
+                        id(), msg
+                    )
+            );
+            ProgressParams param = new ProgressParams(token, msg);
+            lspClient.notifyProgress(param);
+        });
+    }
+    
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/LspProgressUIWorker.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/LspProgressUIWorker.java
new file mode 100644
index 0000000..9919a5a
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/LspProgressUIWorker.java
@@ -0,0 +1,91 @@
+/*
+ * 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.progress;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.ProgressEvent;
+import org.netbeans.modules.progress.spi.ProgressUIWorkerWithModel;
+import org.netbeans.modules.progress.spi.TaskModel;
+
+/**
+ *
+ * @author sdedic
+ */
+public class LspProgressUIWorker implements ProgressUIWorkerWithModel{
+    private static final Logger LOG = Logger.getLogger(LspProgressUIWorker.class.getName());
+    
+    private TaskModel taskModel;
+    
+    public LspProgressUIWorker() {
+    }
+
+    @Override
+    public void setModel(TaskModel model) {
+        this.taskModel = model;
+    }
+
+    @Override
+    public void showPopup() {
+        // ??
+    }
+
+    @Override
+    public void processProgressEvent(ProgressEvent event) {
+        InternalHandle h = event.getSource();
+        if (!(h instanceof LspInternalHandle)) {
+            // sorry ...
+            return;
+        }
+        LspInternalHandle lsHandle = (LspInternalHandle)h;
+        
+        switch (event.getType()) {
+            case ProgressEvent.TYPE_START:
+                lsHandle.sendStartMessage(event);
+                break;
+                
+            case ProgressEvent.TYPE_PROGRESS:
+                lsHandle.sendProgress(event);
+                break;
+                
+            case ProgressEvent.TYPE_SWITCH:
+            case ProgressEvent.TYPE_SILENT:
+            case ProgressEvent.TYPE_REQUEST_STOP:
+                // ignore
+                break;
+                
+            case ProgressEvent.TYPE_FINISH:
+                lsHandle.sendFinish(event);
+                break;
+            default:
+                LOG.log(Level.INFO, "Unexpected progress event type for {0}: {1}", new Object[] {
+                    lsHandle, event.getType()
+                });
+                break;
+        }
+    }
+
+    @Override
+    public void processSelectedProgressEvent(ProgressEvent event) {
+        // No support from LSP / VScode UI. Most probably can be handled
+        // on the client side, if it stacks progresses somehow.
+    }
+    
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/OperationContext.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/OperationContext.java
new file mode 100644
index 0000000..38d6d83
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/OperationContext.java
@@ -0,0 +1,272 @@
+/*
+ * 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.progress;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import org.eclipse.lsp4j.WorkDoneProgressCreateParams;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.modules.progress.spi.Controller;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.TaskModel;
+import org.openide.util.Lookup;
+
+/**
+ * Operation context for reporting progress. Initially the instance is
+ * request-scoped in the initial operation request. Since it becomes part of
+ * the default Lookup, it should be handed  into any RequestProcessor-forked
+ * tasks initiated from the operation request.
+ * 
+ * @author sdedic
+ */
+public final class OperationContext {
+    private static Reference<OperationContext> lastCtx = new WeakReference<>(null);
+    
+    /**
+     * Initial context
+     */
+    private static Reference<OperationContext> initialCtx = new WeakReference<>(null);
+    
+    /**
+     * LSP client that issues progress tokens.
+     */
+    private final NbCodeLanguageClient  client;
+    
+    /**
+     * Progress token for reporting progress to the client. The token is valid
+     * for a single ProgressHandle created.
+     */
+    private Either<String, Number> progressToken;
+    
+    /**
+     * Token to report partial results to the client.
+     */
+    private String partialResultsToken;
+    
+    /**
+     * The controller that collects tasks in progress.
+     */
+    private final Controller  progressController;
+    
+    /**
+     * Handles created during the operation.
+     */
+    private final List<InternalHandle>  createdHandles = new ArrayList<>();
+    
+    private boolean finished;
+    
+    private final OperationContext top;
+    
+    private boolean disableCancels;
+    
+    OperationContext(OperationContext top, NbCodeLanguageClient client, Controller controller) {
+        this.client = client;
+        this.progressController = controller;
+        this.top = top == null ? this : top;
+    }
+    
+    private TaskModel getTaskModel() {
+        return progressController.getModel();
+    }
+    
+    /**
+     * Acquires a progress token. The token is either issued by the client,
+     * from its original operation request, or the server will initiate a 
+     * progress on the client, asking it to issue a token.
+     * @return 
+     */
+    public synchronized Either<String, Number> acquireProgressToken() {
+        Either<String, Number> t = progressToken;
+        progressToken = null;
+        return t;
+    }
+    
+    public String getPartialResultsToken() {
+        return partialResultsToken;
+    }
+    
+    /**
+     * INTERNAL. Do not call this method; it will be likely removed
+     * soon.
+     * @param s 
+     */
+    public void setProgressToken(Either<String, Number> s) {
+        progressToken = s;
+    }
+    
+    public OperationContext operationContext() {
+        OperationContext ctx = new OperationContext(this, client, progressController);
+        lastCtx = new WeakReference<>(ctx);
+        return ctx;
+    }
+    
+    public static synchronized OperationContext create(NbCodeLanguageClient client) {
+        OperationContext ctx = new OperationContext(null, client, 
+            new Controller(new LspProgressUIWorker()));
+        lastCtx = new WeakReference<>(ctx);
+        ctx.registerInitialContext();
+        return ctx;
+    }
+    
+    void registerInitialContext() {
+        initialCtx = new WeakReference<>(this);
+    }
+
+    public NbCodeLanguageClient getClient() {
+        return client;
+    }
+    
+    public static synchronized OperationContext find(Lookup lkp) {
+        OperationContext ctx;
+        Lookup def = Lookup.getDefault();
+        if (lkp == null) {
+            lkp = def;
+        }
+        ctx = lkp.lookup(OperationContext.class);
+        if (ctx != null) {
+            return ctx;
+        }
+        ctx = lastCtx.get();
+        if (ctx == null)  {
+            ctx = initialCtx.get();
+            if (ctx == null) {
+                ctx = create(lkp.lookup(NbCodeLanguageClient.class));
+            }
+        }
+        lastCtx = new WeakReference<>(ctx);
+        return ctx;
+    }
+    
+    public void stop() {
+        acquireProgressToken();
+        if (!isActive()) {
+            return;
+        }
+        if (this != top) {
+            finished = true;
+        }
+    }
+    
+    public boolean isActive() {
+        return Lookup.getDefault().lookup(OperationContext.class) == this;
+    }
+    
+    /**
+     * Finds an active handle identified by client's progress token.
+     * @param token token
+     * @return handle instance or {@code null}
+     */
+    public InternalHandle  findActiveHandle(Either<String, Number> token) {
+        if (top != this) {
+            return top.findActiveHandle(token);
+        }
+        synchronized (this) {
+            return handles.get(token);
+        }
+    }
+    
+    private Either<String, Number> addHandle(Either<String, Number> token, InternalHandle h) {
+        top.registerHandle(token, h);
+        return token;
+    }
+    
+    void removeHandle(Either<String, Number> token, InternalHandle h) {
+        if (top != this) {
+            top.unregisterHandle(token, h);
+        } else {
+            unregisterHandle(token, h);
+        }
+    }
+    
+    private Map<Either<String, Number>, InternalHandle> handles = new HashMap<>();
+    
+    private synchronized void unregisterHandle(Either<String, Number> token, InternalHandle h) {
+        if (token == null) {
+            handles.values().remove(h);
+        } else {
+            handles.remove(token);
+        }
+    }
+    
+    private synchronized void registerHandle(Either<String, Number> token, InternalHandle h) {
+        handles.put(token, h);
+    }
+    
+    CompletableFuture<Either<String, Number>> acquireOrObtainToken(InternalHandle h) {
+        Either<String, Number> t = acquireProgressToken();
+        if (t != null) {
+            return CompletableFuture.completedFuture(addHandle(t, h));
+        } else {
+            WorkDoneProgressCreateParams params = new WorkDoneProgressCreateParams(
+                Either.forLeft(UUID.randomUUID().toString())
+            );
+            CompletableFuture<Either<String, Number>> tokenPromise = client.
+                    createProgress(params).thenApply(v -> params.getToken());
+            return tokenPromise.thenApply((p) -> {
+               synchronized (this) {
+                   return addHandle(p, h);
+               } 
+            });
+        }
+    }
+    
+    public void disableCancels() {
+        if (top != this) {
+            disableCancels = true;
+        }
+    }
+
+    public boolean isDisableCancels() {
+        return disableCancels;
+    }
+    
+    public static OperationContext getHandleContext(InternalHandle h) {
+        if (!(h instanceof LspInternalHandle)) {
+            return null;
+        }
+        return ((LspInternalHandle)h).getContext();
+    }
+    
+    public Collection<InternalHandle> getAllActiveHandles() {
+        if (top != this) {
+            return top.getAllActiveHandles();
+        }
+        synchronized (this) {
+            return new ArrayList<>(handles.values());
+        }
+    }
+    
+    public synchronized List<InternalHandle> getOperationHandles() {
+        return new ArrayList<>(createdHandles);
+    }
+    
+    void internalHandleCreated(InternalHandle h) {
+        synchronized (this) {
+            createdHandles.add(h);
+        }
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeGenerator.java
new file mode 100644
index 0000000..9c741f8
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeGenerator.java
@@ -0,0 +1,215 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import com.sun.source.tree.LineMap;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+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.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.Command;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementHandle;
+import org.netbeans.modules.editor.java.Utilities;
+import org.netbeans.modules.java.source.ElementHandleAccessor;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+public abstract class CodeGenerator {
+
+    public static final String CODE_GENERATOR_KIND = "source.generate";
+    protected static final String ERROR = "<error>"; //NOI18N
+
+    public abstract List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params);
+
+    public abstract Set<String> getCommands();
+
+    public abstract CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments);
+
+    protected static int getOffset(CompilationInfo info, Position pos) {
+        LineMap lm = info.getCompilationUnit().getLineMap();
+        return (int) lm.getPosition(pos.getLine() + 1, pos.getCharacter() + 1);
+    }
+
+    protected static CodeAction createCodeAction(String name, String kind, String command, Object... args) {
+        CodeAction action = new CodeAction(name);
+        action.setKind(kind);
+        action.setCommand(new Command(name, command, Arrays.asList(args)));
+        return action;
+    }
+
+    protected static String createLabel(CompilationInfo info, TypeElement e) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(e.getSimpleName());
+        List<? extends TypeParameterElement> typeParams = e.getTypeParameters();
+        if (typeParams != null && !typeParams.isEmpty()) {
+            sb.append("<"); // NOI18N
+            for(Iterator<? extends TypeParameterElement> it = typeParams.iterator(); it.hasNext();) {
+                TypeParameterElement tp = it.next();
+                sb.append(tp.getSimpleName());
+                List<? extends TypeMirror> bounds = tp.getBounds();
+                if (!bounds.isEmpty()) {
+                    if (bounds.size() > 1 || !"java.lang.Object".equals(bounds.get(0).toString())) { // NOI18N
+                        sb.append(" extends "); // NOI18N
+                        for (Iterator<? extends TypeMirror> bIt = bounds.iterator(); bIt.hasNext();) {
+                            sb.append(Utilities.getTypeName(info, bIt.next(), false));
+                            if (bIt.hasNext()) {
+                                sb.append(" & "); // NOI18N
+                            }
+                        }
+                    }
+                }
+                if (it.hasNext()) {
+                    sb.append(", "); // NOI18N
+                }
+            }
+            sb.append(">"); // NOI18N
+        }
+        return sb.toString();
+    }
+
+    protected static String createLabel(CompilationInfo info, VariableElement e) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(e.getSimpleName());
+        if (e.getKind() != ElementKind.ENUM_CONSTANT) {
+            sb.append(" : "); // NOI18N
+            sb.append(Utilities.getTypeName(info, e.asType(), false));
+        }
+        return sb.toString();
+    }
+
+    protected static String createLabel(CompilationInfo info, ExecutableElement e) {
+        StringBuilder sb = new StringBuilder();
+        if (e.getKind() == ElementKind.CONSTRUCTOR) {
+            sb.append(e.getEnclosingElement().getSimpleName());
+        } else {
+            sb.append(e.getSimpleName());
+        }
+        sb.append("("); // NOI18N
+        for (Iterator<? extends VariableElement> it = e.getParameters().iterator(); it.hasNext();) {
+            VariableElement param = it.next();
+            if (!it.hasNext() && e.isVarArgs() && param.asType().getKind() == TypeKind.ARRAY) {
+                sb.append(Utilities.getTypeName(info, ((ArrayType) param.asType()).getComponentType(), false));
+                sb.append("...");
+            } else {
+                sb.append(Utilities.getTypeName(info, param.asType(), false));
+            }
+            sb.append(" "); // NOI18N
+            sb.append(param.getSimpleName());
+            if (it.hasNext()) {
+                sb.append(", "); // NOI18N
+            }
+        }
+        sb.append(")"); // NOI18N
+        if (e.getKind() != ElementKind.CONSTRUCTOR) {
+            TypeMirror rt = e.getReturnType();
+            if (rt.getKind() != TypeKind.VOID) {
+                sb.append(" : "); // NOI18N
+                sb.append(Utilities.getTypeName(info, e.getReturnType(), false));
+            }
+        }
+        return sb.toString();
+    }
+
+    public static class ElementData {
+
+        private String kind;
+        private String[] signature;
+
+        public ElementData() {
+        }
+
+        public ElementData(Element element) {
+            ElementHandle<Element> handle = ElementHandle.create(element);
+            this.kind = handle.getKind().name();
+            this.signature = ElementHandleAccessor.getInstance().getJVMSignature(handle);
+        }
+
+        Element resolve(CompilationInfo info) {
+            ElementHandle handle = ElementHandleAccessor.getInstance().create(ElementKind.valueOf(kind), signature);
+            return handle.resolve(info);
+        }
+
+        @Pure
+        public String getKind() {
+            return kind;
+        }
+
+        public void setKind(final String kind) {
+            this.kind = kind;
+        }
+
+        @Pure
+        public String[] getSignature() {
+            return signature;
+        }
+
+        public void setSignature(final String[] signature) {
+            this.signature = signature;
+        }
+
+        @Override
+        @Pure
+        public int hashCode() {
+            int hash = 7;
+            hash = 97 * hash + Objects.hashCode(this.kind);
+            hash = 97 * hash + Arrays.deepHashCode(this.signature);
+            return hash;
+        }
+
+        @Override
+        @Pure
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            final ElementData other = (ElementData) obj;
+            if (this.kind != other.kind) {
+                return false;
+            }
+            if (!Arrays.deepEquals(this.signature, other.signature)) {
+                return false;
+            }
+            return true;
+        }
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ConstructorGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ConstructorGenerator.java
new file mode 100644
index 0000000..27e3dd8
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ConstructorGenerator.java
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.NestingKind;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 10)
+public final class ConstructorGenerator extends CodeGenerator {
+
+    public static final String GENERATE_CONSTRUCTOR =  "java.generate.constructor";
+
+    private final Set<String> commands = Collections.singleton(GENERATE_CONSTRUCTOR);
+    private final Gson gson = new Gson();
+
+    public ConstructorGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateConstructor=Generate Constructor...",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        if (only == null || !only.contains(CodeActionKind.Source)) {
+            return Collections.emptyList();
+        }
+        int offset = getOffset(info, params.getRange().getStart());
+        TreePath tp = info.getTreeUtilities().pathFor(offset);
+        tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+        if (tp == null) {
+            return Collections.emptyList();
+        }
+        TypeElement typeElement = (TypeElement) info.getTrees().getElement(tp);
+        if (typeElement == null || !typeElement.getKind().isClass() || NestingKind.ANONYMOUS.equals(typeElement.getNestingKind())) {
+            return Collections.emptyList();
+        }
+        final Set<? extends VariableElement> uninitializedFields = info.getTreeUtilities().getUninitializedFields(tp);
+        final List<ExecutableElement> inheritedConstructors = new ArrayList<>();
+        TypeMirror superClassType = typeElement.getSuperclass();
+        if (superClassType.getKind() == TypeKind.DECLARED) {
+            TypeElement superClass = (TypeElement) ((DeclaredType) superClassType).asElement();
+            Elements elements = info.getElements();
+            for (ExecutableElement executableElement : ElementFilter.constructorsIn(superClass.getEnclosedElements())) {
+                PackageElement currentPackage = elements.getPackageOf(typeElement);
+                PackageElement ctorPackage = elements.getPackageOf(executableElement);
+                Set<Modifier> ctorMods = executableElement.getModifiers();
+                if ((currentPackage != ctorPackage && !(ctorMods.contains(Modifier.PUBLIC) || ctorMods.contains(Modifier.PROTECTED)))
+                        || ctorMods.contains(Modifier.PRIVATE)) {
+                    continue;
+                }
+                inheritedConstructors.add(executableElement);
+            }
+        }
+        List<QuickPickItem> constructors;
+        if (typeElement.getKind() != ElementKind.ENUM && inheritedConstructors.size() == 1) {
+            if (uninitializedFields.isEmpty() && inheritedConstructors.get(0).getParameters().isEmpty()
+                    && ElementFilter.constructorsIn(typeElement.getEnclosedElements()).stream().filter(ctor -> ctor.getParameters().isEmpty() && !info.getElementUtilities().isSynthetic(ctor)).count() > 0) {
+                constructors = Collections.emptyList();
+            } else {
+                QuickPickItem item = new QuickPickItem(createLabel(info, inheritedConstructors.get(0)));
+                item.setUserData(new ElementData(inheritedConstructors.get(0)));
+                constructors = Collections.singletonList(item);
+            }
+        } else if (inheritedConstructors.size() > 1) {
+            constructors = new ArrayList<>(inheritedConstructors.size());
+            for (ExecutableElement constructorElement : inheritedConstructors) {
+                QuickPickItem item = new QuickPickItem(createLabel(info, constructorElement));
+                item.setUserData(new ElementData(constructorElement));
+                constructors.add(item);
+            }
+        } else {
+            constructors = Collections.emptyList();
+        }
+        List<QuickPickItem> fields;
+        if (uninitializedFields.isEmpty()) {
+            fields = Collections.emptyList();
+        } else {
+            fields = new ArrayList<>();
+            for (VariableElement variableElement : uninitializedFields) {
+                QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+                item.setUserData(new ElementData(variableElement));
+                fields.add(item);
+            }
+        }
+        if (constructors.isEmpty() && fields.isEmpty()) {
+            return Collections.emptyList();
+        }
+        String uri = Utils.toUri(info.getFileObject());
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateConstructor(), CODE_GENERATOR_KIND, GENERATE_CONSTRUCTOR, uri, offset, constructors, fields));
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectSuperConstructor=Select super constructor",
+    })
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        if (arguments.size() > 3) {
+            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+            List<QuickPickItem> constructors = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(3)), QuickPickItem[].class));
+            if (constructors.size() < 2 && fields.isEmpty()) {
+                generate(client, uri, offset, constructors, fields);
+            } else {
+                if (constructors.size() > 1) {
+                    client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectSuperConstructor(), true, constructors)).thenAccept(selected -> {
+                        if (selected != null) {
+                            selectFields(client, uri, offset, selected, fields);
+                        }
+                    });
+                } else {
+                    selectFields(client, uri, offset, constructors, fields);
+                }
+            }
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+
+    @NbBundle.Messages({
+        "DN_SelectConstructorFields=Select fields to be initialized by constructor",
+    })
+    private void selectFields(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> constructors, List<QuickPickItem> fields) {
+        if (!fields.isEmpty()) {
+            client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectConstructorFields(), true, fields)).thenAccept(selected -> {
+                if (selected != null) {
+                    generate(client, uri, offset, constructors, selected);
+                }
+            });
+        } else {
+            generate(client, uri, offset, constructors, fields);
+        }
+    }
+
+    @NbBundle.Messages({
+        "DN_ConstructorAlreadyExists=Given constructor already exists",
+    })
+    private void generate(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> constructors, List<QuickPickItem> fields) {
+        try {
+            FileObject file = Utils.fromUri(uri);
+            JavaSource js = JavaSource.forFileObject(file);
+            if (js == null) {
+                throw new IOException("Cannot get JavaSource for: " + uri);
+            }
+            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                wc.toPhase(JavaSource.Phase.RESOLVED);
+                TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+                if (tp != null) {
+                    List<ExecutableElement> selectedConstructors = constructors.stream().map(item -> {
+                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                        return (ExecutableElement)data.resolve(wc);
+                    }).collect(Collectors.toList());
+                    List<VariableElement> selectedFields = fields.stream().map(item -> {
+                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                        return (VariableElement)data.resolve(wc);
+                    }).collect(Collectors.toList());
+                    GeneratorUtils.generateConstructors(wc, tp, selectedFields, selectedConstructors, -1);
+                }
+            });
+            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+        } catch (GeneratorUtils.DuplicateMemberException dme) {
+            client.showMessage(new MessageParams(MessageType.Info, Bundle.DN_ConstructorAlreadyExists()));
+        } catch (IOException | IllegalArgumentException ex) {
+            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+        }
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DelegateMethodGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DelegateMethodGenerator.java
new file mode 100644
index 0000000..d59cec4
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DelegateMethodGenerator.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.tree.Scope;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 60)
+public final class DelegateMethodGenerator extends CodeGenerator {
+
+    public static final String GENERATE_DELEGATE_METHOD =  "java.generate.delegateMethod";
+
+    private final Set<String> commands = Collections.singleton(GENERATE_DELEGATE_METHOD);
+    private final Gson gson = new Gson();
+
+    public DelegateMethodGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateDelegateMethod=Generate Delegate Method...",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        if (only == null || !only.contains(CodeActionKind.Source)) {
+            return Collections.emptyList();
+        }
+        int offset = getOffset(info, params.getRange().getStart());
+        TreePath tp = info.getTreeUtilities().pathFor(offset);
+        tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+        if (tp == null) {
+            return Collections.emptyList();
+        }
+        TypeElement typeElement = (TypeElement) info.getTrees().getElement(tp);
+        if (typeElement == null || !typeElement.getKind().isClass()) {
+            return Collections.emptyList();
+        }
+        Elements elements = info.getElements();
+        Trees trees = info.getTrees();
+        Scope scope = trees.getScope(tp);
+        List<QuickPickItem> fields = new ArrayList<>();
+        TypeElement cls;
+        while (scope != null && (cls = scope.getEnclosingClass()) != null) {
+            DeclaredType type = (DeclaredType) cls.asType();
+            for (VariableElement field : ElementFilter.fieldsIn(elements.getAllMembers(cls))) {
+                TypeMirror fieldType = field.asType();
+                if (!ERROR.contentEquals(field.getSimpleName()) && !fieldType.getKind().isPrimitive() && fieldType.getKind() != TypeKind.ARRAY
+                        && (fieldType.getKind() != TypeKind.DECLARED || ((DeclaredType)fieldType).asElement() != cls) && trees.isAccessible(scope, field, type)) {
+                    QuickPickItem item = new QuickPickItem(createLabel(info, field));
+                    item.setUserData(new ElementData(field));
+                    fields.add(item);
+                }
+            }
+            scope = scope.getEnclosingScope();
+        }
+        if (fields.isEmpty()) {
+            return Collections.emptyList();
+        }
+        String uri = Utils.toUri(info.getFileObject());
+        QuickPickItem typeItem = new QuickPickItem(createLabel(info, typeElement));
+        typeItem.setUserData(new ElementData(typeElement));
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateDelegateMethod(), CODE_GENERATOR_KIND, GENERATE_DELEGATE_METHOD, uri, offset, typeItem, fields));
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectDelegateMethodField=Select target field to generate delegates for",
+    })
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        if (arguments.size() > 3) {
+            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+            QuickPickItem type = gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem.class);
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(3)), QuickPickItem[].class));
+            if (fields.size() == 1) {
+                selectMethods(client, uri, offset, type, fields.get(0));
+            } else {
+                client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectDelegateMethodField(), false, fields)).thenAccept(selected -> {
+                    if (selected != null && !selected.isEmpty()) {
+                        selectMethods(client, uri, offset, type, selected.get(0));
+                    }
+                });
+            }
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+
+    @NbBundle.Messages({
+        "DN_SelectDelegateMethods=Select methods to generate delegates for",
+    })
+    private void selectMethods(NbCodeLanguageClient client, String uri, int offset, QuickPickItem type, QuickPickItem selectedField) {
+        try {
+            FileObject file = Utils.fromUri(uri);
+            JavaSource js = JavaSource.forFileObject(file);
+            if (js == null) {
+                throw new IOException("Cannot get JavaSource for: " + uri);
+            }
+            js.runUserActionTask(info -> {
+                info.toPhase(JavaSource.Phase.RESOLVED);
+                TypeElement origin = (TypeElement) gson.fromJson(gson.toJson(type.getUserData()), ElementData.class).resolve(info);
+                VariableElement field = (VariableElement) gson.fromJson(gson.toJson(selectedField.getUserData()), ElementData.class).resolve(info);
+                if (origin != null && field != null) {
+                    final ElementUtilities eu = info.getElementUtilities();
+                    final Trees trees = info.getTrees();
+                    final Scope scope = info.getTreeUtilities().scopeFor(offset);
+                    ElementUtilities.ElementAcceptor acceptor = new ElementUtilities.ElementAcceptor() {
+                        @Override
+                        public boolean accept(Element e, TypeMirror type) {
+                            if (e.getKind() == ElementKind.METHOD && trees.isAccessible(scope, e, (DeclaredType)type)) {
+                                Element impl = eu.getImplementationOf((ExecutableElement)e, origin);
+                                return impl == null || (!impl.getModifiers().contains(Modifier.FINAL) && impl.getEnclosingElement() != origin);
+                            }
+                            return false;
+                        }
+                    };
+                    List<QuickPickItem> methods = new ArrayList<>();
+                    for (ExecutableElement method : ElementFilter.methodsIn(eu.getMembers(field.asType(), acceptor))) {
+                        QuickPickItem item = new QuickPickItem(String.format("%s.%s", field.getSimpleName().toString(), createLabel(info, method)));
+                        item.setUserData(new ElementData(method));
+                        methods.add(item);
+                    }
+                    client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectDelegateMethods(), true, methods)).thenAccept(selected -> {
+                        if (selected != null && !selected.isEmpty()) {
+                            generate(client, uri, offset, selectedField, selected);
+                        }
+                    });
+                }
+            }, true);
+        } catch (IOException | IllegalArgumentException ex) {
+            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+        }
+    }
+
+    private void generate(NbCodeLanguageClient client, String uri, int offset, QuickPickItem selectedField, List<QuickPickItem> selectedMethods) {
+        try {
+            FileObject file = Utils.fromUri(uri);
+            JavaSource js = JavaSource.forFileObject(file);
+            if (js == null) {
+                throw new IOException("Cannot get JavaSource for: " + uri);
+            }
+            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                wc.toPhase(JavaSource.Phase.RESOLVED);
+                TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+                if (tp != null) {
+                    VariableElement field = (VariableElement) gson.fromJson(gson.toJson(selectedField.getUserData()), ElementData.class).resolve(wc);
+                    List<ExecutableElement> methods = selectedMethods.stream().map(item -> {
+                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                        return (ExecutableElement)data.resolve(wc);
+                    }).collect(Collectors.toList());
+                    org.netbeans.modules.java.editor.codegen.DelegateMethodGenerator.generateDelegatingMethods(wc, tp, field, methods, -1);
+                }
+            });
+            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+        } catch (IOException | IllegalArgumentException ex) {
+            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+        }
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/EqualsHashCodeGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/EqualsHashCodeGenerator.java
new file mode 100644
index 0000000..45fb6d3
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/EqualsHashCodeGenerator.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.ElementFilter;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 40)
+public final class EqualsHashCodeGenerator extends CodeGenerator {
+
+    public static final String GENERATE_EQUALS =  "java.generate.equals";
+    public static final String GENERATE_HASH_CODE =  "java.generate.hashCode";
+    public static final String GENERATE_EQUALS_HASH_CODE =  "java.generate.equals.hashCode";
+
+    private final Set<String> commands = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(GENERATE_EQUALS_HASH_CODE, GENERATE_EQUALS, GENERATE_HASH_CODE)));
+    private final Gson gson = new Gson();
+
+    public EqualsHashCodeGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateEquals=Generate equals()...",
+        "DN_GenerateHashCode=Generate hashCode()...",
+        "DN_GenerateEqualsHashCode=Generate equals() and hashCode()...",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        if (only == null || !only.contains(CodeActionKind.Source)) {
+            return Collections.emptyList();
+        }
+        int offset = getOffset(info, params.getRange().getStart());
+        TreePath tp = info.getTreeUtilities().pathFor(offset);
+        tp = info.getTreeUtilities().getPathElementOfKind(Tree.Kind.CLASS, tp);
+        if (tp == null) {
+            return Collections.emptyList();
+        }
+        TypeElement type = (TypeElement) info.getTrees().getElement(tp);
+        if (type == null || type.getKind() != ElementKind.CLASS) {
+            return Collections.emptyList();
+        }
+        ExecutableElement[] equalsHashCode = org.netbeans.modules.java.editor.codegen.EqualsHashCodeGenerator.overridesHashCodeAndEquals(info, type, null);
+        if (equalsHashCode[0] != null && equalsHashCode[1] != null) {
+            return Collections.emptyList();
+        }
+        List<QuickPickItem> fields = new ArrayList<>();
+        for (VariableElement variableElement : ElementFilter.fieldsIn(type.getEnclosedElements())) {
+            if (!ERROR.contentEquals(variableElement.getSimpleName()) && !variableElement.getModifiers().contains(Modifier.STATIC)) {
+                QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+                item.setUserData(new ElementData(variableElement));
+                fields.add(item);
+            }
+        }
+        if (fields.isEmpty()) {
+            return Collections.emptyList();
+        }
+        String uri = Utils.toUri(info.getFileObject());
+        if (equalsHashCode[0] == null) {
+            if (equalsHashCode[1] == null) {
+                return Collections.singletonList(createCodeAction(Bundle.DN_GenerateEqualsHashCode(), CODE_GENERATOR_KIND, GENERATE_EQUALS_HASH_CODE, uri, offset, fields));
+            }
+            return Collections.singletonList(createCodeAction(Bundle.DN_GenerateEquals(), CODE_GENERATOR_KIND, GENERATE_EQUALS, uri, offset, fields));
+        }
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateHashCode(), CODE_GENERATOR_KIND, GENERATE_HASH_CODE, uri, offset, fields));
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectEquals=Select fields to be included in equals()",
+        "DN_SelectHashCode=Select fields to be included in hashCode()",
+        "DN_SelectEqualsHashCode=Select fields to be included in equals() and hashCode()",
+    })
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        if (arguments.size() > 2) {
+            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+            String text;
+            boolean generateEquals = !GENERATE_HASH_CODE.equals(command);
+            boolean generateHashCode = !GENERATE_EQUALS.equals(command);
+            switch (command) {
+                case GENERATE_EQUALS: text = Bundle.DN_SelectEquals(); break;
+                case GENERATE_HASH_CODE: text = Bundle.DN_SelectHashCode(); break;
+                default: text = Bundle.DN_SelectEqualsHashCode(); break;
+            }
+            client.showQuickPick(new ShowQuickPickParams(text, true, fields)).thenAccept(selected -> {
+                if (selected != null) {
+                    try {
+                        FileObject file = Utils.fromUri(uri);
+                        JavaSource js = JavaSource.forFileObject(file);
+                        if (js == null) {
+                            throw new IOException("Cannot get JavaSource for: " + uri);
+                        }
+                        List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                            wc.toPhase(JavaSource.Phase.RESOLVED);
+                            TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                            tp = wc.getTreeUtilities().getPathElementOfKind(Tree.Kind.CLASS, tp);
+                            if (tp != null) {
+                                List<VariableElement> selectedFields = selected.stream().map(item -> {
+                                    ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                                    return (VariableElement)data.resolve(wc);
+                                }).collect(Collectors.toList());
+                                org.netbeans.modules.java.editor.codegen.EqualsHashCodeGenerator.generateEqualsAndHashCode(wc, tp, generateEquals ? selectedFields : null, generateHashCode ? selectedFields : null, -1);
+                            }
+                        });
+                        client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+                    } catch (IOException | IllegalArgumentException ex) {
+                        client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+                    }
+                }
+            });
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java
new file mode 100644
index 0000000..4b7df97
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.ElementFilter;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CodeStyle;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.Pair;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author lahvac
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 30)
+public final class GetterSetterGenerator extends CodeGenerator {
+
+    public static final String GENERATE_GETTERS =  "java.generate.getters";
+    public static final String GENERATE_SETTERS =  "java.generate.setters";
+    public static final String GENERATE_GETTERS_SETTERS =  "java.generate.getters.setters";
+
+    private final Set<String> commands = Collections.unmodifiableSet(new HashSet(Arrays.asList(GENERATE_GETTERS, GENERATE_SETTERS, GENERATE_GETTERS_SETTERS)));
+    private final Gson gson = new Gson();
+
+    public GetterSetterGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateGetters=Generate Getters...",
+        "DN_GenerateSetters=Generate Setters...",
+        "DN_GenerateGettersSetters=Generate Getters and Setters...",
+        "DN_GenerateGetterFor=Generate Getter for \"{0}\"",
+        "DN_GenerateSetterFor=Generate Setter for \"{0}\"",
+        "DN_GenerateGetterSetterFor=Generate Getter and Setter for \"{0}\"",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        boolean all = only != null && only.contains(CodeActionKind.Source);
+        Pair<Set<VariableElement>, Set<VariableElement>> pair = findMissingGettersSetters(info, params.getRange(), all);
+        boolean missingGetters = !pair.first().isEmpty();
+        boolean missingSetters = !pair.second().isEmpty();
+        String uri = Utils.toUri(info.getFileObject());
+        int offset = getOffset(info, params.getRange().getStart());
+        List<CodeAction> result = new ArrayList<>();
+        if (missingGetters) {
+            String name = pair.first().size() == 1 ? Bundle.DN_GenerateGetterFor(pair.first().iterator().next().getSimpleName().toString()) : Bundle.DN_GenerateGetters();
+            result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, GENERATE_GETTERS, uri, offset, all, pair.first().stream().map(variableElement -> {
+                QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+                item.setUserData(new ElementData(variableElement));
+                return item;
+            }).collect(Collectors.toList())));
+        }
+        if (missingSetters) {
+            String name = pair.second().size() == 1 ? Bundle.DN_GenerateSetterFor(pair.second().iterator().next().getSimpleName().toString()) : Bundle.DN_GenerateSetters();
+            result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, GENERATE_SETTERS, uri, offset, all, pair.second().stream().map(variableElement -> {
+                QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+                item.setUserData(new ElementData(variableElement));
+                return item;
+            }).collect(Collectors.toList())));
+        }
+        if (missingGetters && missingSetters) {
+            pair.first().retainAll(pair.second());
+            String name = pair.first().size() == 1 ? Bundle.DN_GenerateGetterSetterFor(pair.first().iterator().next().getSimpleName().toString()) : Bundle.DN_GenerateGettersSetters();
+            result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, GENERATE_GETTERS_SETTERS, uri, offset, all, pair.first().stream().map(variableElement -> {
+                QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+                item.setUserData(new ElementData(variableElement));
+                return item;
+            }).collect(Collectors.toList())));
+        }
+        return result;
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectGetters=Select fields to generate getters for",
+        "DN_SelectSetters=Select fields to generate setters for",
+        "DN_SelectGettersSetters=Select fields to generate getters and setters for",
+    })
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        if (arguments.size() > 3) {
+            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+            boolean all = gson.fromJson(gson.toJson(arguments.get(2)), boolean.class);
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(3)), QuickPickItem[].class));
+            int kind;
+            String text;
+            switch (command) {
+                case GENERATE_GETTERS: kind = GeneratorUtils.GETTERS_ONLY; text = Bundle.DN_SelectGetters(); break;
+                case GENERATE_SETTERS: kind = GeneratorUtils.SETTERS_ONLY; text = Bundle.DN_SelectSetters(); break;
+                default: kind = 0; text = Bundle.DN_SelectGettersSetters(); break;
+            }
+            if (all && fields.size() > 1) {
+                client.showQuickPick(new ShowQuickPickParams(text, true, fields)).thenAccept(selected -> {
+                    if (selected != null && !selected.isEmpty()) {
+                        generate(client, kind, uri, offset, selected);
+                    }
+                });
+            } else if (fields.size() == 1) {
+                generate(client, kind, uri, offset, fields);
+            }
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+
+    private void generate(NbCodeLanguageClient client, int kind, String uri, int offset, List<QuickPickItem> fields) throws IllegalArgumentException {
+        try {
+            FileObject file = Utils.fromUri(uri);
+            JavaSource js = JavaSource.forFileObject(file);
+            if (js == null) {
+                throw new IOException("Cannot get JavaSource for: " + uri);
+            }
+            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                wc.toPhase(JavaSource.Phase.RESOLVED);
+                TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+                if (tp != null) {
+                    List<VariableElement> variableElements = fields.stream().map(item -> {
+                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                        return (VariableElement) data.resolve(wc);
+                    }).collect(Collectors.toList());
+                    GeneratorUtils.generateGettersAndSetters(wc, tp, variableElements, kind, -1);
+                }
+            });
+            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+        } catch (IOException ex) {
+            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+        }
+    }
+
+    private static Pair<Set<VariableElement>, Set<VariableElement>> findMissingGettersSetters(CompilationInfo info, Range range, boolean all) {
+        TreePath tp = info.getTreeUtilities().pathFor(getOffset(info, range.getStart()));
+        tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+        if (tp == null) {
+            return Pair.of(Collections.emptySet(), Collections.emptySet());
+        }
+
+        TypeElement type = (TypeElement) info.getTrees().getElement(tp);
+        if (type == null) {
+            return Pair.of(Collections.emptySet(), Collections.emptySet());
+        }
+
+        int selectionStart = getOffset(info, range.getStart());
+        int selectionEnd   = getOffset(info, range.getEnd());
+
+        ClassTree clazz = (ClassTree) tp.getLeaf();
+        Set<VariableElement> selectedFields = new HashSet<>();
+
+        for (Tree m : clazz.getMembers()) {
+            if (m.getKind() != Tree.Kind.VARIABLE) continue;
+            int start = (int) info.getTrees().getSourcePositions().getStartPosition(tp.getCompilationUnit(), m);
+            int end   = (int) info.getTrees().getSourcePositions().getEndPosition(tp.getCompilationUnit(), m);
+
+            if (all || intersects(start, end, selectionStart, selectionEnd)) {
+                selectedFields.add((VariableElement) info.getTrees().getElement(new TreePath(tp, m)));
+            }
+        }
+
+        Pair<Set<VariableElement>, Set<VariableElement>> pair = findMissingGettersSetters(info, type);
+
+        pair.first().retainAll(selectedFields);
+        pair.second().retainAll(selectedFields);
+
+        return pair;
+    }
+
+    private static boolean intersects(int fieldStart, int fieldEnd, int selectionStart, int selectionEnd) {
+        return selectionStart <= fieldEnd && selectionEnd >= fieldStart;
+    }
+
+    private static Pair<Set<VariableElement>, Set<VariableElement>> findMissingGettersSetters(CompilationInfo info, TypeElement type) {
+        Set<VariableElement> missingGetters = new LinkedHashSet<>();
+        Set<VariableElement> missingSetters = new LinkedHashSet<>();
+        ElementUtilities eu = info.getElementUtilities();
+        CodeStyle codeStyle = CodeStyle.getDefault(info.getFileObject());
+
+        for (VariableElement variableElement : ElementFilter.fieldsIn(info.getElements().getAllMembers(type))) {
+            if (ERROR.contentEquals(variableElement.getSimpleName())) {
+                continue;
+            }
+            boolean hasGetter = eu.hasGetter(type, variableElement, codeStyle);
+            boolean hasSetter = variableElement.getModifiers().contains(Modifier.FINAL) ||
+                                eu.hasSetter(type, variableElement, codeStyle);
+            if (!hasGetter) {
+                missingGetters.add(variableElement);
+            }
+            if (!hasSetter) {
+                missingSetters.add(variableElement);
+            }
+        }
+
+        return Pair.of(missingGetters, missingSetters);
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementOverrideMethodGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementOverrideMethodGenerator.java
new file mode 100644
index 0000000..231287d
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementOverrideMethodGenerator.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 70)
+public final class ImplementOverrideMethodGenerator extends CodeGenerator {
+
+    public static final String GENERATE_IMPLEMENT_METHOD =  "java.generate.implementMethod";
+    public static final String GENERATE_OVERRIDE_METHOD =  "java.generate.overrideMethod";
+
+    private final Set<String> commands = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(GENERATE_IMPLEMENT_METHOD, GENERATE_OVERRIDE_METHOD)));
+    private final Gson gson = new Gson();
+
+    public ImplementOverrideMethodGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateImplementMethod=Generate Implement Method...",
+        "DN_GenerateOverrideMethod=Generate Override Method...",
+        "DN_From=(from {0})",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        if (only == null || !only.contains(CodeActionKind.Source)) {
+            return Collections.emptyList();
+        }
+        int offset = getOffset(info, params.getRange().getStart());
+        TreePath tp = info.getTreeUtilities().pathFor(offset);
+        tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+        if (tp == null) {
+            return Collections.emptyList();
+        }
+        TypeElement typeElement = (TypeElement) info.getTrees().getElement(tp);
+        if (typeElement == null || typeElement.getKind() == ElementKind.ANNOTATION_TYPE) {
+            return Collections.emptyList();
+        }
+        List<CodeAction> result = new ArrayList<>();
+        String uri = Utils.toUri(info.getFileObject());
+        ElementUtilities eu = info.getElementUtilities();
+        if (typeElement.getKind().isClass() || typeElement.getKind().isInterface() && SourceVersion.RELEASE_8.compareTo(info.getSourceVersion()) <= 0) {
+            List<QuickPickItem> implementMethods = new ArrayList<>();
+            for (ExecutableElement method : eu.findUnimplementedMethods(typeElement, true)) {
+                boolean mustImplement = !method.getModifiers().contains(Modifier.DEFAULT);
+                Element enclosingElement = method.getEnclosingElement();
+                String enclosingTypeName = enclosingElement.getKind().isClass() || enclosingElement.getKind().isInterface() ? Bundle.DN_From(((TypeElement)enclosingElement).getQualifiedName().toString()) : null;
+                implementMethods.add(new QuickPickItem(createLabel(info, method), enclosingTypeName, null, mustImplement, new ElementData(method)));
+            }
+            if (!implementMethods.isEmpty()) {
+                result.add(createCodeAction(Bundle.DN_GenerateImplementMethod(), CODE_GENERATOR_KIND, GENERATE_IMPLEMENT_METHOD, uri, offset, implementMethods));
+            }
+        }
+        if (typeElement.getKind().isClass() || typeElement.getKind().isInterface()) {
+            List<QuickPickItem> overrideMethods = new ArrayList<>();
+            for (ExecutableElement method : eu.findOverridableMethods(typeElement)) {
+                Element enclosingElement = method.getEnclosingElement();
+                String enclosingTypeName = enclosingElement.getKind().isClass() || enclosingElement.getKind().isInterface() ? Bundle.DN_From(((TypeElement) enclosingElement).getQualifiedName().toString()) : null;
+                QuickPickItem item = new QuickPickItem(createLabel(info, method));
+                if (enclosingTypeName != null) {
+                    item.setDescription(enclosingTypeName);
+                }
+                item.setUserData(new ElementData(method));
+                overrideMethods.add(item);
+            }
+            if (!overrideMethods.isEmpty()) {
+                result.add(createCodeAction(Bundle.DN_GenerateOverrideMethod(), CODE_GENERATOR_KIND, GENERATE_OVERRIDE_METHOD, uri, offset, overrideMethods));
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectImplementMethod=Select methods to implement",
+        "DN_SelectOverrideMethod=Select methods to override",
+    })
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        if (arguments.size() > 2) {
+            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+            List<QuickPickItem> methods = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+            String text = command == GENERATE_IMPLEMENT_METHOD ? Bundle.DN_SelectImplementMethod() : Bundle.DN_SelectOverrideMethod();
+            boolean isImplement = command == GENERATE_IMPLEMENT_METHOD;
+            client.showQuickPick(new ShowQuickPickParams(text, true, methods)).thenAccept(selected -> {
+                if (selected != null && !selected.isEmpty()) {
+                    generate(client, uri, offset, isImplement, selected);
+                }
+            });
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+
+    private void generate(NbCodeLanguageClient client, String uri, int offset, boolean isImplement, List<QuickPickItem> methods) {
+        try {
+            FileObject file = Utils.fromUri(uri);
+            JavaSource js = JavaSource.forFileObject(file);
+            if (js == null) {
+                throw new IOException("Cannot get JavaSource for: " + uri);
+            }
+            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                wc.toPhase(JavaSource.Phase.RESOLVED);
+                TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+                if (tp != null) {
+                    List<ExecutableElement> selectedMethods = methods.stream().map(item -> {
+                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                        return (ExecutableElement)data.resolve(wc);
+                    }).collect(Collectors.toList());
+                    if (isImplement) {
+                        GeneratorUtils.generateAbstractMethodImplementations(wc, tp, selectedMethods, -1);
+                    } else {
+                        GeneratorUtils.generateMethodOverrides(wc, tp, selectedMethods, -1);
+                    }
+                }
+            });
+            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+        } catch (IOException | IllegalArgumentException ex) {
+            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+        }
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LoggerGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LoggerGenerator.java
new file mode 100644
index 0000000..e0fcc30
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LoggerGenerator.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.logging.Logger;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.GeneratorUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.BaseUtilities;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 20)
+public final class LoggerGenerator extends CodeGenerator {
+
+    public static final String GENERATE_LOGGER =  "java.generate.logger";
+
+    private final Set<String> commands = Collections.singleton(GENERATE_LOGGER);
+    private final Gson gson = new Gson();
+
+    public LoggerGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateLogger=Generate Logger...",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        if (only == null || !only.contains(CodeActionKind.Source)) {
+            return Collections.emptyList();
+        }
+        int offset = getOffset(info, params.getRange().getStart());
+        TreePath tp = info.getTreeUtilities().pathFor(offset);
+        tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+        if (tp == null) {
+            return Collections.emptyList();
+        }
+        TypeElement typeElement = (TypeElement) info.getTrees().getElement(tp);
+        if (typeElement == null || !typeElement.getKind().isClass()) {
+            return Collections.emptyList();
+        }
+        for (VariableElement ve : ElementFilter.fieldsIn(typeElement.getEnclosedElements())) {
+            TypeMirror type = ve.asType();
+            if (type.getKind() == TypeKind.DECLARED && ((TypeElement)((DeclaredType)type).asElement()).getQualifiedName().contentEquals(Logger.class.getName())
+                    || type.getKind() == TypeKind.ERROR && ((TypeElement)((DeclaredType)type).asElement()).getSimpleName().contentEquals(Logger.class.getSimpleName())) {
+                return Collections.emptyList();
+            }
+        }
+        String uri = Utils.toUri(info.getFileObject());
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateLogger(), CODE_GENERATOR_KIND, GENERATE_LOGGER, uri, offset));
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectLoggerName=Logger field name",
+    })
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        if (arguments.size() > 1) {
+            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+            client.showInputBox(new ShowInputBoxParams(Bundle.DN_SelectLoggerName(), "LOG")).thenAccept(value -> {
+                if (value != null && BaseUtilities.isJavaIdentifier(value)) {
+                    try {
+                        FileObject file = Utils.fromUri(uri);
+                        JavaSource js = JavaSource.forFileObject(file);
+                        if (js == null) {
+                            throw new IOException("Cannot get JavaSource for: " + uri);
+                        }
+                        List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                            wc.toPhase(JavaSource.Phase.RESOLVED);
+                            TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                            tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+                            if (tp != null) {
+                                ClassTree cls = (ClassTree) tp.getLeaf();
+                                VariableTree field = org.netbeans.modules.java.editor.codegen.LoggerGenerator.createLoggerField(wc.getTreeMaker(), cls, value, EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL));
+                                wc.rewrite(cls, GeneratorUtilities.get(wc).insertClassMember(cls, field));
+                            }
+                        });
+                        client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+                    } catch (IOException | IllegalArgumentException ex) {
+                        client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+                    }
+                }
+            });
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java
index 41f7e0a..33046d3 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java
@@ -20,6 +20,7 @@
 
 import com.google.gson.GsonBuilder;
 import com.google.gson.JsonElement;
+import org.eclipse.lsp4j.ClientCapabilities;
 import org.eclipse.lsp4j.InitializeParams;
 
 /**
@@ -35,12 +36,21 @@
  */
 public final class NbCodeClientCapabilities {
     /**
+     * The LSP client official capabilities.
+     */
+    private ClientCapabilities clientCaps;
+    
+    /**
      * Supports status bar messages:
      * <ul>
      * <li>window/showStatusBarMessage
      * </ul>
      */
     private Boolean statusBarMessageSupport;
+    
+    public ClientCapabilities getClientCapabilities() {
+        return clientCaps;
+    }
 
     public Boolean getStatusBarMessageSupport() {
         return statusBarMessageSupport;
@@ -54,6 +64,14 @@
         this.statusBarMessageSupport = statusBarMessageSupport;
     }
     
+    private NbCodeClientCapabilities withCapabilities(ClientCapabilities caps) {
+        if (caps == null) {
+            caps = new ClientCapabilities();
+        }
+        this.clientCaps = caps;
+        return this;
+    }
+    
     public static NbCodeClientCapabilities get(InitializeParams initParams) {
         if (initParams == null) {
             return null;
@@ -69,7 +87,7 @@
                 */
                 create().
                 fromJson((JsonElement)ext, InitializationExtendedCapabilities.class);
-        return root == null ? null : root.getNbcodeCapabilities();
+        return root == null ? null : root.getNbcodeCapabilities().withCapabilities(initParams.getCapabilities());
                 
     }
     
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java
index c4ba4b5..975f018 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java
@@ -25,11 +25,13 @@
 import org.eclipse.lsp4j.ConfigurationParams;
 import org.eclipse.lsp4j.MessageActionItem;
 import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.ProgressParams;
 import org.eclipse.lsp4j.PublishDiagnosticsParams;
 import org.eclipse.lsp4j.RegistrationParams;
 import org.eclipse.lsp4j.SemanticHighlightingParams;
 import org.eclipse.lsp4j.ShowMessageRequestParams;
 import org.eclipse.lsp4j.UnregistrationParams;
+import org.eclipse.lsp4j.WorkDoneProgressCreateParams;
 import org.eclipse.lsp4j.WorkspaceFolder;
 
 /**
@@ -52,7 +54,7 @@
             this.clientCaps = clientCaps;
         }
     }
-    
+
     @Override
     public NbCodeClientCapabilities getNbCodeCapabilities() {
         return clientCaps;
@@ -64,6 +66,16 @@
     }
 
     @Override
+    public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+        return remote.showQuickPick(params);
+    }
+
+    @Override
+    public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+        return remote.showInputBox(params);
+    }
+
+    @Override
     public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
         return remote.applyEdit(params);
     }
@@ -117,4 +129,14 @@
     public void semanticHighlighting(SemanticHighlightingParams params) {
         remote.semanticHighlighting(params);
     }
+
+    @Override
+    public CompletableFuture<Void> createProgress(WorkDoneProgressCreateParams params) {
+        return remote.createProgress(params);
+    }
+
+    @Override
+    public void notifyProgress(ProgressParams params) {
+        remote.notifyProgress(params);
+    }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java
index 06177ae..50fe926 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java
@@ -18,8 +18,11 @@
  */
 package org.netbeans.modules.java.lsp.server.protocol;
 
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import org.eclipse.lsp4j.MessageParams;
 import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
+import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
 import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
 import org.eclipse.lsp4j.services.LanguageClient;
 
@@ -40,7 +43,25 @@
      */
     @JsonNotification("window/showStatusBarMessage")
     public void showStatusBarMessage(@NonNull ShowStatusMessageParams params);
-    
+
+    /**
+     * Shows a selection list allowing multiple selections.
+     *
+     * @param params input parameters
+     * @return selected items
+     */
+    @JsonRequest("window/showQuickPick")
+    public CompletableFuture<List<QuickPickItem>> showQuickPick(@NonNull ShowQuickPickParams params);
+
+    /**
+     * Shows an input box to ask the user for input.
+     *
+     * @param params input parameters
+     * @return input value
+     */
+    @JsonRequest("window/showInputBox")
+    public CompletableFuture<String> showInputBox(@NonNull ShowInputBoxParams params);
+
     /**
      * Returns extended code capabilities.
      * @return code capabilities.
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/QuickPickItem.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/QuickPickItem.java
new file mode 100644
index 0000000..3b17385
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/QuickPickItem.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import java.util.Objects;
+import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
+import org.eclipse.lsp4j.util.Preconditions;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ * Represents an item that can be selected from a list of items.
+ *
+ * @author Dusan Balek
+ */
+@SuppressWarnings("all")
+public class QuickPickItem {
+
+    /**
+     * A human-readable string which is rendered prominent.
+     */
+    @NonNull
+    private String label;
+
+    /**
+     * A human-readable string which is rendered less prominent in the same line.
+     */
+    private String description;
+
+    /**
+     * A human-readable string which is rendered less prominent in a separate line.
+     */
+    private String detail;
+
+    /**
+     * Optional flag indicating if this item is picked initially.
+     */
+    private boolean picked;
+
+    /**
+     * Optional user data.
+     */
+    private Object userData;
+
+    public QuickPickItem() {
+    }
+
+    public QuickPickItem(@NonNull final String label) {
+        this.label = Preconditions.checkNotNull(label, "label");
+    }
+
+    public QuickPickItem(@NonNull final String label, final String description, final String detail, final boolean picked, final Object userData) {
+        this(label);
+        this.description = description;
+        this.detail = detail;
+        this.picked = picked;
+        this.userData = userData;
+    }
+
+    /**
+     * A human-readable string which is rendered prominent.
+     */
+    @Pure
+    @NonNull
+    public String getLabel() {
+        return label;
+    }
+
+    /**
+     * A human-readable string which is rendered prominent.
+     */
+    public void setLabel(@NonNull final String label) {
+        this.label = label;
+    }
+
+    /**
+     * A human-readable string which is rendered less prominent in the same line.
+     */
+    @Pure
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * A human-readable string which is rendered less prominent in the same line.
+     */
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    /**
+     * A human-readable string which is rendered less prominent in a separate line.
+     */
+    @Pure
+    public String getDetail() {
+        return detail;
+    }
+
+    /**
+     * A human-readable string which is rendered less prominent in a separate line.
+     */
+    public void setDetail(final String detail) {
+        this.detail = detail;
+    }
+
+    /**
+     * Optional flag indicating if this item is picked initially.
+     */
+    @Pure
+    public boolean isPicked() {
+        return picked;
+    }
+
+    /**
+     * Optional flag indicating if this item is picked initially.
+     */
+    public void setPicked(boolean picked) {
+        this.picked = picked;
+    }
+
+    /**
+     * Optional user data.
+     */
+    @Pure
+    public Object getUserData() {
+        return userData;
+    }
+
+    /**
+     * Optional user data.
+     */
+    public void setUserData(final Object userData) {
+        this.userData = userData;
+    }
+
+    @Override
+    @Pure
+    public String toString() {
+        ToStringBuilder b = new ToStringBuilder(this);
+        b.add("label", label);
+        b.add("description", description);
+        b.add("detail", detail);
+        b.add("picked", picked);
+        b.add("userData", userData);
+        return b.toString();
+    }
+
+    @Override
+    @Pure
+    public int hashCode() {
+        int hash = 7;
+        hash = 83 * hash + Objects.hashCode(this.label);
+        hash = 83 * hash + Objects.hashCode(this.description);
+        hash = 83 * hash + Objects.hashCode(this.detail);
+        hash = 83 * hash + (this.picked ? 1 : 0);
+        hash = 83 * hash + Objects.hashCode(this.userData);
+        return hash;
+    }
+
+    @Override
+    @Pure
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final QuickPickItem other = (QuickPickItem) obj;
+        if (this.picked != other.picked) {
+            return false;
+        }
+        if (!Objects.equals(this.label, other.label)) {
+            return false;
+        }
+        if (!Objects.equals(this.description, other.description)) {
+            return false;
+        }
+        if (!Objects.equals(this.detail, other.detail)) {
+            return false;
+        }
+        if (!Objects.equals(this.userData, other.userData)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
index af46181..1e5ff19 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
@@ -32,6 +32,8 @@
 import java.util.function.Function;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionOptions;
 import org.eclipse.lsp4j.CompletionOptions;
 import org.eclipse.lsp4j.ExecuteCommandOptions;
 import org.eclipse.lsp4j.InitializeParams;
@@ -40,15 +42,20 @@
 import org.eclipse.lsp4j.MessageParams;
 import org.eclipse.lsp4j.MessageType;
 import org.eclipse.lsp4j.PublishDiagnosticsParams;
+import org.eclipse.lsp4j.RenameOptions;
 import org.eclipse.lsp4j.ServerCapabilities;
 import org.eclipse.lsp4j.ShowMessageRequestParams;
 import org.eclipse.lsp4j.TextDocumentSyncKind;
+import org.eclipse.lsp4j.WorkDoneProgressCancelParams;
+import org.eclipse.lsp4j.WorkDoneProgressParams;
 import org.eclipse.lsp4j.WorkspaceFolder;
 import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
 import org.eclipse.lsp4j.jsonrpc.Launcher;
 import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
 import org.eclipse.lsp4j.jsonrpc.MessageIssueException;
 import org.eclipse.lsp4j.jsonrpc.messages.Message;
+import org.eclipse.lsp4j.jsonrpc.messages.NotificationMessage;
+import org.eclipse.lsp4j.jsonrpc.messages.RequestMessage;
 import org.eclipse.lsp4j.launch.LSPLauncher;
 import org.eclipse.lsp4j.services.LanguageClient;
 import org.eclipse.lsp4j.services.LanguageClientAware;
@@ -63,6 +70,9 @@
 import org.netbeans.api.project.ProjectUtils;
 import org.netbeans.api.project.Sources;
 import org.netbeans.api.project.ui.OpenProjects;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.modules.java.lsp.server.progress.OperationContext;
+import org.netbeans.modules.progress.spi.InternalHandle;
 import org.openide.filesystems.FileObject;
 import org.openide.util.Exceptions;
 import org.openide.util.Lookup;
@@ -115,6 +125,7 @@
             .setInput(in)
             .setOutput(out)
             .wrapMessages(processor)
+//                .traceMessages(new java.io.PrintWriter(System.err))
             .create();
     }
     
@@ -127,6 +138,7 @@
     private static class ConsumeWithLookup {
         private final Lookup sessionLookup;
         private NbCodeLanguageClient client;
+        private OperationContext initialContext;
         
         public ConsumeWithLookup(Lookup sessionLookup) {
             this.sessionLookup = sessionLookup;
@@ -137,13 +149,67 @@
         }
         
         public MessageConsumer attachLookup(MessageConsumer delegate) {
+            // PENDING: allow for message consumer wrappers to be registered to add pre/post processing for
+            // the request plus build the request's default Lookup contents.
             return new MessageConsumer() {
                 @Override
                 public void consume(Message msg) throws MessageIssueException, JsonRpcException {
+                    InstanceContent ic = new InstanceContent();
+                    ProxyLookup ll = new ProxyLookup(new AbstractLookup(ic), sessionLookup);
+                    final OperationContext ctx;
+                    
+                    // Intercept client REQUESTS; take the progress token from them, if it is
+                    // attached.
+                    Runnable r;
+                    InternalHandle toCancel = null;
+                    if (msg instanceof RequestMessage) {
+                        RequestMessage rq = (RequestMessage)msg;
+                        Object p = rq.getParams();
+                        if (initialContext == null) {
+                            initialContext = OperationContext.create(client);
+                            ctx = initialContext;
+                        } else {
+                            ctx = initialContext.operationContext();
+                        }
+                        // PENDING: this ought to be somehow registered, so different services
+                        // may enrich lookup/pre/postprocess the processing, not just the progress support.
+                        if (p instanceof WorkDoneProgressParams) {
+                            ctx.setProgressToken(((WorkDoneProgressParams)p).getWorkDoneToken());
+                        }
+                    } else if (msg instanceof NotificationMessage) {
+                        NotificationMessage not = (NotificationMessage)msg;
+                        Object p = not.getParams();
+                        OperationContext selected = null;
+                        if (p instanceof WorkDoneProgressCancelParams && initialContext != null) {
+                            WorkDoneProgressCancelParams wdc = (WorkDoneProgressCancelParams)p;
+                            toCancel = initialContext.findActiveHandle(wdc.getToken());
+                            selected = OperationContext.getHandleContext(toCancel);
+                        }
+                        ctx = selected;
+                    } else {
+                        ctx = null;
+                    }
+                    if (ctx != null) {
+                        ic.add(ctx);
+                    }
+                    final InternalHandle ftoCancel = toCancel;
                     try {
                         DISPATCHERS.set(client);
-                        Lookups.executeWith(sessionLookup, () -> {
-                            delegate.consume(msg);
+                        Lookups.executeWith(ll, () -> {
+                            try {
+                                delegate.consume(msg);
+                            } finally {
+                                // cancel while the OperationContext is still active.
+                                if (ftoCancel != null) {
+                                    ftoCancel.requestCancel();
+                                }
+                                if (ctx != null) {
+                                    // if initialized (for requests only), discards the token,
+                                    // as it becomes invalid at the end of this message. Further progresses
+                                    // must do their own processing.
+                                    ctx.stop();
+                                }
+                            }
                         });
                     } finally {
                         DISPATCHERS.remove();
@@ -247,12 +313,21 @@
                 completionOptions.setResolveProvider(true);
                 completionOptions.setTriggerCharacters(Collections.singletonList("."));
                 capabilities.setCompletionProvider(completionOptions);
-                capabilities.setCodeActionProvider(true);
+                capabilities.setHoverProvider(true);
+                capabilities.setCodeActionProvider(new CodeActionOptions(Arrays.asList(CodeActionKind.QuickFix, CodeActionKind.Source)));
                 capabilities.setDocumentSymbolProvider(true);
                 capabilities.setDefinitionProvider(true);
                 capabilities.setDocumentHighlightProvider(true);
                 capabilities.setReferencesProvider(true);
-                capabilities.setExecuteCommandProvider(new ExecuteCommandOptions(Arrays.asList(JAVA_BUILD_WORKSPACE, GRAALVM_PAUSE_SCRIPT)));
+                List<String> commands = new ArrayList<>(Arrays.asList(JAVA_BUILD_WORKSPACE, GRAALVM_PAUSE_SCRIPT));
+                for (CodeGenerator codeGenerator : Lookup.getDefault().lookupAll(CodeGenerator.class)) {
+                    commands.addAll(codeGenerator.getCommands());
+                }
+                capabilities.setExecuteCommandProvider(new ExecuteCommandOptions(commands));
+                capabilities.setWorkspaceSymbolProvider(true);
+                RenameOptions renOpt = new RenameOptions();
+                renOpt.setPrepareProvider(true);
+                capabilities.setRenameProvider(renOpt);
             }
             return new InitializeResult(capabilities);
         }
@@ -266,7 +341,7 @@
             if (folders != null) {
                 for (WorkspaceFolder w : folders) {
                     try {
-                        projectCandidates.add(TextDocumentServiceImpl.fromUri(w.getUri()));
+                        projectCandidates.add(Utils.fromUri(w.getUri()));
                     } catch (MalformedURLException ex) {
                         LOG.log(Level.FINE, null, ex);
                     }
@@ -276,7 +351,7 @@
 
                 if (root != null) {
                     try {
-                        projectCandidates.add(TextDocumentServiceImpl.fromUri(root));
+                        projectCandidates.add(Utils.fromUri(root));
                     } catch (MalformedURLException ex) {
                         LOG.log(Level.FINE, null, ex);
                     }
@@ -289,9 +364,18 @@
             
             return fProjects.
                     thenApply(this::showIndexingCompleted).
-                    thenApply(this::constructInitResponse);
+                    thenApply(this::constructInitResponse).
+                    thenApply(this::finishInitialization);
         }
-
+        
+        public InitializeResult finishInitialization(InitializeResult res) {
+            OperationContext c = OperationContext.find(sessionLookup);
+            // discard the progress token as it is going to be invalid anyway. Further pending
+            // initializations need to create its own tokens.
+            c.acquireProgressToken();
+            return res;
+        }
+        
         @Override
         public CompletableFuture<Object> shutdown() {
             return CompletableFuture.completedFuture(null);
@@ -322,7 +406,6 @@
                 }
             });
             sessionServices.add(new WorkspaceUIContext(client));
-            
             ((LanguageClientAware) getTextDocumentService()).connect(aClient);
             ((LanguageClientAware) getWorkspaceService()).connect(aClient);
         }
@@ -332,7 +415,7 @@
     public static final String GRAALVM_PAUSE_SCRIPT =  "graalvm.pause.script";
     static final String INDEXING_COMPLETED = "Indexing completed.";
     static final String NO_JAVA_SUPPORT = "Cannot initialize Java support on JDK ";
-    
+
     static final NbCodeLanguageClient STUB_CLIENT = new NbCodeLanguageClient() {
         private final NbCodeClientCapabilities caps = new NbCodeClientCapabilities();
         
@@ -347,6 +430,18 @@
         }
 
         @Override
+        public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+            logWarning(params);
+            return CompletableFuture.completedFuture(params.getCanPickMany() || params.getItems().isEmpty() ? params.getItems() : Collections.singletonList(params.getItems().get(0)));
+        }
+
+        @Override
+        public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+            logWarning(params);
+            return CompletableFuture.completedFuture(params.getValue());
+        }
+
+        @Override
         public NbCodeClientCapabilities getNbCodeCapabilities() {
             logWarning();
             return caps;
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java
new file mode 100644
index 0000000..a262919
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import java.util.Objects;
+import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
+import org.eclipse.lsp4j.util.Preconditions;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@SuppressWarnings("all")
+public class ShowInputBoxParams {
+
+    /**
+     * The text to display underneath the input box.
+     */
+    @NonNull
+    private String prompt;
+
+    /**
+     * The value to prefill in the input box.
+     */
+    @NonNull
+    private String value;
+
+    public ShowInputBoxParams() {
+        this("", "");
+    }
+
+    public ShowInputBoxParams(@NonNull final String prompt, @NonNull final String value) {
+        this.prompt = Preconditions.checkNotNull(prompt, "prompt");
+        this.value = Preconditions.checkNotNull(value, "value");
+    }
+
+    /**
+     * The text to display underneath the input box.
+     */
+    @Pure
+    @NonNull
+    public String getPrompt() {
+        return prompt;
+    }
+
+    /**
+     * The text to display underneath the input box.
+     */
+    public void setPrompt(@NonNull final String prompt) {
+        this.prompt = prompt;
+    }
+
+    /**
+     * The value to prefill in the input box.
+     */
+    @Pure
+    @NonNull
+    public String getValue() {
+        return value;
+    }
+
+    /**
+     * The value to prefill in the input box.
+     */
+    public void setValue(@NonNull final String value) {
+        this.value = value;
+    }
+
+    @Override
+    @Pure
+    public String toString() {
+        ToStringBuilder b = new ToStringBuilder(this);
+        b.add("prompt", prompt);
+        b.add("value", value);
+        return b.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 5;
+        hash = 59 * hash + Objects.hashCode(this.prompt);
+        hash = 59 * hash + Objects.hashCode(this.value);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ShowInputBoxParams other = (ShowInputBoxParams) obj;
+        if (!Objects.equals(this.prompt, other.prompt)) {
+            return false;
+        }
+        if (!Objects.equals(this.value, other.value)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowQuickPickParams.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowQuickPickParams.java
new file mode 100644
index 0000000..107084f
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowQuickPickParams.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
+import org.eclipse.lsp4j.util.Preconditions;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ * A selection list parameters.
+ *
+ * @author Dusan Balek
+ */
+@SuppressWarnings("all")
+public class ShowQuickPickParams {
+
+    /**
+     * A string to show as placeholder in the input box to guide the user what to pick on.
+     */
+    @NonNull
+    private String placeHolder;
+
+    /**
+     * An optional flag to make the picker accept multiple selections.
+     */
+    private boolean canPickMany;
+
+    /**
+     * A list of items.
+     */
+    @NonNull
+    private List<QuickPickItem> items;
+
+    public ShowQuickPickParams() {
+        this("", new ArrayList<>());
+    }
+
+    public ShowQuickPickParams(@NonNull final String placeHolder, @NonNull final List<QuickPickItem> items) {
+        this.placeHolder = Preconditions.checkNotNull(placeHolder, "placeHolder");
+        this.items = Preconditions.checkNotNull(items, "items");
+    }
+
+    public ShowQuickPickParams(final String placeHolder, final boolean canPickMany, @NonNull final List<QuickPickItem> items) {
+        this(placeHolder, items);
+        this.canPickMany = canPickMany;
+    }
+
+    /**
+     * A string to show as placeholder in the input box to guide the user what to pick on.
+     */
+    @Pure
+    @NonNull
+    public String getPlaceHolder() {
+        return placeHolder;
+    }
+
+    /**
+     * A string to show as placeholder in the input box to guide the user what to pick on.
+     */
+    public void setPlaceHolder(@NonNull final String placeHolder) {
+        this.placeHolder = placeHolder;
+    }
+
+    /**
+     * An optional flag to make the picker accept multiple selections.
+     */
+    @Pure
+    public boolean getCanPickMany() {
+        return canPickMany;
+    }
+
+    /**
+     * An optional flag to make the picker accept multiple selections.
+     */
+    public void setCanPickMany(final boolean canPickMany) {
+        this.canPickMany = canPickMany;
+    }
+
+    /**
+     * A list of items.
+     */
+    @Pure
+    @NonNull
+    public List<QuickPickItem> getItems() {
+        return items;
+    }
+
+    /**
+     * A list of items.
+     */
+    public void setItems(@NonNull final List<QuickPickItem> items) {
+        this.items = items;
+    }
+
+    @Override
+    @Pure
+    public String toString() {
+        ToStringBuilder b = new ToStringBuilder(this);
+        b.add("placeHolder", placeHolder);
+        b.add("canPickMany", canPickMany);
+        b.add("items", items);
+        return b.toString();
+    }
+
+    @Override
+    @Pure
+    public int hashCode() {
+        int hash = 7;
+        hash = 29 * hash + Objects.hashCode(this.placeHolder);
+        hash = 29 * hash + (this.canPickMany ? 1 : 0);
+        hash = 29 * hash + Objects.hashCode(this.items);
+        return hash;
+    }
+
+    @Override
+    @Pure
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ShowQuickPickParams other = (ShowQuickPickParams) obj;
+        if (this.canPickMany != other.canPickMany) {
+            return false;
+        }
+        if (!Objects.equals(this.placeHolder, other.placeHolder)) {
+            return false;
+        }
+        if (!Objects.equals(this.items, other.items)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
index 33d95eb..4122491 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
@@ -24,33 +24,28 @@
 import com.sun.source.tree.CompilationUnitTree;
 import com.sun.source.tree.LineMap;
 import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Scope;
 import com.sun.source.tree.Tree;
 import com.sun.source.tree.Tree.Kind;
 import com.sun.source.tree.VariableTree;
 import com.sun.source.util.TreePath;
 import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
 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.io.UncheckedIOException;
 import java.net.MalformedURLException;
-import java.net.URI;
-import java.nio.file.Files;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.EnumMap;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CompletableFuture;
@@ -58,7 +53,9 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiConsumer;
+import java.util.function.IntFunction;
 import java.util.prefs.Preferences;
+import java.util.stream.Collectors;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.ExecutableElement;
@@ -83,6 +80,8 @@
 import org.eclipse.lsp4j.CompletionItemKind;
 import org.eclipse.lsp4j.CompletionList;
 import org.eclipse.lsp4j.CompletionParams;
+import org.eclipse.lsp4j.CompletionTriggerKind;
+import org.eclipse.lsp4j.CreateFile;
 import org.eclipse.lsp4j.DefinitionParams;
 import org.eclipse.lsp4j.Diagnostic;
 import org.eclipse.lsp4j.DiagnosticSeverity;
@@ -106,25 +105,33 @@
 import org.eclipse.lsp4j.MessageParams;
 import org.eclipse.lsp4j.MessageType;
 import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.PrepareRenameParams;
+import org.eclipse.lsp4j.PrepareRenameResult;
 import org.eclipse.lsp4j.PublishDiagnosticsParams;
 import org.eclipse.lsp4j.Range;
 import org.eclipse.lsp4j.ReferenceParams;
+import org.eclipse.lsp4j.RenameFile;
 import org.eclipse.lsp4j.RenameParams;
+import org.eclipse.lsp4j.ResourceOperation;
 import org.eclipse.lsp4j.SignatureHelp;
 import org.eclipse.lsp4j.SignatureHelpParams;
 import org.eclipse.lsp4j.SymbolInformation;
-import org.eclipse.lsp4j.SymbolKind;
 import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
 import org.eclipse.lsp4j.TextDocumentEdit;
 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.jsonrpc.messages.ResponseErrorCode;
 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.classpath.ClassPath;
+import org.netbeans.api.java.lexer.JavaTokenId;
+import org.netbeans.api.java.queries.SourceJavadocAttacher;
+import org.netbeans.api.java.source.ClasspathInfo;
 import org.netbeans.api.java.source.CompilationController;
 import org.netbeans.api.java.source.CompilationInfo;
 import org.netbeans.api.java.source.CompilationInfo.CacheClearPolicy;
@@ -139,6 +146,7 @@
 import org.netbeans.api.java.source.WorkingCopy;
 import org.netbeans.api.java.source.support.ReferencesCount;
 import org.netbeans.api.java.source.ui.ElementJavadoc;
+import org.netbeans.api.lexer.TokenSequence;
 import org.netbeans.modules.editor.java.GoToSupport;
 import org.netbeans.modules.editor.java.GoToSupport.Context;
 import org.netbeans.modules.editor.java.GoToSupport.GoToTarget;
@@ -147,41 +155,59 @@
 import org.netbeans.modules.java.completion.JavaCompletionTask.Options;
 import org.netbeans.modules.java.completion.JavaDocumentationTask;
 import org.netbeans.modules.java.editor.base.semantic.MarkOccurrencesHighlighterBase;
+import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
 import org.netbeans.modules.java.editor.options.MarkOccurencesSettings;
+import org.netbeans.modules.java.hints.errors.CreateFixBase;
 import org.netbeans.modules.java.hints.errors.ImportClass;
 import org.netbeans.modules.java.hints.infrastructure.CreatorBasedLazyFixList;
 import org.netbeans.modules.java.hints.infrastructure.ErrorHintsProvider;
+import org.netbeans.modules.java.hints.introduce.IntroduceFixBase;
+import org.netbeans.modules.java.hints.introduce.IntroduceHint;
+import org.netbeans.modules.java.hints.introduce.IntroduceKind;
 import org.netbeans.modules.java.hints.project.IncompleteClassPath;
 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.lsp.server.Utils;
+import org.netbeans.modules.java.lsp.server.debugging.utils.ErrorUtilities;
 import org.netbeans.modules.java.source.ElementHandleAccessor;
 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.impl.indexing.implspi.ActiveDocumentProvider.IndexingAware;
 import org.netbeans.modules.parsing.spi.ParseException;
 import org.netbeans.modules.parsing.spi.SchedulerEvent;
 import org.netbeans.modules.refactoring.api.Problem;
 import org.netbeans.modules.refactoring.api.RefactoringElement;
 import org.netbeans.modules.refactoring.api.RefactoringSession;
+import org.netbeans.modules.refactoring.api.RenameRefactoring;
 import org.netbeans.modules.refactoring.api.WhereUsedQuery;
+import org.netbeans.modules.refactoring.api.impl.APIAccessor;
+import org.netbeans.modules.refactoring.api.impl.SPIAccessor;
+import org.netbeans.modules.refactoring.java.spi.hooks.JavaModificationResult;
+import org.netbeans.modules.refactoring.plugins.FileRenamePlugin;
+import org.netbeans.modules.refactoring.spi.RefactoringCommit;
+import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
+import org.netbeans.modules.refactoring.spi.Transaction;
 import org.netbeans.spi.editor.hints.EnhancedFix;
 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.editor.hints.Severity;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
 import org.netbeans.spi.java.hints.JavaFix;
 import org.openide.cookies.EditorCookie;
 import org.openide.filesystems.FileObject;
-import org.openide.filesystems.FileUtil;
-import org.openide.filesystems.URLMapper;
-import org.openide.modules.Places;
 import org.openide.text.NbDocument;
 import org.openide.text.PositionBounds;
 import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
 import org.openide.util.RequestProcessor;
+import org.openide.util.WeakSet;
 import org.openide.util.lookup.Lookups;
+import org.openide.util.lookup.ServiceProvider;
 
 /**
  *
@@ -197,41 +223,94 @@
     private NbCodeLanguageClient client;
 
     public TextDocumentServiceImpl() {
+        Lookup.getDefault().lookup(RefreshDocument.class).register(this);
+    }
+
+    private void reRunDiagnostics() {
+        Set<String> documents = new HashSet<>(openedDocuments.keySet());
+
+        for (String doc : documents) {
+            runDiagnoticTasks(doc);
+        }
+    }
+
+    @ServiceProvider(service=IndexingAware.class, position=0)
+    public static final class RefreshDocument implements IndexingAware {
+
+        private final Set<TextDocumentServiceImpl> delegates = new WeakSet<>();
+
+        public synchronized void register(TextDocumentServiceImpl delegate) {
+            delegates.add(delegate);
+        }
+
+        @Override
+        public void indexingComplete(Set<URL> indexedRoots) {
+            TextDocumentServiceImpl[] delegates;
+            synchronized (this) {
+                delegates = this.delegates.toArray(new TextDocumentServiceImpl[this.delegates.size()]);
+            }
+            for (TextDocumentServiceImpl delegate : delegates) {
+                delegate.reRunDiagnostics();
+            }
+        }
     }
 
     @Override
     public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams params) {
         try {
             String uri = params.getTextDocument().getUri();
-            FileObject file = fromUri(uri);
+            FileObject file = Utils.fromUri(uri);
             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(client, uri), 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();
+            final int caret = Utils.getOffset(doc, params.getPosition());
+            final CompletionList completionList = new CompletionList();
+            ParserManager.parse(Collections.singletonList(Source.create(doc)), new UserTask() {
+                @Override
+                public void run(ResultIterator resultIterator) throws Exception {
+                    TokenSequence<JavaTokenId> ts = resultIterator.getSnapshot().getTokenHierarchy().tokenSequence(JavaTokenId.language());
+                    if (ts.move(caret) == 0 || !ts.moveNext()) {
+                        if (!ts.movePrevious()) {
+                            ts.moveNext();
+                        }
+                    }
+                    int len = caret - ts.offset();
+                    boolean allCompletion = params.getContext() != null && params.getContext().getTriggerKind() == CompletionTriggerKind.TriggerForIncompleteCompletions
+                            || len > 0 && ts.token().length() >= len && ts.token().id() == JavaTokenId.IDENTIFIER;
+                    CompilationController controller = CompilationController.get(resultIterator.getParserResult(ts.offset()));
+                    controller.toPhase(JavaSource.Phase.RESOLVED);
+                    JavaCompletionTask<CompletionItem> task = JavaCompletionTask.create(caret, new ItemFactoryImpl(client, controller, uri, ts.offset()), allCompletion ? EnumSet.of(Options.ALL_COMPLETION) : EnumSet.noneOf(Options.class), () -> false);
+                    task.run(resultIterator);
+                    List<CompletionItem> results = task.getResults();
+                    if (results != null) {
+                        for (Iterator<CompletionItem> it = results.iterator(); it.hasNext();) {
+                            CompletionItem item = it.next();
+                            if (item == null) {
+                                it.remove();
+                            }
+                        }
+                        completionList.setItems(results);
+                    }
+                    completionList.setIsIncomplete(task.hasAdditionalClasses());
                 }
-            }
-            return CompletableFuture.completedFuture(Either.<List<CompletionItem>, CompletionList>forRight(new CompletionList(result)));
+            });
+            return CompletableFuture.completedFuture(Either.<List<CompletionItem>, CompletionList>forRight(completionList));
         } catch (IOException | ParseException ex) {
             throw new IllegalStateException(ex);
-            }
         }
+    }
 
     public static final class CompletionData {
         public String uri;
+        public int offset;
         public String kind;
         public String[] elementHandle;
 
         public CompletionData() {
         }
 
-        public CompletionData(String uri, String kind, String[] elementHandle) {
+        public CompletionData(String uri, int offset, String kind, String[] elementHandle) {
             this.uri = uri;
+            this.offset = offset;
             this.kind = kind;
             this.elementHandle = elementHandle;
         }
@@ -252,30 +331,44 @@
 
         private final LanguageClient client;
         private final String uri;
+        private final int offset;
+        private final CompilationInfo info;
+        private final Scope scope;
 
-        public ItemFactoryImpl(LanguageClient client, String uri) {
+        public ItemFactoryImpl(LanguageClient client, CompilationInfo info, String uri, int offset) {
             this.client = client;
             this.uri = uri;
+            this.offset = offset;
+            this.info = info;
+            this.scope = info.getTrees().getScope(info.getTreeUtilities().pathFor(offset));
         }
 
         private static final Set<String> SUPPORTED_ELEMENT_KINDS = new HashSet<>(Arrays.asList("PACKAGE", "CLASS", "INTERFACE", "ENUM", "ANNOTATION_TYPE", "METHOD", "CONSTRUCTOR", "INSTANCE_INIT", "STATIC_INIT", "FIELD", "ENUM_CONSTANT", "TYPE_PARAMETER", "MODULE"));
+
         private void setCompletionData(CompletionItem ci, Element el) {
             if (SUPPORTED_ELEMENT_KINDS.contains(el.getKind().name())) {
-                ci.setData(new CompletionData(uri, el.getKind().name(), SourceUtils.getJVMSignature(ElementHandle.create(el))));
+                setCompletionData(ci, ElementHandle.create(el));
             }
         }
 
+        private void setCompletionData(CompletionItem ci, ElementHandle handle) {
+            ci.setData(new CompletionData(uri, offset, handle.getKind().name(), SourceUtils.getJVMSignature(handle)));
+        }
+
         @Override
         public CompletionItem createKeywordItem(String kwd, String postfix, int substitutionOffset, boolean smartType) {
             CompletionItem item = new CompletionItem(kwd);
             item.setKind(CompletionItemKind.Keyword);
+            item.setSortText(String.format("%4d%s", smartType ? 670 : 1670, kwd)); //NOI18N
             return item;
         }
 
         @Override
         public CompletionItem createPackageItem(String pkgFQN, int substitutionOffset, boolean inPackageStatement) {
-            CompletionItem item = new CompletionItem(pkgFQN.substring(pkgFQN.lastIndexOf('.') + 1));
+            final String simpleName = pkgFQN.substring(pkgFQN.lastIndexOf('.') + 1);
+            CompletionItem item = new CompletionItem(simpleName);
             item.setKind(CompletionItemKind.Folder);
+            item.setSortText(String.format("%4d%s#%s", 1900, simpleName, pkgFQN)); //NOI18N
             return item;
         }
 
@@ -283,13 +376,34 @@
         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()));
+            String name = elem.getQualifiedName().toString();
+            int idx = name.lastIndexOf('.');
+            String pkgName = idx < 0 ? "" : name.substring(0, idx);
+            if (!pkgName.isEmpty()) {
+                item.setDetail(name.substring(0, idx));
+            }
+            item.setSortText(String.format("%4d%s#%2d#%s", smartType ? 800 : 1800, elem.getSimpleName().toString(), Utilities.getImportanceLevel(name), pkgName)); //NOI18N
             setCompletionData(item, elem);
             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
+            TypeElement te = handle.resolve(info);
+            if (te != null && info.getTrees().isAccessible(scope, te)) {
+                CompletionItem item = new CompletionItem(te.getSimpleName().toString());
+                String name = handle.getQualifiedName();
+                int idx = name.lastIndexOf('.');
+                String pkgName = idx < 0 ? "" : name.substring(0, idx);
+                if (!pkgName.isEmpty()) {
+                    item.setDetail(pkgName);
+                }
+                item.setKind(elementKind2CompletionItemKind(handle.getKind()));
+                item.setSortText(String.format("%4d%s#%2d#%s", 1800, te.getSimpleName().toString(), Utilities.getImportanceLevel(name), pkgName)); //NOI18N
+                setCompletionData(item, handle);
+                return item;
+            }
+            return null;
         }
 
         @Override
@@ -301,6 +415,7 @@
         public CompletionItem createTypeParameterItem(TypeParameterElement elem, int substitutionOffset) {
             CompletionItem item = new CompletionItem(elem.getSimpleName().toString());
             item.setKind(elementKind2CompletionItemKind(elem.getKind()));
+            item.setSortText(String.format("%4d%s", 1700, elem.getSimpleName().toString())); //NOI18N
             return item;
         }
 
@@ -308,6 +423,8 @@
         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()));
+            int priority = elem.getKind() == ElementKind.ENUM_CONSTANT || elem.getKind() == ElementKind.FIELD ? smartType ? 300 : 1300 : smartType ? 200 : 1200;
+            item.setSortText(String.format("%4d%s", priority, elem.getSimpleName().toString())); //NOI18N
             setCompletionData(item, elem);
             return item;
         }
@@ -316,6 +433,7 @@
         public CompletionItem createVariableItem(CompilationInfo info, String varName, int substitutionOffset, boolean newVarName, boolean smartType) {
             CompletionItem item = new CompletionItem(varName);
             item.setKind(CompletionItemKind.Variable);
+            item.setSortText(String.format("%4d%s", smartType ? 200 : 1200, varName)); //NOI18N
             return item;
         }
 
@@ -327,18 +445,28 @@
             String sep = "";
             label.append(elem.getSimpleName().toString());
             label.append("(");
+            StringBuilder sortParams = new StringBuilder();
+            sortParams.append('(');
+            int cnt = 0;
             while(it.hasNext() && tIt.hasNext()) {
                 TypeMirror tm = tIt.next();
                 if (tm == null) {
                     break;
                 }
                 label.append(sep);
-                label.append(Utilities.getTypeName(info, tm, false, elem.isVarArgs() && !tIt.hasNext()).toString());
+                String paramTypeName = Utilities.getTypeName(info, tm, false, elem.isVarArgs() && !tIt.hasNext()).toString();
+                label.append(paramTypeName);
                 label.append(' ');
                 label.append(it.next().getSimpleName().toString());
                 sep = ", ";
+                sortParams.append(paramTypeName);
+                if (tIt.hasNext()) {
+                    sortParams.append(',');
+                }
+                cnt++;
             }
             label.append(") : ");
+            sortParams.append(')');
             TypeMirror retType = type.getReturnType();
             label.append(Utilities.getTypeName(info, retType, false).toString());
             CompletionItem item = new CompletionItem(label.toString());
@@ -351,22 +479,52 @@
             }
             item.setInsertText(insertText.toString());
             item.setInsertTextFormat(InsertTextFormat.PlainText);
+            int priority = elem.getKind() == ElementKind.METHOD ? smartType ? 500 : 1500 : smartType ? 650 : 1650;
+            item.setSortText(String.format("%4d%s#%2d%s", priority, elem.getSimpleName().toString(), cnt, sortParams)); //NOI18N
             setCompletionData(item, elem);
             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);
+            CompletionItem item = createExecutableItem(info, elem, type, substitutionOffset, null, false, isDeprecated, false, false, false, -1, false);
+            item.setLabel(name != null ? name : elem.getEnclosingElement().getSimpleName().toString());
+            StringBuilder insertText = new StringBuilder();
+            insertText.append(item.getLabel());
+            insertText.append("(");
+            if (elem.getParameters().isEmpty()) {
+                insertText.append(")");
+            }
+            item.setInsertText(insertText.toString());
+            item.setKind(CompletionItemKind.Constructor);
+            item.setSortText(String.format("%4d%s", name != null ? 1550 : 1650, item.getSortText().substring(4))); //NOI18N
             setCompletionData(item, elem);
             return item;
         }
 
         @Override
         public CompletionItem createOverrideMethodItem(CompilationInfo info, ExecutableElement elem, ExecutableType type, int substitutionOffset, boolean implement) {
-            CompletionItem item = new CompletionItem(elem.getSimpleName().toString() + " - override");
+            CompletionItem item = createExecutableItem(info, elem, type, substitutionOffset, null, false, false, false, false, false, -1, false);
+            item.setLabel(String.format("%s - %s", item.getLabel(), implement ? "implement" : "override"));
+            item.setInsertText(null);
             item.setKind(elementKind2CompletionItemKind(elem.getKind()));
+            try {
+                List<TextEdit> textEdits = modify2TextEdits(JavaSource.forFileObject(info.getFileObject()), wc -> {
+                    wc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
+                    TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                    if (implement) {
+                        GeneratorUtils.generateAbstractMethodImplementation(wc, tp, elem, offset);
+                    } else {
+                        GeneratorUtils.generateMethodOverride(wc, tp, elem, offset);
+                    }
+                });
+                if (!textEdits.isEmpty()) {
+                    item.setTextEdit(textEdits.get(0));
+                }
+            } catch (IOException ex) {
+                //TODO: include stack trace:
+                client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+            }
             setCompletionData(item, elem);
             return item;
         }
@@ -429,6 +587,29 @@
             item.setInsertText(label);
             item.setInsertTextFormat(InsertTextFormat.PlainText);
             item.setAdditionalTextEdits(currentClassImport);
+            String sortText = memberElem.getSimpleName().toString();
+            if (memberElem.getKind().isField()) {
+                sortText += String.format("#%s", Utilities.getTypeName(info, type, false)); //NOI18N
+            } else {
+                StringBuilder sortParams = new StringBuilder();
+                sortParams.append('(');
+                int cnt = 0;
+                Iterator<? extends TypeMirror> tIt = ((ExecutableType)memberType).getParameterTypes().iterator();
+                while(tIt.hasNext()) {
+                    TypeMirror tm = tIt.next();
+                    if (tm == null) {
+                        break;
+                    }
+                    sortParams.append(Utilities.getTypeName(info, tm, false, ((ExecutableElement)memberElem).isVarArgs() && !tIt.hasNext()).toString());
+                    if (tIt.hasNext()) {
+                        sortParams.append(',');
+                    }
+                    cnt++;
+                }
+                sortParams.append(')');
+                sortText += String.format("#%2d#%s#s", cnt, sortParams.toString(), Utilities.getTypeName(info, type, false)); //NOI18N
+            }
+            item.setSortText(String.format("%4d%s", memberElem.getKind().isField() ? 720 : 750, sortText)); //NOI18N
             setCompletionData(item, memberElem);
             return item;
         }
@@ -499,17 +680,41 @@
         }
         CompletionData data = new Gson().fromJson(rawData, CompletionData.class);
         try {
-            FileObject file = fromUri(data.uri);
+            FileObject file = Utils.fromUri(data.uri);
             EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
             Document doc = ec.openDocument();
-            ElementHandle<Element> handle = ElementHandleAccessor.getInstance().create(ElementKind.valueOf(data.kind), data.elementHandle);
-            JavaDocumentationTask<Future<String>> task = JavaDocumentationTask.create(-1, handle, new JavaDocumentationTask.DocumentationFactory<Future<String>>() {
+            final ElementHandle<Element> handle = ElementHandleAccessor.getInstance().create(ElementKind.valueOf(data.kind), data.elementHandle);
+            final JavaDocumentationTask<Future<String>> task = JavaDocumentationTask.create(-1, handle, new JavaDocumentationTask.DocumentationFactory<Future<String>>() {
                 @Override
                 public Future<String> create(CompilationInfo compilationInfo, Element element, Callable<Boolean> cancel) {
                     return ElementJavadoc.create(compilationInfo, element, cancel).getTextAsync();
                 }
             }, () -> false);
-            ParserManager.parse(Collections.singletonList(Source.create(doc)), task);
+            LineMap[] lm = new LineMap[1];
+            ModificationResult mr = ModificationResult.runModificationTask(Collections.singletonList(Source.create(doc)), new UserTask() {
+                @Override
+                public void run(ResultIterator resultIterator) throws Exception {
+                    task.run(resultIterator);
+                    if (ci.getDetail() != null) {
+                        final WorkingCopy copy = WorkingCopy.get(resultIterator.getParserResult(data.offset));
+                        copy.toPhase(JavaSource.Phase.RESOLVED);
+                        Element e = handle.resolve(copy);
+                        if (e != null) {
+                            copy.rewrite(copy.getCompilationUnit(), GeneratorUtilities.get(copy).addImports(copy.getCompilationUnit(), Collections.singleton(e)));
+                        }
+                        lm[0] = copy.getCompilationUnit().getLineMap();
+                    }
+                }
+            });
+            List<? extends ModificationResult.Difference> diffs = mr.getDifferences(file);
+            if (diffs != null && !diffs.isEmpty()) {
+                List<TextEdit> edits = new ArrayList<>();
+                for (ModificationResult.Difference diff : diffs) {
+                    edits.add(new TextEdit(new Range(Utils.createPosition(lm[0], diff.getStartPosition().getOffset()),
+                            Utils.createPosition(lm[0], diff.getEndPosition().getOffset())), diff.getNewText()));
+                }
+                ci.setAdditionalTextEdits(edits);
+            }
             Future<String> futureJavadoc = task.getDocumentation();
             CompletableFuture<CompletionItem> result = new CompletableFuture<CompletionItem>() {
                 @Override
@@ -539,12 +744,55 @@
     }
 
     public static String html2MD(String html) {
-        return FlexmarkHtmlConverter.builder().build().convert(html).replaceAll("<br />[ \n]*$", "");
+        int idx = html.indexOf("<p id=\"not-found\">"); // strip "No Javadoc found" message
+        return FlexmarkHtmlConverter.builder().build().convert(idx >= 0 ? html.substring(0, idx) : html).replaceAll("<br />[ \n]*$", "");
     }
 
     @Override
     public CompletableFuture<Hover> hover(HoverParams params) {
-        throw new UnsupportedOperationException("Not supported yet.");
+        try {
+            String uri = params.getTextDocument().getUri();
+            FileObject file = Utils.fromUri(uri);
+            EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
+            Document doc = ec.openDocument();
+            final JavaDocumentationTask<Future<String>> task = JavaDocumentationTask.create(Utils.getOffset(doc, params.getPosition()), null, new JavaDocumentationTask.DocumentationFactory<Future<String>>() {
+                @Override
+                public Future<String> create(CompilationInfo compilationInfo, Element element, Callable<Boolean> cancel) {
+                    return ElementJavadoc.create(compilationInfo, element, cancel).getTextAsync();
+                }
+            }, () -> false);
+            ParserManager.parse(Collections.singletonList(Source.create(doc)), new UserTask() {
+                @Override
+                public void run(ResultIterator resultIterator) throws Exception {
+                    task.run(resultIterator);
+                }
+            });
+            Future<String> futureJavadoc = task.getDocumentation();
+            CompletableFuture<Hover> result = new CompletableFuture<Hover>() {
+                @Override
+                public boolean cancel(boolean mayInterruptIfRunning) {
+                    return futureJavadoc != null && futureJavadoc.cancel(mayInterruptIfRunning) && super.cancel(mayInterruptIfRunning);
+                }
+            };
+            JAVADOC_WORKER.post(() -> {
+                try {
+                    String javadoc = futureJavadoc != null ? futureJavadoc.get() : null;
+                    if (javadoc != null) {
+                        MarkupContent markup = new MarkupContent();
+                        markup.setKind("markdown");
+                        markup.setValue(html2MD(javadoc));
+                        result.complete(new Hover(markup));
+                    } else {
+                        result.complete(null);
+                    }
+                } catch (ExecutionException | InterruptedException ex) {
+                    result.completeExceptionally(ex);
+                }
+            });
+            return result;
+        } catch (IOException | ParseException ex) {
+            throw new IllegalStateException(ex);
+        }
     }
 
     @Override
@@ -561,7 +809,7 @@
             js.runUserActionTask(cc -> {
                 cc.toPhase(JavaSource.Phase.RESOLVED);
                 Document doc = cc.getSnapshot().getSource().getDocument(true);
-                int offset = getOffset(doc, params.getPosition());
+                int offset = Utils.getOffset(doc, params.getPosition());
                 Context context = GoToSupport.resolveContext(cc, doc, offset, false, false);
                 if (context == null) {
                     return ;
@@ -579,21 +827,79 @@
         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 && target[0].resourceName != null) {
+                    // try to attach sources
+                    final ClassPath cp = ClassPathSupport.createProxyClassPath(
+                            target[0].cpInfo.getClassPath(ClasspathInfo.PathKind.BOOT),
+                            target[0].cpInfo.getClassPath(ClasspathInfo.PathKind.COMPILE),
+                            target[0].cpInfo.getClassPath(ClasspathInfo.PathKind.SOURCE));
+                    final FileObject resource = cp.findResource(target[0].resourceName);
+                    if (resource != null) {
+                        final FileObject root = cp.findOwnerRoot(resource);
+                        if (root != null) {
+                            final CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> future = new CompletableFuture<>();
+                            SourceJavadocAttacher.attachSources(root.toURL(), new SourceJavadocAttacher.AttachmentListener() {
+                                @Override
+                                public void attachmentSucceeded() {
+                                    Object[] openInfo = ElementOpenAccessor.getInstance().getOpenInfo(target[0].cpInfo, target[0].elementToOpen, new AtomicBoolean());
+                                    if (openInfo != null && (int) openInfo[1] != (-1) && (int) openInfo[2] != (-1) && openInfo[3] != null) {
+                                        FileObject file = (FileObject) openInfo[0];
+                                        int start = (int) openInfo[1];
+                                        int end = (int) openInfo[2];
+                                        LineMap lm = (LineMap) openInfo[3];
+                                        future.complete(Either.forLeft(Collections.singletonList(new Location(Utils.toUri(file),
+                                                new Range(Utils.createPosition(lm, start), Utils.createPosition(lm, end))))));
+                                    }
+                                }
+                                @Override
+                                public void attachmentFailed() {
+                                    try {
+                                        FileObject generated = org.netbeans.modules.java.classfile.CodeGenerator.generateCode(target[0].cpInfo, target[0].elementToOpen);
+                                        if (generated != null) {
+                                            final int[] pos = new int[] {-1};
+                                            try {
+                                                JavaSource.create(target[0].cpInfo, generated).runUserActionTask(new Task<CompilationController>() {
+                                                    @Override public void run(CompilationController parameter) throws Exception {
+                                                        parameter.toPhase(JavaSource.Phase.RESOLVED);
+                                                        Element el = target[0].elementToOpen.resolve(parameter);
+                                                        if (el != null) {
+                                                            TreePath p = parameter.getTrees().getPath(el);
+                                                            if (p != null) {
+                                                                pos[0] = (int) parameter.getTrees().getSourcePositions().getStartPosition(p.getCompilationUnit(), p.getLeaf());
+                                                            }
+                                                        }
+                                                    }
+                                                }, true);
+                                            } catch (IOException ex) {
+                                            }
+                                            int offset = pos[0] != -1 ? pos[0] : 0;
+                                            future.complete(Either.forLeft(Collections.singletonList(new Location(Utils.toUri(generated), new Range(Utils.createPosition(generated, offset), Utils.createPosition(generated, offset))))));
+                                            return;
+                                        }
+                                    } catch (Exception e) {
+                                    }
+                                    future.complete(Either.forLeft((Collections.emptyList())));
+                                }
+                            });
+                            return future;
+                        }
+                    }
+                }
                 if (openInfo != null && (int) openInfo[1] != (-1) && (int) openInfo[2] != (-1) && openInfo[3] != null) {
                     FileObject file = (FileObject) openInfo[0];
                     int start = (int) openInfo[1];
                     int end = (int) openInfo[2];
                     LineMap lm = (LineMap) openInfo[3];
-                    result.add(new Location(toUri(file),
-                                            new Range(createPosition(lm, start),
-                                                      createPosition(lm, end))));
+                    result.add(new Location(Utils.toUri(file),
+                                            new Range(Utils.createPosition(lm, start),
+                                                      Utils.createPosition(lm, end))));
                 }
             } else {
                 int start = target[0].offsetToOpen;
                 int end = target[0].endPos;
                 result.add(new Location(params.getTextDocument().getUri(),
-                                        new Range(createPosition(thisFileLineMap[0], start),
-                                                  createPosition(thisFileLineMap[0], end))));
+                                        new Range(Utils.createPosition(thisFileLineMap[0], start),
+                                                  Utils.createPosition(thisFileLineMap[0], end))));
             }
         }
         return CompletableFuture.completedFuture(Either.forLeft(result));
@@ -622,7 +928,7 @@
                     cc.toPhase(JavaSource.Phase.RESOLVED);
                     if (cancel.get()) return ;
                     Document doc = cc.getSnapshot().getSource().getDocument(true);
-                    TreePath path = cc.getTreeUtilities().pathFor(getOffset(doc, params.getPosition()));
+                    TreePath path = cc.getTreeUtilities().pathFor(Utils.getOffset(doc, params.getPosition()));
                     if (params.getContext().isIncludeDeclaration()) {
                         Element decl = cc.getTrees().getElement(path);
                         if (decl != null) {
@@ -630,7 +936,7 @@
                             if (declPath != null && cc.getCompilationUnit() == declPath.getCompilationUnit()) {
                                 Range range = declarationRange(cc, declPath);
                                 if (range != null) {
-                                    locations.add(new Location(toUri(cc.getFileObject()),
+                                    locations.add(new Location(Utils.toUri(cc.getFileObject()),
                                                                range));
                                 }
                             } else {
@@ -645,7 +951,7 @@
                                         if (declPath2 != null) {
                                             Range range = declarationRange(nestedCC, declPath2);
                                             if (range != null) {
-                                                locations.add(new Location(toUri(nestedCC.getFileObject()),
+                                                locations.add(new Location(Utils.toUri(nestedCC.getFileObject()),
                                                                            range));
                                             }
                                         }
@@ -663,23 +969,23 @@
                 p = query[0].checkParameters();
                 if (cancel.get()) return ;
                 if (p != null && p.isFatal()) {
-                    result.completeExceptionally(new IllegalStateException(p.getMessage()));
+                    ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
                     return ;
                 }
                 p = query[0].preCheck();
                 if (p != null && p.isFatal()) {
-                    result.completeExceptionally(new IllegalStateException(p.getMessage()));
+                    ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
                     return ;
                 }
                 if (cancel.get()) return ;
                 p = query[0].prepare(refactoring);
                 if (p != null && p.isFatal()) {
-                    result.completeExceptionally(new IllegalStateException(p.getMessage()));
+                    ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
                     return ;
                 }
                 for (RefactoringElement re : refactoring.getRefactoringElements()) {
                     if (cancel.get()) return ;
-                    locations.add(new Location(toUri(re.getParentFile()), toRange(re.getPosition())));
+                    locations.add(new Location(Utils.toUri(re.getParentFile()), toRange(re.getPosition())));
                 }
 
                 refactoring.finished();
@@ -710,8 +1016,8 @@
         if (span == null) {
             return null;
         }
-        return new Range(createPosition(info.getCompilationUnit().getLineMap(), span[0]),
-                         createPosition(info.getCompilationUnit().getLineMap(), span[1]));
+        return new Range(Utils.createPosition(info.getCompilationUnit().getLineMap(), span[0]),
+                         Utils.createPosition(info.getCompilationUnit().getLineMap(), span[1]));
     }
 
     private static Range toRange(PositionBounds bounds) throws IOException {
@@ -742,12 +1048,12 @@
             js.runUserActionTask(cc -> {
                 cc.toPhase(JavaSource.Phase.RESOLVED);
                 Document doc = cc.getSnapshot().getSource().getDocument(true);
-                int offset = getOffset(doc, params.getPosition());
+                int offset = Utils.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]))));
+                        result.add(new DocumentHighlight(new Range(Utils.createPosition(cc.getCompilationUnit(), span[0]),
+                                                                   Utils.createPosition(cc.getCompilationUnit(), span[1]))));
                     }
                 }
             }, true);
@@ -783,12 +1089,9 @@
         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))
+        Range range = Utils.treeRange(info, path.getLeaf());
+        if (range == null)
             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);
@@ -796,61 +1099,28 @@
                 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?
+        String simpleName;
+
+        if (el.getKind() == ElementKind.CONSTRUCTOR) {
+            simpleName = el.getEnclosingElement().getSimpleName().toString();
+        } else {
+            simpleName = el.getSimpleName().toString();
         }
+
+        return new DocumentSymbol(simpleName, Utils.elementKind2SymbolKind(el.getKind()), range, range, null, children);
     }
 
     @Override
     public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
         Document doc = openedDocuments.get(params.getTextDocument().getUri());
-        if (doc == null) {
+        JavaSource js = JavaSource.forDocument(doc);
+        if (doc == null || js == 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<>();
+        if (id2Errors != null) {
         for (Diagnostic diag : params.getContext().getDiagnostics()) {
             ErrorDescription err = id2Errors.get(diag.getCode().getLeft());
 
@@ -932,8 +1202,108 @@
                         client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
                     }
                 }
+                if (f instanceof CreateFixBase) {
+                    try {
+                        CreateFixBase cf = (CreateFixBase) f;
+                        ModificationResult changes = cf.getModificationResult();
+                        List<Either<TextDocumentEdit, ResourceOperation>> documentChanges = new ArrayList<>();
+                        Set<File> newFiles = changes.getNewFiles();
+                        if (newFiles.size() > 1) {
+                            throw new IllegalStateException();
+                        }
+                        String newFilePath = null;
+                        for (File newFile : newFiles) {
+                            newFilePath = newFile.getPath();
+                            documentChanges.add(Either.forRight(new CreateFile(newFilePath)));
+                        }
+                        outer: for (FileObject fileObject : changes.getModifiedFileObjects()) {
+                            List<? extends ModificationResult.Difference> diffs = changes.getDifferences(fileObject);
+                            if (diffs != null) {
+                                List<TextEdit> edits = new ArrayList<>();
+                                for (ModificationResult.Difference diff : diffs) {
+                                    String newText = diff.getNewText();
+                                    if (diff.getKind() == ModificationResult.Difference.Kind.CREATE) {
+                                        if (newFilePath != null) {
+                                            documentChanges.add(Either.forLeft(new TextDocumentEdit(new VersionedTextDocumentIdentifier(newFilePath, -1),
+                                                    Collections.singletonList(new TextEdit(new Range(Utils.createPosition(fileObject, 0), Utils.createPosition(fileObject, 0)),
+                                                            newText != null ? newText : "")))));
+                                        }
+                                        continue outer;
+                                    } else {
+                                        edits.add(new TextEdit(new Range(Utils.createPosition(fileObject, diff.getStartPosition().getOffset()),
+                                                                         Utils.createPosition(fileObject, diff.getEndPosition().getOffset())),
+                                                               newText != null ? newText : ""));
+                                    }
+                                }
+                                documentChanges.add(Either.forLeft(new TextDocumentEdit(new VersionedTextDocumentIdentifier(Utils.toUri(fileObject), -1), edits)));
+                            }
+                        }
+                        CodeAction codeAction = new CodeAction(f.getText());
+                        codeAction.setKind(CodeActionKind.QuickFix);
+                        codeAction.setEdit(new WorkspaceEdit(documentChanges));
+                        result.add(Either.forRight(codeAction));
+                    } catch (IOException ex) {
+                        client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+                    }
+                }
             }
         }
+        }
+
+        try {
+            js.runUserActionTask(cc -> {
+                cc.toPhase(JavaSource.Phase.RESOLVED);
+                //code generators:
+                for (CodeGenerator codeGenerator : Lookup.getDefault().lookupAll(CodeGenerator.class)) {
+                    for (CodeAction codeAction : codeGenerator.getCodeActions(cc, params)) {
+                        result.add(Either.forRight(codeAction));
+                    }
+                }
+                //introduce hints
+                Range range = params.getRange();
+                if (!range.getStart().equals(range.getEnd())) {
+                    for (ErrorDescription err : IntroduceHint.computeError(cc, Utils.getOffset(doc, range.getStart()), Utils.getOffset(doc, range.getEnd()), new EnumMap<IntroduceKind, Fix>(IntroduceKind.class), new EnumMap<IntroduceKind, String>(IntroduceKind.class), new AtomicBoolean())) {
+                        for (Fix fix : err.getFixes().getFixes()) {
+                            if (fix instanceof IntroduceFixBase) {
+                                try {
+                                    ModificationResult changes = ((IntroduceFixBase) fix).getModificationResult();
+                                    if (changes != null) {
+                                        List<Either<TextDocumentEdit, ResourceOperation>> documentChanges = new ArrayList<>();
+                                        Set<? extends FileObject> fos = changes.getModifiedFileObjects();
+                                        if (fos.size() == 1) {
+                                            FileObject fileObject = fos.iterator().next();
+                                            List<? extends ModificationResult.Difference> diffs = changes.getDifferences(fileObject);
+                                            if (diffs != null) {
+                                                List<TextEdit> edits = new ArrayList<>();
+                                                for (ModificationResult.Difference diff : diffs) {
+                                                    String newText = diff.getNewText();
+                                                    edits.add(new TextEdit(new Range(Utils.createPosition(fileObject, diff.getStartPosition().getOffset()),
+                                                                                     Utils.createPosition(fileObject, diff.getEndPosition().getOffset())),
+                                                                           newText != null ? newText : ""));
+                                                }
+                                                documentChanges.add(Either.forLeft(new TextDocumentEdit(new VersionedTextDocumentIdentifier(Utils.toUri(fileObject), -1), edits)));
+                                            }
+                                            CodeAction codeAction = new CodeAction(fix.getText());
+                                            codeAction.setKind(CodeActionKind.RefactorExtract);
+                                            codeAction.setEdit(new WorkspaceEdit(documentChanges));
+                                            int renameOffset = ((IntroduceFixBase) fix).getNameOffset(changes);
+                                            if (renameOffset >= 0) {
+                                                codeAction.setCommand(new Command("Rename", "java.rename.element.at", Collections.singletonList(renameOffset)));
+                                            }
+                                            result.add(Either.forRight(codeAction));
+                                        }
+                                    }
+                                } catch (GeneratorUtils.DuplicateMemberException dme) {
+                                }
+                            }
+                        }
+                    }
+                }
+            }, true);
+        } catch (IOException ex) {
+            //TODO: include stack trace:
+            client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+        }
 
         return CompletableFuture.completedFuture(result);
     }
@@ -1001,14 +1371,166 @@
     }
 
     @Override
-    public CompletableFuture<WorkspaceEdit> rename(RenameParams arg0) {
-        throw new UnsupportedOperationException("Not supported yet.");
+    public CompletableFuture<Either<Range, PrepareRenameResult>> prepareRename(PrepareRenameParams params) {
+        JavaSource source = getSource(params.getTextDocument().getUri());
+        if (source == null) {
+            return CompletableFuture.completedFuture(Either.forLeft(null));
+        }
+        CompletableFuture<Either<Range, PrepareRenameResult>> result = new CompletableFuture<>();
+        try {
+            source.runUserActionTask(cc -> {
+                cc.toPhase(JavaSource.Phase.RESOLVED);
+                Document doc = cc.getSnapshot().getSource().getDocument(true);
+                int pos = Utils.getOffset(doc, params.getPosition());
+                TreePath path = cc.getTreeUtilities().pathFor(pos);
+                RenameRefactoring ref = new RenameRefactoring(Lookups.singleton(TreePathHandle.create(path, cc)));
+                ref.setNewName("any");
+                Problem p = ref.fastCheckParameters();
+                boolean hasFatalProblem = false;
+                while (p != null) {
+                    hasFatalProblem |= p.isFatal();
+                    p = p.getNext();
+                }
+                if (hasFatalProblem) {
+                    result.complete(null);
+                } else {
+                    //XXX: better range computation
+                    TokenSequence<JavaTokenId> ts = cc.getTokenHierarchy().tokenSequence(JavaTokenId.language());
+                    int d = ts.move(pos);
+                    if (ts.moveNext()) {
+                        if (d == 0 && ts.token().id() != JavaTokenId.IDENTIFIER) {
+                            ts.movePrevious();
+                        }
+                        Range r = new Range(Utils.createPosition(cc.getCompilationUnit(), ts.offset()),
+                                            Utils.createPosition(cc.getCompilationUnit(), ts.offset() + ts.token().length()));
+                        result.complete(Either.forRight(new PrepareRenameResult(r, ts.token().text().toString())));
+                    } else {
+                        result.complete(null);
+                    }
+                }
+            }, true);
+        } catch (IOException ex) {
+            result.completeExceptionally(ex);
+        }
+        return result;
+    }
+
+    @Override
+    public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
+        AtomicBoolean cancel = new AtomicBoolean();
+        Runnable[] cancelCallback = new Runnable[1];
+        CompletableFuture<WorkspaceEdit> result = new CompletableFuture<WorkspaceEdit>() {
+            @Override
+            public boolean cancel(boolean mayInterruptIfRunning) {
+                cancel.set(mayInterruptIfRunning);
+                if (cancelCallback[0] != null) {
+                    cancelCallback[0].run();
+                }
+                return super.cancel(mayInterruptIfRunning);
+            }
+        };
+        WORKER.post(() -> {
+            JavaSource js = getSource(params.getTextDocument().getUri());
+            try {
+                RenameRefactoring[] refactoring = new RenameRefactoring[1];
+                js.runUserActionTask(cc -> {
+                    cc.toPhase(JavaSource.Phase.RESOLVED);
+                    if (cancel.get()) return ;
+                    Document doc = cc.getSnapshot().getSource().getDocument(true);
+                    TreePath path = cc.getTreeUtilities().pathFor(Utils.getOffset(doc, params.getPosition()));
+                    List<Object> lookupContent = new ArrayList<>();
+
+                    lookupContent.add(TreePathHandle.create(path, cc));
+
+                    //from RenameRefactoringUI:
+                    Element selected = cc.getTrees().getElement(path);
+                    if (selected instanceof TypeElement && !((TypeElement) selected).getNestingKind().isNested()) {
+                        ElementHandle<TypeElement> handle = ElementHandle.create((TypeElement) selected);
+                        FileObject f = SourceUtils.getFile(handle, cc.getClasspathInfo());
+                        if (f != null && selected.getSimpleName().toString().equals(f.getName())) {
+                            lookupContent.add(f);
+                        }
+                    }
+
+                    refactoring[0] = new RenameRefactoring(Lookups.fixed(lookupContent.toArray(new Object[0])));
+                    refactoring[0].setNewName(params.getNewName());
+                    refactoring[0].setSearchInComments(true); //TODO?
+                }, true);
+                if (cancel.get()) return ;
+                cancelCallback[0] = () -> refactoring[0].cancelRequest();
+                RefactoringSession session = RefactoringSession.create("Rename");
+                Problem p;
+                p = refactoring[0].checkParameters();
+                if (cancel.get()) return ;
+                if (p != null && p.isFatal()) {
+                    ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
+                    return ;
+                }
+                p = refactoring[0].preCheck();
+                if (p != null && p.isFatal()) {
+                    ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
+                    return ;
+                }
+                if (cancel.get()) return ;
+                p = refactoring[0].prepare(session);
+                if (p != null && p.isFatal()) {
+                    ErrorUtilities.completeExceptionally(result, p.getMessage(), ResponseErrorCode.UnknownErrorCode);
+                    return ;
+                }
+                //TODO: check client capabilities!
+                List<Either<TextDocumentEdit, ResourceOperation>> resultChanges = new ArrayList<>();
+                List<Transaction> transactions = APIAccessor.DEFAULT.getCommits(session);
+                List<ModificationResult> results = new ArrayList<>();
+                for (Transaction t : transactions) {
+                    if (t instanceof RefactoringCommit) {
+                        RefactoringCommit c = (RefactoringCommit) t;
+                        for (org.netbeans.modules.refactoring.spi.ModificationResult refResult : SPIAccessor.DEFAULT.getTransactions(c)) {
+                            if (refResult instanceof JavaModificationResult) {
+                                results.add(((JavaModificationResult) refResult).delegate);
+                            } else {
+                                throw new IllegalStateException(refResult.getClass().toString());
+                            }
+                        }
+                    } else {
+                        throw new IllegalStateException(t.getClass().toString());
+                    }
+                }
+                for (ModificationResult mr : results) {
+                    for (FileObject modified : mr.getModifiedFileObjects()) {
+                        resultChanges.add(Either.forLeft(new TextDocumentEdit(new VersionedTextDocumentIdentifier(params.getTextDocument().getUri(), /*XXX*/-1), fileModifications(mr, modified, null))));
+                    }
+                }
+                List<RefactoringElementImplementation> fileChanges = APIAccessor.DEFAULT.getFileChanges(session);
+                for (RefactoringElementImplementation rei : fileChanges) {
+                    if (rei instanceof FileRenamePlugin.RenameFile) {
+                        String oldURI = params.getTextDocument().getUri();
+                        int dot = oldURI.lastIndexOf('.');
+                        int slash = oldURI.lastIndexOf('/');
+                        String newURI = oldURI.substring(0, slash + 1) + params.getNewName() + oldURI.substring(dot);
+                        ResourceOperation op = new RenameFile(oldURI, newURI);
+                        resultChanges.add(Either.forRight(op));
+                    } else {
+                        throw new IllegalStateException(rei.getClass().toString());
+                    }
+                }
+                for (RefactoringElement re : session.getRefactoringElements()) {
+                    //TODO: verify no unknown elements!
+                }
+
+                session.finished();
+
+                result.complete(new WorkspaceEdit(resultChanges));
+            } catch (Throwable ex) {
+                result.completeExceptionally(ex);
+            }
+        });
+        return result;
     }
 
     @Override
     public void didOpen(DidOpenTextDocumentParams params) {
         try {
-            FileObject file = fromUri(params.getTextDocument().getUri());
+            FileObject file = Utils.fromUri(params.getTextDocument().getUri());
             EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
             Document doc = ec.getDocument();
             // the document may be not opened yet. Clash with in-memory content can happen only if
@@ -1042,8 +1564,8 @@
         NbDocument.runAtomic((StyledDocument) doc, () -> {
             for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
                 try {
-                    int start = getOffset(doc, change.getRange().getStart());
-                    int end   = getOffset(doc, change.getRange().getEnd());
+                    int start = Utils.getOffset(doc, change.getRange().getStart());
+                    int end   = Utils.getOffset(doc, change.getRange().getEnd());
                     doc.remove(start, end - start);
                     doc.insertString(start, change.getText(), null);
                 } catch (BadLocationException ex) {
@@ -1057,8 +1579,16 @@
 
     @Override
     public void didClose(DidCloseTextDocumentParams params) {
-        openedDocuments.remove(params.getTextDocument().getUri());
-        reportNotificationDone("didClose", params);
+        try {
+            FileObject file = Utils.fromUri(params.getTextDocument().getUri());
+            EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
+            ec.close();
+            openedDocuments.remove(params.getTextDocument().getUri());
+        } catch (IOException ex) {
+            throw new IllegalStateException(ex);
+        } finally {
+            reportNotificationDone("didClose", params);
+        }
     }
 
     @Override
@@ -1076,7 +1606,14 @@
                 }, "errors", false);
                 BACKGROUND_TASKS.create(() -> {
                     computeDiags(u, (info, doc) -> {
-                        return new HintsInvoker(HintsSettings.getGlobalSettings(), new AtomicBoolean()).computeHints(info);
+                        Set<Severity> disabled = org.netbeans.modules.java.hints.spiimpl.Utilities.disableErrors(info.getFileObject());
+                        if (disabled.size() == Severity.values().length) {
+                            return Collections.emptyList();
+                        }
+                        return new HintsInvoker(HintsSettings.getGlobalSettings(), new AtomicBoolean()).computeHints(info)
+                                                                                                       .stream()
+                                                                                                       .filter(ed -> !disabled.contains(ed.getSeverity()))
+                                                                                                       .collect(Collectors.toList());
                     }, "hints", true);
                 }).schedule(DELAY);
             });
@@ -1087,7 +1624,7 @@
 
     private void computeDiags(String uri, ProduceErrors produceErrors, String keyPrefix, boolean update) {
         try {
-            FileObject file = fromUri(uri);
+            FileObject file = Utils.fromUri(uri);
             EditorCookie ec = file.getLookup().lookup(EditorCookie.class);
             Document doc = ec.openDocument();
             ParserManager.parse(Collections.singletonList(Source.create(doc)), new UserTask() {
@@ -1103,8 +1640,8 @@
                         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())),
+                        Diagnostic diag = new Diagnostic(new Range(Utils.createPosition(cc.getCompilationUnit(), err.getRange().getBegin().getOffset()),
+                                                                   Utils.createPosition(cc.getCompilationUnit(), err.getRange().getEnd().getOffset())),
                                                          err.getDescription());
                         switch (err.getSeverity()) {
                             case ERROR: diag.setSeverity(DiagnosticSeverity.Error); break;
@@ -1113,7 +1650,7 @@
                             case HINT: diag.setSeverity(DiagnosticSeverity.Hint); break;
                             default: diag.setSeverity(DiagnosticSeverity.Information); break;
                         }
-                        String id = keyPrefix + ":" + idx + "-" + err.getId();
+                        String id = keyPrefix + ":" + idx++ + "-" + err.getId();
                         diag.setCode(id);
                         id2Errors.put(id, err);
                         diags.add(diag);
@@ -1152,7 +1689,7 @@
         Document doc = openedDocuments.get(fileUri);
         if (doc == null) {
             try {
-                FileObject file = fromUri(fileUri);
+                FileObject file = Utils.fromUri(fileUri);
                 return JavaSource.forFileObject(file);
             } catch (MalformedURLException ex) {
                 return null;
@@ -1162,115 +1699,7 @@
         }
     }
 
-    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:
-            //another workaround, should be:
-            //File cacheDir = Places.getCacheSubfile("java-server");
-            //but that locks up VS Code, using a temp directory:
-            File cacheDir;
-            try {
-                cacheDir = Files.createTempDirectory("nbcode").toFile();
-            } catch (IOException ex) {
-                throw new UncheckedIOException(ex);
-            }
-            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());
-    }
-
-    private static List<TextEdit> modify2TextEdits(JavaSource js, Task<WorkingCopy> task) throws IOException {
+    public static List<TextEdit> modify2TextEdits(JavaSource js, Task<WorkingCopy> task) throws IOException {
         FileObject[] file = new FileObject[1];
         LineMap[] lm = new LineMap[1];
         ModificationResult changes = js.runModificationTask(wc -> {
@@ -1278,27 +1707,34 @@
             file[0] = wc.getFileObject();
             lm[0] = wc.getCompilationUnit().getLineMap();
         });
+        return fileModifications(changes, file[0], lm[0]);
+    }
+    
+    private static List<TextEdit> fileModifications(ModificationResult changes, FileObject file, LineMap lm) {
         //TODO: full, correct and safe edit production:
-        List<? extends ModificationResult.Difference> diffs = changes.getDifferences(file[0]);
+        List<? extends ModificationResult.Difference> diffs = changes.getDifferences(file);
         if (diffs == null) {
             return Collections.emptyList();
         }
         List<TextEdit> edits = new ArrayList<>();
+        IntFunction<Position> offset2Position = lm != null ? pos -> Utils.createPosition(lm, pos)
+                                                           : pos -> Utils.createPosition(file, pos);
+                                            
         for (ModificationResult.Difference diff : diffs) {
             String newText = diff.getNewText();
-            edits.add(new TextEdit(new Range(createPosition(lm[0], diff.getStartPosition().getOffset()),
-                                             createPosition(lm[0], diff.getEndPosition().getOffset())),
+            edits.add(new TextEdit(new Range(offset2Position.apply(diff.getStartPosition().getOffset()),
+                                             offset2Position.apply(diff.getEndPosition().getOffset())),
                                    newText != null ? newText : ""));
         }
         return edits;
     }
-    
+
     private static void reportNotificationDone(String s, Object parameter) {
         if (HOOK_NOTIFICATION != null) {
             HOOK_NOTIFICATION.accept(s, parameter);
         }
     }
-    
+
     /**
      * For testing only; calls that do not return a result should call
      * this hook, if defined, with the method name and parameter.
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ToStringGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ToStringGenerator.java
new file mode 100644
index 0000000..846bd3f
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ToStringGenerator.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.GeneratorUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 50)
+public final class ToStringGenerator extends CodeGenerator {
+
+    public static final String GENERATE_TO_STRING =  "java.generate.toString";
+
+    private final Set<String> commands = Collections.singleton(GENERATE_TO_STRING);
+    private final Gson gson = new Gson();
+
+    public ToStringGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateToString=Generate toString()...",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        if (only == null || !only.contains(CodeActionKind.Source)) {
+            return Collections.emptyList();
+        }
+        int offset = getOffset(info, params.getRange().getStart());
+        TreePath tp = info.getTreeUtilities().pathFor(offset);
+        tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+        if (tp == null) {
+            return Collections.emptyList();
+        }
+        TypeElement type = (TypeElement) info.getTrees().getElement(tp);
+        if (type == null || !type.getKind().isClass()) {
+            return Collections.emptyList();
+        }
+        List<QuickPickItem> fields = new ArrayList<>();
+        for (Element element : type.getEnclosedElements()) {
+            switch (element.getKind()) {
+                case METHOD:
+                    if (element.getSimpleName().contentEquals("toString") && ((ExecutableElement) element).getParameters().isEmpty()) { //NOI18N
+                        return Collections.emptyList();
+                    }
+                    break;
+                case FIELD:
+                    if (!ERROR.contentEquals(element.getSimpleName()) && !element.getModifiers().contains(Modifier.STATIC)) {
+                        QuickPickItem item = new QuickPickItem(createLabel(info, (VariableElement)element));
+                        item.setUserData(new ElementData(element));
+                        fields.add(item);
+                    }
+                    break;
+            }
+        }
+        String uri = Utils.toUri(info.getFileObject());
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateToString(), CODE_GENERATOR_KIND, GENERATE_TO_STRING, uri, offset, fields));
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectToString=Select fields to be included in toString()",
+    })
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        if (arguments.size() > 2) {
+            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+            if (fields.isEmpty()) {
+                generate(client, uri, offset, fields);
+            } else {
+                client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectToString(), true, fields)).thenAccept(selected -> {
+                    if (selected != null) {
+                        generate(client, uri, offset, selected);
+                    }
+                });
+            }
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+
+    private void generate(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> fields) {
+        try {
+            FileObject file = Utils.fromUri(uri);
+            JavaSource js = JavaSource.forFileObject(file);
+            if (js == null) {
+                throw new IOException("Cannot get JavaSource for: " + uri);
+            }
+            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                wc.toPhase(JavaSource.Phase.RESOLVED);
+                TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+                if (tp != null) {
+                    ClassTree cls = (ClassTree) tp.getLeaf();
+                    List<VariableElement> selectedFields = fields.stream().map(item -> {
+                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                        return (VariableElement)data.resolve(wc);
+                    }).collect(Collectors.toList());
+                    MethodTree method = org.netbeans.modules.java.editor.codegen.ToStringGenerator.createToStringMethod(wc, selectedFields, cls.getSimpleName().toString(), true);
+                    wc.rewrite(cls, GeneratorUtilities.get(wc).insertClassMember(cls, method));
+                }
+            });
+            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+        } catch (IOException | IllegalArgumentException ex) {
+            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+        }
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
index 52c395b..e542b39 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
@@ -18,21 +18,53 @@
  */
 package org.netbeans.modules.java.lsp.server.protocol;
 
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
 import org.eclipse.lsp4j.DidChangeConfigurationParams;
 import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
 import org.eclipse.lsp4j.ExecuteCommandParams;
+import org.eclipse.lsp4j.Location;
 import org.eclipse.lsp4j.SymbolInformation;
 import org.eclipse.lsp4j.WorkspaceSymbolParams;
 import org.eclipse.lsp4j.services.LanguageClient;
 import org.eclipse.lsp4j.services.LanguageClientAware;
 import org.eclipse.lsp4j.services.WorkspaceService;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.annotations.common.NullAllowed;
 import org.netbeans.api.debugger.ActionsManager;
 import org.netbeans.api.debugger.DebuggerManager;
+import org.netbeans.api.java.source.ClasspathInfo;
+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.JavaSource.Phase;
+import org.netbeans.api.java.source.SourceUtils;
 import org.netbeans.api.project.Project;
 import org.netbeans.api.project.ui.OpenProjects;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.modules.java.source.ui.JavaSymbolProvider;
+import org.netbeans.modules.java.source.ui.JavaSymbolProvider.ResultHandler;
+import org.netbeans.modules.java.source.ui.JavaSymbolProvider.ResultHandler.Exec;
+import org.netbeans.modules.java.source.usages.ClassIndexImpl;
+import org.netbeans.modules.parsing.lucene.support.Queries;
+import org.netbeans.spi.jumpto.type.SearchType;
+import org.netbeans.spi.project.ActionProgress;
 import org.netbeans.spi.project.ActionProvider;
+import org.openide.filesystems.FileObject;
+import org.openide.util.Lookup;
+import org.openide.util.Pair;
+import org.openide.util.RequestProcessor;
 import org.openide.util.lookup.Lookups;
 
 /**
@@ -41,6 +73,8 @@
  */
 public final class WorkspaceServiceImpl implements WorkspaceService, LanguageClientAware {
 
+    private static final RequestProcessor WORKER = new RequestProcessor(WorkspaceServiceImpl.class.getName(), 1, false, false);
+
     private NbCodeLanguageClient client;
 
     public WorkspaceServiceImpl() {
@@ -48,27 +82,232 @@
 
     @Override
     public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
-        switch (params.getCommand()) {
+        String command = params.getCommand();
+        switch (command) {
             case Server.GRAALVM_PAUSE_SCRIPT:
                 ActionsManager am = DebuggerManager.getDebuggerManager().getCurrentEngine().getActionsManager();
                 am.doAction("pauseInGraalScript");
                 return CompletableFuture.completedFuture(true);
-            case Server.JAVA_BUILD_WORKSPACE:
-                for (Project prj : OpenProjects.getDefault().getOpenProjects()) {
-                    ActionProvider ap = prj.getLookup().lookup(ActionProvider.class);
-                    if (ap != null && ap.isActionEnabled(ActionProvider.COMMAND_BUILD, Lookups.fixed())) {
-                        ap.invokeAction(ActionProvider.COMMAND_REBUILD, Lookups.fixed());
+            case Server.JAVA_BUILD_WORKSPACE: {
+                CompletableFuture<Object> compileFinished = new CompletableFuture<>();
+                class CompileAllProjects extends ActionProgress {
+                    private int running;
+                    private int success;
+                    private int failure;
+
+                    @Override
+                    protected synchronized void started() {
+                        running++;
+                    }
+
+                    @Override
+                    public synchronized void finished(boolean ok) {
+                        if (ok) {
+                            success++;
+                        } else {
+                            failure++;
+                        }
+                        checkStatus();
+                    }
+
+                    synchronized final void checkStatus() {
+                        if (running <= success + failure) {
+                            compileFinished.complete(failure == 0);
+                        }
                     }
                 }
-                return CompletableFuture.completedFuture(true);
+                final CompileAllProjects progressOfCompilation = new CompileAllProjects();
+                final Lookup ctx = Lookups.singleton(progressOfCompilation);
+                for (Project prj : OpenProjects.getDefault().getOpenProjects()) {
+                    ActionProvider ap = prj.getLookup().lookup(ActionProvider.class);
+                    if (ap != null && ap.isActionEnabled(ActionProvider.COMMAND_BUILD, Lookup.EMPTY)) {
+                        ap.invokeAction(ActionProvider.COMMAND_REBUILD, ctx);
+                    }
+                }
+                progressOfCompilation.checkStatus();
+                return compileFinished;
+            }
             default:
-                throw new UnsupportedOperationException("Command not supported: " + params.getCommand());
+                for (CodeGenerator codeGenerator : Lookup.getDefault().lookupAll(CodeGenerator.class)) {
+                    if (codeGenerator.getCommands().contains(command)) {
+                        return codeGenerator.processCommand(client, command, params.getArguments());
+                    }
+                }
         }
+        throw new UnsupportedOperationException("Command not supported: " + params.getCommand());
     }
 
     @Override
-    public CompletableFuture<List<? extends SymbolInformation>> symbol(WorkspaceSymbolParams arg0) {
-        throw new UnsupportedOperationException("Not supported yet.");
+    public CompletableFuture<List<? extends SymbolInformation>> symbol(WorkspaceSymbolParams params) {
+        String query = params.getQuery();
+        if (query.isEmpty()) {
+            //cannot query "all":
+            return CompletableFuture.completedFuture(Collections.emptyList());
+        }
+        System.err.println("query=" + query);
+        boolean exact = false;
+        if (query.endsWith(" ")) {
+            query = query.substring(0, query.length() - 1);
+            exact = true;
+        }
+        String queryFin = query;
+        boolean exactFin = exact;
+        AtomicBoolean cancel = new AtomicBoolean();
+        CompletableFuture<List<? extends SymbolInformation>> result = new CompletableFuture<List<? extends SymbolInformation>>() {
+            @Override
+            public boolean cancel(boolean mayInterruptIfRunning) {
+                cancel.set(mayInterruptIfRunning);
+                return super.cancel(mayInterruptIfRunning);
+            }
+        };
+        WORKER.post(() -> {
+            try {
+                List<SymbolInformation> symbols = new ArrayList<>();
+                ResultHandler handler = new ResultHandler() {
+                    @Override
+                    public void setHighlightText(String text) {
+                    }
+
+                    private Map<ElementHandle<TypeElement>, List<String>> type2Idents;
+
+                    @Override
+                    public void runRoot(FileObject root, ClassIndexImpl ci, Exec exec) throws IOException, InterruptedException {
+                        ClasspathInfo cpInfo = ClasspathInfo.create(root);
+                        try {
+                            type2Idents = new HashMap<>();
+                            exec.run();
+                            Map<FileObject, Map<ElementHandle<TypeElement>, List<String>>> sources = new HashMap<>();
+                            for (Entry<ElementHandle<TypeElement>, List<String>> e : type2Idents.entrySet()) {
+                                FileObject sourceFile = SourceUtils.getFile(e.getKey(), cpInfo);
+                                sources.computeIfAbsent(sourceFile, s -> new HashMap<>())
+                                       .put(e.getKey(), e.getValue());
+                            }
+                            if (!sources.isEmpty()) {
+                                JavaSource.create(cpInfo, sources.keySet())
+                                          .runUserActionTask(cc -> {
+                                              if (Phase.ELEMENTS_RESOLVED.compareTo(cc.toPhase(Phase.ELEMENTS_RESOLVED))> 0) {
+                                                  return ;
+                                              }
+                                              for (Entry<ElementHandle<TypeElement>, List<String>> e : sources.get(cc.getFileObject()).entrySet()) {
+                                                  TypeElement te = e.getKey().resolve(cc);
+
+                                                  if (te == null) {
+                                                      //cannot resolve
+                                                      continue;
+                                                  }
+
+                                                  for (String ident : e.getValue()) {
+                                                      if (ident.equals(getSimpleName(te, null, false))) {
+                                                          TreePath path = cc.getTrees().getPath(te);
+
+                                                          if (path != null) {
+                                                              final String symbolName = te.getSimpleName().toString();
+                                                              final ElementKind kind = te.getKind();
+                                                              SymbolInformation symbol = new SymbolInformation(symbolName, Utils.elementKind2SymbolKind(kind), tree2Location(cc, path), te.getQualifiedName().toString());
+
+                                                              symbol.setDeprecated(false);
+                                                              symbols.add(symbol);
+                                                          }
+                                                      }
+                                                      for (Element ne : te.getEnclosedElements()) {
+                                                          if (ident.equals(getSimpleName(ne, te, false))) {
+                                                              TreePath path = cc.getTrees().getPath(ne);
+
+                                                              if (path != null) {
+                                                                  final Pair<String,String> name = JavaSymbolProvider.getDisplayName(ne, te);
+                                                                  final String symbolName = name.first() + (name.second() != null ? name.second() : "");
+                                                                  final ElementKind kind = ne.getKind();
+                                                                  SymbolInformation symbol = new SymbolInformation(symbolName, Utils.elementKind2SymbolKind(kind), tree2Location(cc, path), te.getQualifiedName().toString());
+
+                                                                  symbol.setDeprecated(false);
+                                                                  symbols.add(symbol);
+                                                              }
+                                                          }
+                                                      }
+                                                  }
+                                              }
+                                          }, true);
+                            }
+                            //TODO: handle exceptions
+                        } finally {
+                            type2Idents = null;
+                        }
+                    }
+
+                    @Override
+                    public void handleResult(ElementHandle<TypeElement> owner, String ident, boolean caseSensitive) {
+                        type2Idents.computeIfAbsent(owner, s -> new ArrayList<>()).add(ident);
+                    }
+                };
+                JavaSymbolProvider.doComputeSymbols(getSearchType(queryFin, exactFin, false, null, null), queryFin, handler, true, cancel);
+                Collections.sort(symbols, (i1, i2) -> i1.getName().compareToIgnoreCase(i2.getName()));
+                result.complete(symbols);
+            } catch (Throwable t) {
+                result.completeExceptionally(t);
+            }
+        });
+        return result;
+    }
+
+    private Location tree2Location(CompilationInfo info, TreePath path) {
+        return new Location(Utils.toUri(info.getFileObject()),
+                            Utils.treeRange(info, path.getLeaf()));
+    }
+
+    //from jumpto.Utils:
+    public static int containsWildCard( String text ) {
+        for( int i = 0; i < text.length(); i++ ) {
+            if ( text.charAt( i ) == '?' || text.charAt( i ) == '*' ) { // NOI18N
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public static boolean isAllUpper( String text ) {
+        for( int i = 0; i < text.length(); i++ ) {
+            if ( !Character.isUpperCase( text.charAt( i ) ) ) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static SearchType getSearchType(
+            @NonNull final String text,
+            final boolean exact,
+            final boolean isCaseSensitive,
+            @NullAllowed final String camelCaseSeparator,
+            @NullAllowed final String camelCasePart) {
+        int wildcard = containsWildCard(text);
+        if (exact) {
+            //nameKind = isCaseSensitive ? SearchType.EXACT_NAME : SearchType.CASE_INSENSITIVE_EXACT_NAME;
+            return SearchType.EXACT_NAME;
+        } else if (wildcard != -1) {
+            return isCaseSensitive ? SearchType.REGEXP : SearchType.CASE_INSENSITIVE_REGEXP;
+        } else if ((isAllUpper(text) && text.length() > 1) || Queries.isCamelCase(text, camelCaseSeparator, camelCasePart)) {
+            return isCaseSensitive ? SearchType.CAMEL_CASE : SearchType.CASE_INSENSITIVE_CAMEL_CASE;
+        } else {
+            return isCaseSensitive ? SearchType.PREFIX : SearchType.CASE_INSENSITIVE_PREFIX;
+        }
+    }
+
+    //TODO: from AsyncJavaSymbolDescriptor:
+    private static final String INIT = "<init>"; //NOI18N
+    @NonNull
+    private static String getSimpleName (
+            @NonNull final Element element,
+            @NullAllowed final Element enclosingElement,
+            final boolean caseSensitive) {
+        String result = element.getSimpleName().toString();
+        if (enclosingElement != null && INIT.equals(result)) {
+            result = enclosingElement.getSimpleName().toString();
+        }
+        if (!caseSensitive) {
+            result = result.toLowerCase();
+        }
+        return result;
     }
 
     @Override
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractDialogDisplayer.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractDialogDisplayer.java
index 35e05a0..a35ba88 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractDialogDisplayer.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractDialogDisplayer.java
@@ -46,6 +46,22 @@
     @Override
     public Object notify(NotifyDescriptor descriptor) {
         LspServerUtils.avoidClientMessageThread(context);
+
+        // XXX: Subject to better fix
+        // XXX: Possibly use 3.16 protocol's willRename/didRename
+        // XXX: when available
+        StackTraceElement[] stack = new Exception().getStackTrace();
+        if (stack.length > 2 && stack[1] != null) {
+            if (
+                "org.openide.text.DataEditorSupport".equals(stack[1].getClassName()) &&
+                "canClose".equals(stack[1].getMethodName())
+            ) {
+                // when VSCode renames modified file, don't try to save the
+                // original
+                return NotifyDescriptor.CLOSED_OPTION;
+            }
+        }
+
         UIContext ctx = UIContext.find(context);
         NotifyDescriptorAdapter adapter = new NotifyDescriptorAdapter(descriptor, ctx);
         return adapter.clientNotify();
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractProgressEnvironment.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractProgressEnvironment.java
new file mode 100644
index 0000000..13923ab
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/ui/AbstractProgressEnvironment.java
@@ -0,0 +1,135 @@
+/*
+ * 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.ui;
+
+import java.util.regex.Pattern;
+import org.netbeans.api.progress.ProgressHandle;
+import org.netbeans.modules.java.lsp.server.progress.LspInternalHandle;
+import org.netbeans.modules.java.lsp.server.progress.LspProgressUIWorker;
+import org.netbeans.modules.java.lsp.server.progress.OperationContext;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.modules.progress.spi.Controller;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.ProgressEnvironment;
+import org.openide.util.Cancellable;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.Lookups;
+
+/**
+ *
+ * @author sdedic
+ */
+public abstract class AbstractProgressEnvironment implements ProgressEnvironment {
+    /**
+     * Some cancellables should not be accepted: they actually do weird things like
+     * RepositoryUpdater's one: it will do further UI operations when cancelled. Add
+     * impl class patterns here to make such progresses not cancellable in LSP clients.
+     */
+    private static final String[] MASK_CANCELLABLES = {
+        "org.netbeans.modules.parsing.impl.indexing.RepositoryUpdater$"
+    };
+    
+    private static final Controller NO_CONTEXT_CONTROLLER = new Controller(null);
+    private final Lookup env;
+    
+    private Pattern patternMaskingCancellables;
+    
+    protected AbstractProgressEnvironment() {
+        this(Lookups.proxy(() -> Lookup.getDefault()));
+    }
+
+    protected AbstractProgressEnvironment(Lookup env) {
+        this.env = env;
+    }
+    
+    Pattern maskCancellablesPattern() {
+        if (patternMaskingCancellables == null) {
+            StringBuilder re = new StringBuilder();
+            for (String pref : MASK_CANCELLABLES) {
+                if (re.length() > 0) {
+                    re.append("|");
+                }
+                re.append(Pattern.quote(pref) + ".*");
+            }
+            patternMaskingCancellables = Pattern.compile(re.toString());
+        }
+        return patternMaskingCancellables;
+    }
+    
+    @Override
+    public ProgressHandle createHandle(String displayname, Cancellable c, boolean userInit) {
+        NbCodeLanguageClient client = env.lookup(NbCodeLanguageClient.class);
+        OperationContext ctx = OperationContext.find(env);
+        if (client == null) {
+            if (ctx == null) {
+                return new NoContextHandle(displayname, c, userInit).createProgressHandle();
+            }
+            client = ctx.getClient();
+        }
+        if (c != null && maskCancellablesPattern().matcher(c.getClass().getName()).matches()) {
+            c = null;
+        }
+        return new LspInternalHandle(
+                ctx, client, this::findController, displayname, c, userInit).createProgressHandle();
+    }
+    
+    public Controller findController(InternalHandle h) {
+        if (!(h instanceof LspInternalHandle)) {
+            return getController();
+        }
+        OperationContext ctx = ((LspInternalHandle)h).getContext();
+        NbCodeLanguageClient client = ctx.getClient();
+        if (client == null) {
+            return getController();
+        }
+        return new Controller(
+            new LspProgressUIWorker()
+        );
+    }
+
+    @Override
+    public Controller getController() {
+        NbCodeLanguageClient client = Lookup.getDefault().lookup(NbCodeLanguageClient.class);
+        if (client == null) {
+            return NO_CONTEXT_CONTROLLER;
+        }
+        return new Controller(
+            new LspProgressUIWorker()
+        );
+    }
+    
+    static class NoContextHandle extends InternalHandle {
+
+        public NoContextHandle(String displayName, Cancellable cancel, boolean userInitiated) {
+            super(displayName, cancel, userInitiated);
+        }
+        
+
+        @Override
+        public synchronized void start(String message, int workunits, long estimate) {
+            try {
+                setController(NO_CONTEXT_CONTROLLER);
+            } catch (IllegalStateException ex) {
+                // controller already set
+            }
+            super.start(message, workunits, estimate);
+        }
+    }
+    
+}
diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
index 38bccfe..bbb84d6 100644
--- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
+++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
@@ -18,7 +18,6 @@
  */
 package org.netbeans.modules.java.lsp.server.protocol;
 
-import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import java.io.File;
 import java.io.FileWriter;
@@ -30,25 +29,33 @@
 import java.net.InetAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
+import java.net.URL;
 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.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import javax.swing.text.Document;
 import javax.swing.text.StyledDocument;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.ApplyWorkspaceEditResponse;
+import org.eclipse.lsp4j.ClientCapabilities;
 import org.eclipse.lsp4j.CodeAction;
 import org.eclipse.lsp4j.CodeActionContext;
+import org.eclipse.lsp4j.CodeActionKind;
 import org.eclipse.lsp4j.CodeActionParams;
 import org.eclipse.lsp4j.Command;
 import org.eclipse.lsp4j.CompletionItem;
@@ -65,10 +72,14 @@
 import org.eclipse.lsp4j.DocumentHighlightParams;
 import org.eclipse.lsp4j.DocumentSymbol;
 import org.eclipse.lsp4j.DocumentSymbolParams;
+import org.eclipse.lsp4j.ExecuteCommandParams;
+import org.eclipse.lsp4j.Hover;
+import org.eclipse.lsp4j.HoverParams;
 import org.eclipse.lsp4j.InitializeParams;
 import org.eclipse.lsp4j.InitializeResult;
 import org.eclipse.lsp4j.InsertTextFormat;
 import org.eclipse.lsp4j.Location;
+import org.eclipse.lsp4j.MarkupContent;
 import org.eclipse.lsp4j.MessageActionItem;
 import org.eclipse.lsp4j.MessageParams;
 import org.eclipse.lsp4j.Position;
@@ -76,14 +87,22 @@
 import org.eclipse.lsp4j.Range;
 import org.eclipse.lsp4j.ReferenceContext;
 import org.eclipse.lsp4j.ReferenceParams;
+import org.eclipse.lsp4j.RenameFile;
+import org.eclipse.lsp4j.RenameParams;
+import org.eclipse.lsp4j.ResourceOperation;
 import org.eclipse.lsp4j.ShowMessageRequestParams;
 import org.eclipse.lsp4j.SymbolInformation;
 import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
+import org.eclipse.lsp4j.TextDocumentEdit;
 import org.eclipse.lsp4j.TextDocumentIdentifier;
 import org.eclipse.lsp4j.TextDocumentItem;
 import org.eclipse.lsp4j.TextEdit;
 import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
+import org.eclipse.lsp4j.WorkspaceClientCapabilities;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.eclipse.lsp4j.WorkspaceEditCapabilities;
 import org.eclipse.lsp4j.WorkspaceFolder;
+import org.eclipse.lsp4j.WorkspaceSymbolParams;
 import org.eclipse.lsp4j.jsonrpc.Launcher;
 import org.eclipse.lsp4j.jsonrpc.messages.Either;
 import org.eclipse.lsp4j.launch.LSPLauncher;
@@ -93,6 +112,7 @@
 import org.netbeans.api.java.classpath.GlobalPathRegistry;
 import org.netbeans.api.java.source.JavaSource;
 import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ui.OpenProjects;
 import org.netbeans.api.sendopts.CommandLine;
 import org.netbeans.junit.NbTestCase;
 import org.netbeans.modules.java.source.BootClassPathUtil;
@@ -106,6 +126,7 @@
 import org.openide.cookies.LineCookie;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
 import org.openide.modules.ModuleInfo;
 import org.openide.modules.Places;
 import org.openide.text.Line;
@@ -150,7 +171,7 @@
                 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);
+                CacheFolderProvider.getCacheFolderForRoot(Utilities.toURI(Places.getUserDirectory()).toURL(), EnumSet.noneOf(CacheFolderProvider.Kind.class), CacheFolderProvider.Mode.EXISTENT);
 
                 Lookup.getDefault().lookup(ModuleInfo.class); //start the module system
 
@@ -168,6 +189,7 @@
         super.tearDown();
         TextDocumentServiceImpl.HOOK_NOTIFICATION = null;
         serverThread.stop();
+        OpenProjects.getDefault().close(OpenProjects.getDefault().getOpenProjects());
     }
     
     List<Diagnostic>[] diags = new List[1];
@@ -214,26 +236,26 @@
         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)));
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "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();
+        Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), 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() : int"), actualItems);
         VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(1);
-        id.setUri(src.toURI().toString());
+        id.setUri(toURI(src));
         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();
+        completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(0, hashCodeStart + 2))).get();
         actualItems = completion.getRight().getItems().stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
         if (jdk9Plus()) {
             assertEquals(Arrays.asList("Method:equals(Object anObject) : boolean", "Method:equalsIgnoreCase(String anotherString) : boolean"), actualItems);
         }
         int testStart = code.indexOf("test") + "equ".length() - "hashCode".length();
-        completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, testStart + 3))).get();
+        completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(0, testStart + 3))).get();
         List<CompletionItem> actualCompletionItem = completion.getRight().getItems();
         actualItems = actualCompletionItem.stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList());
         assertEquals(Arrays.asList("Method:test() : void"), actualItems);
@@ -248,7 +270,7 @@
                      "Test.\n" +
                      "\n",
                      resolvedItem.getDocumentation().getRight().getValue());
-        completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, 0))).get();
+        completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), 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"))));
@@ -258,7 +280,7 @@
         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.size() >= 2);
         assertTrue(log, codeActions.get(0).isRight());
         CodeAction action = codeActions.get(0).getRight();
         assertEquals("Cast ...o to String", action.getTitle());
@@ -503,14 +525,14 @@
         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)));
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
         assertDiags(diags); //errors
         List<Diagnostic> diagnostics = assertDiags(diags, "Warning:1:7-1:19");//hints
         VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(1);
-        id.setUri(src.toURI().toString());
+        id.setUri(toURI(src));
         List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(1, 7), new Position(1, 19)), new CodeActionContext(Arrays.asList(diagnostics.get(0))))).get();
         String log = codeActions.toString();
-        assertEquals(log, 1, codeActions.size());
+        assertTrue(log, codeActions.size() >= 1);
         assertTrue(log, codeActions.get(0).isRight());
         CodeAction action = codeActions.get(0).getRight();
         assertEquals("Remove .toString()", action.getTitle());
@@ -597,8 +619,8 @@
         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();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
+        List<Either<SymbolInformation, DocumentSymbol>> symbols = server.getTextDocumentService().documentSymbol(new DocumentSymbolParams(new TextDocumentIdentifier(toURI(src)))).get();
         String textualSymbols = "";
         String sep = "";
         for (Either<SymbolInformation, DocumentSymbol> sym : symbols) {
@@ -625,7 +647,16 @@
                           "    line = 7\n" +
                           "    character = 5\n" +
                           "  ]\n" +
-                          "]:(Method:innerMethod:Range [\n" +
+                          "]:(Constructor:Inner:Range [\n" +
+                          "  start = Position [\n" +
+                          "    line = 4\n" +
+                          "    character = 4\n" +
+                          "  ]\n" +
+                          "  end = Position [\n" +
+                          "    line = 4\n" +
+                          "    character = 4\n" +
+                          "  ]\n" +
+                          "]:(), Method:innerMethod:Range [\n" +
                           "  start = Position [\n" +
                           "    line = 5\n" +
                           "    character = 8\n" +
@@ -634,7 +665,16 @@
                           "    line = 6\n" +
                           "    character = 9\n" +
                           "  ]\n" +
-                          "]:()), Field:field:Range [\n" +
+                          "]:()), Constructor:Test:Range [\n" +
+                          "  start = Position [\n" +
+                          "    line = 0\n" +
+                          "    character = 7\n" +
+                          "  ]\n" +
+                          "  end = Position [\n" +
+                          "    line = 0\n" +
+                          "    character = 7\n" +
+                          "  ]\n" +
+                          "]:(), Field:field:Range [\n" +
                           "  start = Position [\n" +
                           "    line = 1\n" +
                           "    character = 4\n" +
@@ -715,27 +755,27 @@
         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)));
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
         Position pos = new Position(3, 30);
-        List<? extends Location> definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get().getLeft();
+        List<? extends Location> definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(toURI(src)), pos)).get().getLeft();
         assertEquals(1, definition.size());
-        assertEquals(src.toURI().toString(), definition.get(0).getUri());
+        assertEquals(toURI(src), definition.get(0).getUri());
         assertEquals(1, definition.get(0).getRange().getStart().getLine());
         assertEquals(4, definition.get(0).getRange().getStart().getCharacter());
         assertEquals(1, definition.get(0).getRange().getEnd().getLine());
         assertEquals(22, definition.get(0).getRange().getEnd().getCharacter());
         pos = new Position(4, 30);
-        definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get().getLeft();
+        definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(toURI(src)), pos)).get().getLeft();
         assertEquals(1, definition.size());
-        assertEquals(src.toURI().toString(), definition.get(0).getUri());
+        assertEquals(toURI(src), definition.get(0).getUri());
         assertEquals(2, definition.get(0).getRange().getStart().getLine());
         assertEquals(23, definition.get(0).getRange().getStart().getCharacter());
         assertEquals(2, definition.get(0).getRange().getEnd().getLine());
         assertEquals(30, definition.get(0).getRange().getEnd().getCharacter());
         pos = new Position(5, 22);
-        definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get().getLeft();
+        definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(toURI(src)), pos)).get().getLeft();
         assertEquals(1, definition.size());
-        assertEquals(otherSrc.toURI().toString(), definition.get(0).getUri());
+        assertEquals(toURI(otherSrc), definition.get(0).getUri());
         assertEquals(2, definition.get(0).getRange().getStart().getLine());
         assertEquals(4, definition.get(0).getRange().getStart().getCharacter());
         assertEquals(2, definition.get(0).getRange().getEnd().getLine());
@@ -856,13 +896,73 @@
         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 DocumentHighlightParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(1, 13))).get(),
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
+        assertHighlights(server.getTextDocumentService().documentHighlight(new DocumentHighlightParams(new TextDocumentIdentifier(toURI(src)), 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 DocumentHighlightParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(1, 27))).get(),
+        assertHighlights(server.getTextDocumentService().documentHighlight(new DocumentHighlightParams(new TextDocumentIdentifier(toURI(src)), new Position(1, 27))).get(),
                          "<none>:1:26-1:29", "<none>:2:12-2:15", "<none>:3:17-3:20");
     }
 
+    public void testHover() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "/**\n" +
+                      " * This is a test class with Javadoc.\n" +
+                      " */\n" +
+                      "public class Test {\n" +
+                      "    public static void main(String[] args) {\n" +
+                      "        Test t = new Test();\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().getHoverProvider());
+        Hover hover = server.getTextDocumentService().hover(new HoverParams(new TextDocumentIdentifier(toURI(src)), new Position(5, 10))).get();
+        assertNotNull(hover);
+        assertTrue(hover.getContents().isRight());
+        MarkupContent content = hover.getContents().getRight();
+        assertNotNull(content);
+        assertEquals(content.getKind(), "markdown");
+        assertEquals(content.getValue(), "**[](*0)**\n" +
+                "\n" +
+                "```\n" +
+                "public class Test\n" +
+                "extends Object\n" +
+                "```\n" +
+                "\n" +
+                "This is a test class with Javadoc.\n" +
+                "\n");
+    }
+
     public void testAdvancedCompletion1() throws Exception {
         File src = new File(getWorkDir(), "Test.java");
         src.getParentFile().mkdirs();
@@ -913,16 +1013,16 @@
         serverLauncher.startListening();
         LanguageServer server = serverLauncher.getRemoteProxy();
         InitializeParams initParams = new InitializeParams();
-        initParams.setRootUri(getWorkDir().toURI().toString());
+        initParams.setRootUri(toURI(getWorkDir()));
         InitializeResult result = server.initialize(initParams).get();
         indexingComplete.await();
-        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
 
         {
-            VersionedTextDocumentIdentifier id1 = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+            VersionedTextDocumentIdentifier id1 = new VersionedTextDocumentIdentifier(toURI(src), 1);
             server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id1, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(2, 8), new Position(2, 8)), 0, "s."))));
 
-            Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(2, 8 + "s.".length()))).get();
+            Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(2, 8 + "s.".length()))).get();
             assertTrue(completion.isRight());
             Optional<CompletionItem> lengthItem = completion.getRight().getItems().stream().filter(ci -> "length() : int".equals(ci.getLabel())).findAny();
             assertTrue(lengthItem.isPresent());
@@ -935,13 +1035,13 @@
         }
 
         {
-            VersionedTextDocumentIdentifier id2 = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+            VersionedTextDocumentIdentifier id2 = new VersionedTextDocumentIdentifier(toURI(src), 1);
             server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(1, 1), new Position(1, 1)), 0, "@java.lang."))));
 
             Position afterJavaLang = new Position(1, 1 + "@java.lang.".length());
 
             {
-                Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterJavaLang)).get();
+                Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), afterJavaLang)).get();
                 assertTrue(completion.isRight());
                 Optional<CompletionItem> annotationItem = completion.getRight().getItems().stream().filter(ci -> "annotation".equals(ci.getLabel())).findAny();
                 assertTrue(annotationItem.isPresent());
@@ -954,7 +1054,7 @@
             Position afterJavaLangAnnotation = new Position(1, afterJavaLang.getCharacter() + "annotation.".length());
 
             {
-                Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterJavaLangAnnotation)).get();
+                Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), afterJavaLangAnnotation)).get();
                 assertTrue(completion.isRight());
                 completion.getRight().getItems().stream().forEach(ci -> System.err.println(ci.getLabel()));
                 Optional<CompletionItem> targetItem = completion.getRight().getItems().stream().filter(ci -> "Target".equals(ci.getLabel())).findAny();
@@ -968,7 +1068,7 @@
             Position afterTarget = new Position(1, afterJavaLangAnnotation.getCharacter() + "Target(".length());
 
             {
-                Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterTarget)).get();
+                Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), afterTarget)).get();
                 assertTrue(completion.isRight());
                 completion.getRight().getItems().stream().forEach(ci -> System.err.println(ci.getLabel()));
                 Optional<CompletionItem> methodItem = completion.getRight().getItems().stream().filter(ci -> "ElementType.METHOD".equals(ci.getLabel())).findAny();
@@ -987,7 +1087,7 @@
 
             {
                 //import already exists:
-                Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterTarget)).get();
+                Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), afterTarget)).get();
                 assertTrue(completion.isRight());
                 completion.getRight().getItems().stream().forEach(ci -> System.err.println(ci.getLabel()));
                 Optional<CompletionItem> methodItem = completion.getRight().getItems().stream().filter(ci -> "ElementType.METHOD".equals(ci.getLabel())).findAny();
@@ -999,6 +1099,94 @@
         }
     }
 
+    public void testAutoImportOnCompletion() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        try (Writer w = new FileWriter(new File(src.getParentFile(), ".test-project"))) {}
+        String code = "public class Test {\n" +
+                      "    private void t(String s) {\n" +
+                      "        \n" +
+                      "    }\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        List<Diagnostic>[] diags = new List[1];
+        CountDownLatch indexingComplete = new CountDownLatch(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())) {
+                    indexingComplete.countDown();
+                } 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.setRootUri(toURI(getWorkDir()));
+        server.initialize(initParams).get();
+        indexingComplete.await();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
+
+        {
+            VersionedTextDocumentIdentifier id1 = new VersionedTextDocumentIdentifier(toURI(src), 1);
+            server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id1, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(2, 8), new Position(2, 8)), 0, "ArrayL"))));
+
+            Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(2, 8 + "ArrayL".length()))).get();
+            assertTrue(completion.isRight());
+            Optional<CompletionItem> arrayListItem = completion.getRight().getItems().stream().filter(ci -> "ArrayList".equals(ci.getLabel())).findAny();
+            assertTrue(arrayListItem.isPresent());
+            assertNull(arrayListItem.get().getAdditionalTextEdits());
+            CompletableFuture<CompletionItem> resolvedItem = server.getTextDocumentService().resolveCompletionItem(arrayListItem.get());
+            assertEquals(1, resolvedItem.get().getAdditionalTextEdits().size());
+            assertEquals(0, resolvedItem.get().getAdditionalTextEdits().get(0).getRange().getStart().getLine());
+            assertEquals(0, resolvedItem.get().getAdditionalTextEdits().get(0).getRange().getStart().getCharacter());
+            assertEquals(0, resolvedItem.get().getAdditionalTextEdits().get(0).getRange().getEnd().getLine());
+            assertEquals(0, resolvedItem.get().getAdditionalTextEdits().get(0).getRange().getEnd().getCharacter());
+            assertEquals("\nimport java.util.ArrayList;\n\n", resolvedItem.get().getAdditionalTextEdits().get(0).getNewText());
+        }
+
+        {
+            VersionedTextDocumentIdentifier id2 = new VersionedTextDocumentIdentifier(toURI(src), 1);
+            server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, 0), new Position(0, 0)), 0, "import java.util.ArrayList;\n"))));
+            server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(3, 8), new Position(3, 8)), 0, "ArrayL"))));
+
+            Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(3, 8 + "ArrayL".length()))).get();
+            assertTrue(completion.isRight());
+            Optional<CompletionItem> arrayListItem = completion.getRight().getItems().stream().filter(ci -> "ArrayList".equals(ci.getLabel())).findAny();
+            assertTrue(arrayListItem.isPresent());
+            assertNull(arrayListItem.get().getAdditionalTextEdits());
+            CompletableFuture<CompletionItem> resolvedItem = server.getTextDocumentService().resolveCompletionItem(arrayListItem.get());
+            assertNull(resolvedItem.get().getAdditionalTextEdits());
+        }
+    }
+
     public void testFixImports() throws Exception {
         File src = new File(getWorkDir(), "Test.java");
         src.getParentFile().mkdirs();
@@ -1022,10 +1210,20 @@
             }
 
             @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
             public NbCodeClientCapabilities getNbCodeCapabilities() {
                 throw new UnsupportedOperationException("Not supported yet.");
             }
-            
+
             @Override
             public void telemetryEvent(Object arg0) {
                 throw new UnsupportedOperationException("Not supported yet.");
@@ -1059,13 +1257,13 @@
         InitializeParams initParams = new InitializeParams();
         initParams.setInitializationOptions(new JsonParser().parse(
                 "{ nbcodeCapabilities: { statusBarMessageSupport : true } }").getAsJsonObject());
-        initParams.setRootUri(getWorkDir().toURI().toString());
+        initParams.setRootUri(toURI(getWorkDir()));
         InitializeResult result = server.initialize(initParams).get();
         indexingComplete.await();
-        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
 
         Diagnostic unresolvable = assertDiags(diags, "Error:2:8-2:12").get(0);
-        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(new TextDocumentIdentifier(src.toURI().toString()), unresolvable.getRange(), new CodeActionContext(Arrays.asList(unresolvable)))).get();
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(new TextDocumentIdentifier(toURI(src)), unresolvable.getRange(), new CodeActionContext(Arrays.asList(unresolvable)))).get();
         if (jdk9Plus()) {
             assertEquals(2, codeActions.size());
         }
@@ -1130,10 +1328,10 @@
         initParams.setRootUri(getWorkDir().toURI().toString());
         InitializeResult result = server.initialize(initParams).get();
         indexingComplete.await();
-        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
 
         {
-            ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(src.toURI().toString()),
+            ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(toURI(src)),
                                                          new Position(0, 15),
                                                          new ReferenceContext(false));
 
@@ -1144,7 +1342,7 @@
         }
 
         {
-            ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(src.toURI().toString()),
+            ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(toURI(src)),
                                                          new Position(0, 15),
                                                          new ReferenceContext(true));
 
@@ -1188,11 +1386,1841 @@
         }
     }
 
+    public void testWorkspaceSymbols() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        try (Writer w = new FileWriter(new File(src.getParentFile(), ".test-project"))) {}
+        String code = "public class Test {\n" +
+                      "    public static class TestNested {}\n" +
+                      "    public static void testMethod() {}\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        CountDownLatch indexingComplete = new CountDownLatch(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) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void showMessage(MessageParams params) {
+                if (Server.INDEXING_COMPLETED.equals(params.getMessage())) {
+                    indexingComplete.countDown();
+                } 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.setRootUri(toURI(getWorkDir()));
+        InitializeResult result = server.initialize(initParams).get();
+        indexingComplete.await();
+        List<? extends SymbolInformation> symbols = server.getWorkspaceService().symbol(new WorkspaceSymbolParams("Tes")).get();
+        List<String> actual = symbols.stream().map(si -> si.getKind() + ":" + si.getName() + ":" + si.getContainerName() + ":" + si.getDeprecated() + ":" + toString(si.getLocation())).collect(Collectors.toList());
+        assertEquals(Arrays.asList("Class:Test:Test:false:Test.java:0:0-3:1",
+                                   "Constructor:Test():Test:false:Test.java:0:7-0:7",
+                                   "Method:testMethod():Test:false:Test.java:2:4-2:38",
+                                   "Class:TestNested:Test.TestNested:false:Test.java:1:4-1:37",
+                                   "Constructor:TestNested():Test.TestNested:false:Test.java:1:18-1:18"),
+                     actual);
+    }
+
+    public void testCodeActionGenerateVarFieldOrParam() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    public String getName() {\n" +
+                      "        return name;\n" +
+                      "    }\n" +
+                      "}\n";
+        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) {
+            }
+
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        synchronized (diags) {
+            while (diags[0] == null) {
+                try {
+                    diags.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 17), new Position(2, 17)), new CodeActionContext(diags[0]))).get();
+        assertTrue(codeActions.size() >= 3);
+        Optional<CodeAction> generateVariable =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> "Create local variable \"name\"".equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateVariable.isPresent());
+        assertEquals(generateVariable.get().getKind(), CodeActionKind.QuickFix);
+        List<Either<TextDocumentEdit, ResourceOperation>> changes = generateVariable.get().getEdit().getDocumentChanges();
+        assertEquals(1, changes.size());
+        assertTrue(changes.get(0).isLeft());
+        TextDocumentEdit edit = changes.get(0).getLeft();
+        assertEquals(edit.getTextDocument().getUri(), uri);
+        List<TextEdit> fileChanges = edit.getEdits();
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(2, 0),
+                               new Position(2, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("        String name;\n",
+                     fileChanges.get(0).getNewText());
+        Optional<CodeAction> generateField =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> "Create field \"name\" in Test".equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateField.isPresent());
+        assertEquals(generateField.get().getKind(), CodeActionKind.QuickFix);
+        changes = generateField.get().getEdit().getDocumentChanges();
+        assertEquals(1, changes.size());
+        assertTrue(changes.get(0).isLeft());
+        edit = changes.get(0).getLeft();
+        assertEquals(edit.getTextDocument().getUri(), uri);
+        fileChanges = edit.getEdits();
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(1, 0),
+                               new Position(1, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    private String name;\n",
+                     fileChanges.get(0).getNewText());
+        Optional<CodeAction> generateParameter =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> "Create parameter \"name\"".equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateParameter.isPresent());
+        assertEquals(generateParameter.get().getKind(), CodeActionKind.QuickFix);
+        changes = generateParameter.get().getEdit().getDocumentChanges();
+        assertEquals(1, changes.size());
+        assertTrue(changes.get(0).isLeft());
+        edit = changes.get(0).getLeft();
+        assertEquals(edit.getTextDocument().getUri(), uri);
+        fileChanges = edit.getEdits();
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(1, 26),
+                               new Position(1, 26)),
+                     fileChanges.get(0).getRange());
+        assertEquals("String name",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testCodeActionGenerateMethod() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    public String get(int value) {\n" +
+                      "        return convertToString(value);\n" +
+                      "    }\n" +
+                      "}\n";
+        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) {
+            }
+
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        synchronized (diags) {
+            while (diags[0] == null) {
+                try {
+                    diags.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 17), new Position(2, 17)), new CodeActionContext(diags[0]))).get();
+        assertTrue(codeActions.size() >= 1);
+        Optional<CodeAction> generateMehtod =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> "Create method \"convertToString(int)\" in Test".equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateMehtod.isPresent());
+        assertEquals(generateMehtod.get().getKind(), CodeActionKind.QuickFix);
+        List<Either<TextDocumentEdit, ResourceOperation>> changes = generateMehtod.get().getEdit().getDocumentChanges();
+        assertEquals(1, changes.size());
+        assertTrue(changes.get(0).isLeft());
+        TextDocumentEdit edit = changes.get(0).getLeft();
+        assertEquals(edit.getTextDocument().getUri(), uri);
+        List<TextEdit> fileChanges = edit.getEdits();
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(4, 0),
+                               new Position(4, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    private String convertToString(int value) {\n" +
+                     "        throw new UnsupportedOperationException(\"Not supported yet.\"); //To change body of generated methods, choose Tools | Templates.\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testCodeActionGenerateClass() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    public Hello hello;\n" +
+                      "}\n";
+        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) {
+            }
+
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        synchronized (diags) {
+            while (diags[0] == null) {
+                try {
+                    diags.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(1, 14), new Position(2, 14)), new CodeActionContext(diags[0]))).get();
+        assertTrue(codeActions.size() >= 2);
+        Optional<CodeAction> generateClass =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> "Create class \"Hello\" in Test".equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateClass.isPresent());
+        assertEquals(generateClass.get().getKind(), CodeActionKind.QuickFix);
+        List<Either<TextDocumentEdit, ResourceOperation>> changes = generateClass.get().getEdit().getDocumentChanges();
+        assertEquals(1, changes.size());
+        assertTrue(changes.get(0).isLeft());
+        TextDocumentEdit edit = changes.get(0).getLeft();
+        assertEquals(edit.getTextDocument().getUri(), uri);
+        List<TextEdit> fileChanges = edit.getEdits();
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(2, 0),
+                               new Position(2, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    private static class Hello {\n" +
+                     "\n" +
+                     "        public Hello() {\n" +
+                     "        }\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testCodeActionIntroduceVariable() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    public static void main(String[] args) {\n" +
+                      "        System.out.println(\"Hello World\");\n" +
+                      "    }\n" +
+                      "}\n";
+        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) {
+            }
+
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        synchronized (diags) {
+            while (diags[0] == null) {
+                try {
+                    diags.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 8), new Position(2, 18)), new CodeActionContext(diags[0]))).get();
+        assertTrue(codeActions.size() >= 4);
+        Optional<CodeAction> introduceVariable =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> "Introduce Variable...".equals(a.getTitle()))
+                           .findAny();
+        assertTrue(introduceVariable.isPresent());
+        assertEquals(introduceVariable.get().getKind(), CodeActionKind.RefactorExtract);
+        List<Either<TextDocumentEdit, ResourceOperation>> changes = introduceVariable.get().getEdit().getDocumentChanges();
+        assertEquals(1, changes.size());
+        assertTrue(changes.get(0).isLeft());
+        TextDocumentEdit edit = changes.get(0).getLeft();
+        assertEquals(edit.getTextDocument().getUri(), uri);
+        List<TextEdit> fileChanges = edit.getEdits();
+        assertNotNull(fileChanges);
+        assertEquals(4, fileChanges.size());
+        assertEquals(new Range(new Position(0, 0),
+                               new Position(0, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "import java.io.PrintStream;\n" +
+                     "\n",
+                     fileChanges.get(0).getNewText());
+        assertEquals(new Range(new Position(2, 8),
+                               new Position(2, 8)),
+                     fileChanges.get(1).getRange());
+        assertEquals("PrintStream out = ",
+                     fileChanges.get(1).getNewText());
+        assertEquals(new Range(new Position(2, 18),
+                               new Position(2, 41)),
+                     fileChanges.get(2).getRange());
+        assertEquals("",
+                     fileChanges.get(2).getNewText());
+        assertEquals(new Range(new Position(3, 0),
+                               new Position(3, 0)),
+                     fileChanges.get(3).getRange());
+        assertEquals("        out.println(\"Hello World\");\n",
+                     fileChanges.get(3).getNewText());
+        Command command = introduceVariable.get().getCommand();
+        assertNotNull(command);
+        assertEquals("java.rename.element.at", command.getCommand());
+        List<Object> arguments = command.getArguments();
+        assertNotNull(arguments);
+        assertEquals(1, arguments.size());
+        assertEquals("115", arguments.get(0).toString());
+    }
+
+    public void testCodeActionIntroduceConstant() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    public static void main(String[] args) {\n" +
+                      "        System.out.println(\"Hello World\");\n" +
+                      "    }\n" +
+                      "}\n";
+        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) {
+            }
+
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        synchronized (diags) {
+            while (diags[0] == null) {
+                try {
+                    diags.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 8), new Position(2, 18)), new CodeActionContext(diags[0]))).get();
+        assertTrue(codeActions.size() >= 4);
+        Optional<CodeAction> introduceConstant =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> "Introduce Constant...".equals(a.getTitle()))
+                           .findAny();
+        assertTrue(introduceConstant.isPresent());
+        assertEquals(introduceConstant.get().getKind(), CodeActionKind.RefactorExtract);
+        List<Either<TextDocumentEdit, ResourceOperation>> changes = introduceConstant.get().getEdit().getDocumentChanges();
+        assertEquals(1, changes.size());
+        assertTrue(changes.get(0).isLeft());
+        TextDocumentEdit edit = changes.get(0).getLeft();
+        assertEquals(edit.getTextDocument().getUri(), uri);
+        List<TextEdit> fileChanges = edit.getEdits();
+        assertNotNull(fileChanges);
+        assertEquals(3, fileChanges.size());
+        assertEquals(new Range(new Position(0, 0),
+                               new Position(0, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "import java.io.PrintStream;\n" +
+                     "\n",
+                     fileChanges.get(0).getNewText());
+        assertEquals(new Range(new Position(2, 8),
+                               new Position(2, 18)),
+                     fileChanges.get(1).getRange());
+        assertEquals("OUT",
+                     fileChanges.get(1).getNewText());
+        assertEquals(new Range(new Position(4, 0),
+                               new Position(4, 0)),
+                     fileChanges.get(2).getRange());
+        assertEquals("    private static PrintStream OUT = System.out;\n",
+                     fileChanges.get(2).getNewText());
+        Command command = introduceConstant.get().getCommand();
+        assertNotNull(command);
+        assertEquals("java.rename.element.at", command.getCommand());
+        List<Object> arguments = command.getArguments();
+        assertNotNull(arguments);
+        assertEquals(1, arguments.size());
+        assertEquals("168", arguments.get(0).toString());
+    }
+
+    public void testCodeActionIntroduceField() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    public static void main(String[] args) {\n" +
+                      "        System.out.println(\"Hello World\");\n" +
+                      "    }\n" +
+                      "}\n";
+        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) {
+            }
+
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        synchronized (diags) {
+            while (diags[0] == null) {
+                try {
+                    diags.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 8), new Position(2, 18)), new CodeActionContext(diags[0]))).get();
+        assertTrue(codeActions.size() >= 4);
+        Optional<CodeAction> introduceField =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> "Introduce Field...".equals(a.getTitle()))
+                           .findAny();
+        assertTrue(introduceField.isPresent());
+        assertEquals(introduceField.get().getKind(), CodeActionKind.RefactorExtract);
+        List<Either<TextDocumentEdit, ResourceOperation>> changes = introduceField.get().getEdit().getDocumentChanges();
+        assertEquals(1, changes.size());
+        assertTrue(changes.get(0).isLeft());
+        TextDocumentEdit edit = changes.get(0).getLeft();
+        assertEquals(edit.getTextDocument().getUri(), uri);
+        List<TextEdit> fileChanges = edit.getEdits();
+        assertNotNull(fileChanges);
+        assertEquals(3, fileChanges.size());
+        assertEquals(new Range(new Position(0, 0),
+                               new Position(0, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "import java.io.PrintStream;\n" +
+                     "\n",
+                     fileChanges.get(0).getNewText());
+        assertEquals(new Range(new Position(2, 8),
+                               new Position(2, 15)),
+                     fileChanges.get(1).getRange());
+        assertEquals("",
+                     fileChanges.get(1).getNewText());
+        assertEquals(new Range(new Position(4, 0),
+                               new Position(4, 0)),
+                     fileChanges.get(2).getRange());
+        assertEquals("    private static PrintStream out = System.out;\n",
+                     fileChanges.get(2).getNewText());
+        Command command = introduceField.get().getCommand();
+        assertNotNull(command);
+        assertEquals("java.rename.element.at", command.getCommand());
+        List<Object> arguments = command.getArguments();
+        assertNotNull(arguments);
+        assertEquals(1, arguments.size());
+        assertEquals("168", arguments.get(0).toString());
+    }
+
+    public void testCodeActionIntroduceMethod() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    public static void main(String[] args) {\n" +
+                      "        System.out.println(\"Hello World\");\n" +
+                      "    }\n" +
+                      "}\n";
+        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) {
+            }
+
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        synchronized (diags) {
+            while (diags[0] == null) {
+                try {
+                    diags.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 8), new Position(2, 18)), new CodeActionContext(diags[0]))).get();
+        assertTrue(codeActions.size() >= 4);
+        Optional<CodeAction> introduceMethod =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> "Introduce Method...".equals(a.getTitle()))
+                           .findAny();
+        assertTrue(introduceMethod.isPresent());
+        assertEquals(introduceMethod.get().getKind(), CodeActionKind.RefactorExtract);
+        List<Either<TextDocumentEdit, ResourceOperation>> changes = introduceMethod.get().getEdit().getDocumentChanges();
+        assertEquals(1, changes.size());
+        assertTrue(changes.get(0).isLeft());
+        TextDocumentEdit edit = changes.get(0).getLeft();
+        assertEquals(edit.getTextDocument().getUri(), uri);
+        List<TextEdit> fileChanges = edit.getEdits();
+        assertNotNull(fileChanges);
+        assertEquals(3, fileChanges.size());
+        assertEquals(new Range(new Position(0, 0),
+                               new Position(0, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "import java.io.PrintStream;\n" +
+                     "\n",
+                     fileChanges.get(0).getNewText());
+        assertEquals(new Range(new Position(2, 8),
+                               new Position(2, 18)),
+                     fileChanges.get(1).getRange());
+        assertEquals("method()",
+                     fileChanges.get(1).getNewText());
+        assertEquals(new Range(new Position(3, 0),
+                               new Position(3, 0)),
+                     fileChanges.get(2).getRange());
+        assertEquals("    }\n" +
+                     "\n" +
+                     "    private static PrintStream method() {\n" +
+                     "        return System.out;\n",
+                     fileChanges.get(2).getNewText());
+        Command command = introduceMethod.get().getCommand();
+        assertNotNull(command);
+        assertEquals("java.rename.element.at", command.getCommand());
+        List<Object> arguments = command.getArguments();
+        assertNotNull(arguments);
+        assertEquals(1, arguments.size());
+        assertEquals("174", arguments.get(0).toString());
+    }
+
+    public void testCodeActionGetterSetter() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private final String f1;\n" +
+                      "    private String f2;\n" +
+                      "    private final String f3;\n" +
+                      "    private final String f4;\n" +
+                      "    public String getF4() { return f4; }\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        WorkspaceEdit[] edit = new WorkspaceEdit[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) {
+            }
+
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                edit[0] = params.getEdit();
+                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 6), new Position(2, 6)), new CodeActionContext(Arrays.asList()))).get();
+        assertEquals(3, codeActions.size());
+        Optional<CodeAction> generateGetterSetter =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateGetterSetterFor("f2").equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateGetterSetter.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateGetterSetter.get().getCommand().getCommand(), generateGetterSetter.get().getCommand().getArguments())).get();
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(6, 0),
+                               new Position(6, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    public String getF2() {\n" +
+                     "        return f2;\n" +
+                     "    }\n" +
+                     "\n" +
+                     "    public void setF2(String f2) {\n" +
+                     "        this.f2 = f2;\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testSourceActionGetterSetter() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private final String f1;\n" +
+                      "    private String f2;\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        WorkspaceEdit[] edit = new WorkspaceEdit[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) {
+            }
+
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                edit[0] = params.getEdit();
+                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(3, 0), new Position(3, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(9, codeActions.size());
+        Optional<CodeAction> generateGetterSetter =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateGetterSetterFor("f2").equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateGetterSetter.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateGetterSetter.get().getCommand().getCommand(), generateGetterSetter.get().getCommand().getArguments())).get();
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(3, 0),
+                               new Position(3, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    public String getF2() {\n" +
+                     "        return f2;\n" +
+                     "    }\n" +
+                     "\n" +
+                     "    public void setF2(String f2) {\n" +
+                     "        this.f2 = f2;\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+        server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(fileChanges.get(0).getRange(), 0, fileChanges.get(0).getNewText()))));
+        codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(3, 0), new Position(3, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(7, codeActions.size());
+        Optional<CodeAction> generateGetter =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateGetterFor("f1").equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateGetter.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateGetter.get().getCommand().getCommand(), generateGetter.get().getCommand().getArguments())).get();
+        assertEquals(1, edit[0].getChanges().size());
+        fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(11, 0),
+                               new Position(11, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    public String getF1() {\n" +
+                     "        return f1;\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testSourceActionConstructor() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test extends Exception {\n" +
+                      "    private final String f1;\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        WorkspaceEdit[] edit = new WorkspaceEdit[1];
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                edit[0] = params.getEdit();
+                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+            }
+
+            @Override
+            public void showStatusBarMessage(ShowStatusMessageParams params) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public NbCodeClientCapabilities getNbCodeCapabilities() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(7, codeActions.size());
+        Optional<CodeAction> generateConstructor =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateConstructor().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateConstructor.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateConstructor.get().getCommand().getCommand(), generateConstructor.get().getCommand().getArguments())).get();
+        int cnt = 0;
+        while(edit[0] == null && cnt++ < 10) {
+            Thread.sleep(1000);
+        }
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(2, 0),
+                               new Position(2, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    public Test(String f1) {\n" +
+                     "        this.f1 = f1;\n" +
+                     "    }\n" +
+                     "\n" +
+                     "    public Test(String f1, String string) {\n" +
+                     "        super(string);\n" +
+                     "        this.f1 = f1;\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testSourceActionEqualsHashCode() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "import java.util.Objects;\n" +
+                      "\n" +
+                      "public class Test {\n" +
+                      "    private final String f1;\n" +
+                      "    private java.util.List<String> f2;\n" +
+                      "\n" +
+                      "    @Override\n" +
+                      "    public int hashCode() {\n" +
+                      "        int hash = 3;\n" +
+                      "        hash = 71 * hash + Objects.hashCode(this.f1);\n" +
+                      "        hash = 71 * hash + Objects.hashCode(this.f2);\n" +
+                      "        return hash;\n" +
+                      "    }\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        WorkspaceEdit[] edit = new WorkspaceEdit[1];
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                edit[0] = params.getEdit();
+                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+            }
+
+            @Override
+            public void showStatusBarMessage(ShowStatusMessageParams params) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public NbCodeClientCapabilities getNbCodeCapabilities() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(5, 0), new Position(5, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(9, codeActions.size());
+        Optional<CodeAction> generateEquals =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateEquals().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateEquals.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateEquals.get().getCommand().getCommand(), generateEquals.get().getCommand().getArguments())).get();
+        int cnt = 0;
+        while(edit[0] == null && cnt++ < 10) {
+            Thread.sleep(1000);
+        }
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(13, 0),
+                               new Position(13, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    @Override\n" +
+                     "    public boolean equals(Object obj) {\n" +
+                     "        if (this == obj) {\n" +
+                     "            return true;\n" +
+                     "        }\n" +
+                     "        if (obj == null) {\n" +
+                     "            return false;\n" +
+                     "        }\n" +
+                     "        if (getClass() != obj.getClass()) {\n" +
+                     "            return false;\n" +
+                     "        }\n" +
+                     "        final Test other = (Test) obj;\n" +
+                     "        if (!Objects.equals(this.f1, other.f1)) {\n" +
+                     "            return false;\n" +
+                     "        }\n" +
+                     "        if (!Objects.equals(this.f2, other.f2)) {\n" +
+                     "            return false;\n" +
+                     "        }\n" +
+                     "        return true;\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testSourceActionToString() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private final String f1;\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        WorkspaceEdit[] edit = new WorkspaceEdit[1];
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                edit[0] = params.getEdit();
+                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+            }
+
+            @Override
+            public void showStatusBarMessage(ShowStatusMessageParams params) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public NbCodeClientCapabilities getNbCodeCapabilities() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(7, codeActions.size());
+        Optional<CodeAction> generateToString =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateToString().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateToString.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateToString.get().getCommand().getCommand(), generateToString.get().getCommand().getArguments())).get();
+        int cnt = 0;
+        while(edit[0] == null && cnt++ < 10) {
+            Thread.sleep(1000);
+        }
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(2, 0),
+                               new Position(2, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    @Override\n" +
+                     "    public String toString() {\n" +
+                     "        StringBuilder sb = new StringBuilder();\n" +
+                     "        sb.append(\"Test{f1=\").append(f1);\n" +
+                     "        sb.append('}');\n" +
+                     "        return sb.toString();\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testSourceActionDelegateMethod() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private final String f1;\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        WorkspaceEdit[] edit = new WorkspaceEdit[1];
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                edit[0] = params.getEdit();
+                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+            }
+
+            @Override
+            public void showStatusBarMessage(ShowStatusMessageParams params) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public NbCodeClientCapabilities getNbCodeCapabilities() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(7, codeActions.size());
+        Optional<CodeAction> generateDelegateMethod =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateDelegateMethod().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateDelegateMethod.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateDelegateMethod.get().getCommand().getCommand(), generateDelegateMethod.get().getCommand().getArguments())).get();
+        int cnt = 0;
+        while(edit[0] == null && cnt++ < 10) {
+            Thread.sleep(1000);
+        }
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(2, fileChanges.size());
+        assertEquals(new Range(new Position(0, 0),
+                               new Position(0, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\nimport java.util.stream.IntStream;\n\n",
+                     fileChanges.get(0).getNewText());
+        assertEquals(new Range(new Position(2, 0),
+                               new Position(2, 0)),
+                     fileChanges.get(1).getRange());
+        assertEquals("\n" +
+                     "    public IntStream chars() {\n" +
+                     "        return f1.chars();\n" +
+                     "    }\n" +
+                     "\n" +
+                     "    public IntStream codePoints() {\n" +
+                     "        return f1.codePoints();\n" +
+                     "    }\n",
+                     fileChanges.get(1).getNewText());
+    }
+
+    public void testSourceActionOverrideMethod() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private final String f1;\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        WorkspaceEdit[] edit = new WorkspaceEdit[1];
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                edit[0] = params.getEdit();
+                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+            }
+
+            @Override
+            public void showStatusBarMessage(ShowStatusMessageParams params) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public NbCodeClientCapabilities getNbCodeCapabilities() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(7, codeActions.size());
+        Optional<CodeAction> generateOverrideMethod =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateOverrideMethod().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateOverrideMethod.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateOverrideMethod.get().getCommand().getCommand(), generateOverrideMethod.get().getCommand().getArguments())).get();
+        int cnt = 0;
+        while(edit[0] == null && cnt++ < 10) {
+            Thread.sleep(1000);
+        }
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(2, 0),
+                               new Position(2, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    @Override\n" +
+                     "    protected void finalize() throws Throwable {\n" +
+                     "        super.finalize(); //To change body of generated methods, choose Tools | Templates.\n" +
+                     "    }\n" +
+                     "\n" +
+                     "    @Override\n" +
+                     "    public String toString() {\n" +
+                     "        return super.toString(); //To change body of generated methods, choose Tools | Templates.\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testSourceActionLogger() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private final String f1;\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        WorkspaceEdit[] edit = new WorkspaceEdit[1];
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+            @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.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                edit[0] = params.getEdit();
+                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+            }
+
+            @Override
+            public void showStatusBarMessage(ShowStatusMessageParams params) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                return CompletableFuture.completedFuture("LOGGER");
+            }
+
+            @Override
+            public NbCodeClientCapabilities getNbCodeCapabilities() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(7, codeActions.size());
+        Optional<CodeAction> generateLogger =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateLogger().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateLogger.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateLogger.get().getCommand().getCommand(), generateLogger.get().getCommand().getArguments())).get();
+        int cnt = 0;
+        while(edit[0] == null && cnt++ < 10) {
+            Thread.sleep(1000);
+        }
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(2, fileChanges.size());
+        assertEquals(new Range(new Position(0, 0),
+                               new Position(0, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\nimport java.util.logging.Logger;\n\n",
+                     fileChanges.get(0).getNewText());
+        assertEquals(new Range(new Position(1, 0),
+                               new Position(1, 0)),
+                     fileChanges.get(1).getRange());
+        assertEquals("\n" +
+                     "    private static final Logger LOGGER = Logger.getLogger(Test.class.getName());\n",
+                     fileChanges.get(1).getNewText());
+    }
+
+    public void testNoErrorAndHintsFor() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private String field;\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        File otherSrc = new File(getWorkDir(), "Other.java");
+        try (Writer w = new FileWriter(otherSrc)) {
+            w.write("/**Some source*/\n" +
+                    "public class Other {\n" +
+                    "    public void test() { }\n" +
+                    "}");
+        }
+        Map<String, List<Integer>> publishedDiagnostics = new HashMap<>();
+        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) {
+                synchronized (publishedDiagnostics) {
+                    publishedDiagnostics.computeIfAbsent(params.getUri(), uri -> new ArrayList<>())
+                                        .add(params.getDiagnostics().size());
+                    publishedDiagnostics.notifyAll();
+                }
+            }
+
+            @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();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
+        Position pos = new Position(1, 14);
+        List<? extends Location> definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(toURI(src)), pos)).get().getLeft();
+        assertEquals(1, definition.size());
+        String jlStringURI = definition.get(0).getUri();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(jlStringURI, "java", 0, URLMapper.findFileObject(new URL(jlStringURI)).asText())));
+        String otherSrcURI = toURI(otherSrc);
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(otherSrcURI, "java", 0, FileUtil.toFileObject(otherSrc).asText())));
+        synchronized (publishedDiagnostics) {
+            while (publishedDiagnostics.get(otherSrcURI) == null || publishedDiagnostics.get(otherSrcURI).size() != 2) {
+                publishedDiagnostics.wait();
+            }
+        }
+        assertEquals(Arrays.asList(0, 0), publishedDiagnostics.get(jlStringURI));
+    }
+
+    public void testRenameDocumentChangesCapabilitiesRenameOp() throws Exception {
+        doTestRename(init -> {
+                        WorkspaceEditCapabilities wec = new WorkspaceEditCapabilities();
+                        wec.setDocumentChanges(true);
+                        wec.setResourceOperations(Arrays.asList("rename"));
+                        WorkspaceClientCapabilities wcc = new WorkspaceClientCapabilities();
+                        wcc.setWorkspaceEdit(wec);
+                        init.setCapabilities(new ClientCapabilities(wcc, null, null));
+                     },
+                     cf -> {
+                         WorkspaceEdit edit = cf.get();
+                         assertTrue(edit.getChanges().isEmpty());
+                         Set<String> actual = edit.getDocumentChanges().stream().map(this::toString).collect(Collectors.toSet());
+                         Set<String> expected = new HashSet<>(Arrays.asList("Test2.java:[3:25-3:28=>nue]", "Test2.java:[1:8-1:11=>nue]"));
+                         assertEquals(expected, actual);
+                     },
+                     cf -> {
+                         WorkspaceEdit edit = cf.get();
+                         assertTrue(edit.getChanges().isEmpty());
+                         Set<String> actual = edit.getDocumentChanges().stream().map(this::toString).collect(Collectors.toSet());
+                         Set<String> expected = new HashSet<>(Arrays.asList("Test.java:[0:27-0:31=>TestNew, 1:4-1:8=>TestNew, 2:11-2:15=>TestNew]", "Test.java:[0:13-0:17=>TestNew]", "Test.java=>TestNew.java"));
+                         assertEquals(expected, actual);
+                     });
+    }
+    
+    public void testRenameDocumentChangesCapabilitiesNoRenameOp() throws Exception {
+        doTestRename(init -> {
+                        WorkspaceEditCapabilities wec = new WorkspaceEditCapabilities();
+                        wec.setDocumentChanges(true);
+                        WorkspaceClientCapabilities wcc = new WorkspaceClientCapabilities();
+                        wcc.setWorkspaceEdit(wec);
+                        init.setCapabilities(new ClientCapabilities(wcc, null, null));
+                     },
+                     cf -> {
+                         WorkspaceEdit edit = cf.get();
+                         assertTrue(edit.getChanges().isEmpty());
+                         Set<String> actual = edit.getDocumentChanges().stream().map(this::toString).collect(Collectors.toSet());
+                         Set<String> expected = new HashSet<>(Arrays.asList("Test2.java:[3:25-3:28=>nue]", "Test2.java:[1:8-1:11=>nue]"));
+                         assertEquals(expected, actual);
+                     },
+                     cf -> {
+                         WorkspaceEdit edit = cf.get();
+                         assertTrue(edit.getChanges().isEmpty());
+                         Set<String> actual = edit.getDocumentChanges().stream().map(this::toString).collect(Collectors.toSet());
+                         Set<String> expected = new HashSet<>(Arrays.asList("Test.java:[0:27-0:31=>TestNew, 1:4-1:8=>TestNew, 2:11-2:15=>TestNew]", "Test.java:[0:13-0:17=>TestNew]", "Test.java=>TestNew.java"));
+                         assertEquals(expected, actual);
+                     });
+    }
+    
+    private void doTestRename(Consumer<InitializeParams> settings,
+                              Validator<CompletableFuture<WorkspaceEdit>> validateFieldRename,
+                              Validator<CompletableFuture<WorkspaceEdit>> validateClassRename) throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        try (Writer w = new FileWriter(new File(src.getParentFile(), ".test-project"))) {}
+        String code = "public class Test {\n" +
+                      "    int val = new Test2().get();\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        File src2 = new File(getWorkDir(), "Test2.java");
+        try (Writer w = new FileWriter(src2)) {
+            w.write("public class Test2 extends Test {\n" +
+                    "    Test t;\n" +
+                    "    void m(Test p) {};\n" +
+                    "    int get() { return t.val; };\n" +
+                    "}\n");
+        }
+        List<Diagnostic>[] diags = new List[1];
+        CountDownLatch indexingComplete = new CountDownLatch(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())) {
+                    indexingComplete.countDown();
+                } 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.setRootUri(getWorkDir().toURI().toString());
+        settings.accept(initParams);
+        InitializeResult result = server.initialize(initParams).get();
+        indexingComplete.await();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
+
+        {
+            RenameParams params = new RenameParams(new TextDocumentIdentifier(src2.toURI().toString()),
+                                                   new Position(3, 27),
+                                                   "nue");
+
+            validateFieldRename.validate(server.getTextDocumentService().rename(params));
+        }
+
+        {
+            RenameParams params = new RenameParams(new TextDocumentIdentifier(src.toURI().toString()),
+                                                   new Position(0, 15),
+                                                   "TestNew");
+
+            validateClassRename.validate(server.getTextDocumentService().rename(params));
+        }
+
+    }
+
+    interface Validator<T> {
+        public void validate(T t) throws Exception;
+    }
+
+    private String toString(Either<TextDocumentEdit, ResourceOperation> e) {
+        if (e.isLeft()) {
+            TextDocumentEdit ted = e.getLeft();
+            VersionedTextDocumentIdentifier td = ted.getTextDocument();
+
+            return toString(td) + ":" + ted.getEdits().stream().map(this::toString).collect(Collectors.joining(", ", "[", "]"));
+        } else {
+            switch (e.getRight().getKind()) {
+                case "rename":
+                    RenameFile rf = (RenameFile) e.getRight();
+                    return uriToString(rf.getOldUri()) + "=>" + uriToString(rf.getNewUri());
+                default:
+                    throw new IllegalStateException(e.getRight().getKind());
+            }
+        }
+    }
+
+    private String toString(VersionedTextDocumentIdentifier td) {
+        return uriToString(td.getUri())/* + "(" + td.getVersion() + ")"*/;
+    }
+
+    private String toString(TextEdit edit) {
+        return toString(edit.getRange()) + "=>" + edit.getNewText();
+    }
+
     private String toString(Location location) {
         String path = location.getUri();
         String simpleName = path.substring(path.lastIndexOf('/') + 1);
-        return simpleName + ":" + location.getRange().getStart().getLine() + ":" + location.getRange().getStart().getCharacter() +
-                            "-" + location.getRange().getEnd().getLine() + ":" + location.getRange().getEnd().getCharacter();
+        return simpleName + ":" + toString(location.getRange());
+    }
+
+    private String uriToString(String uri) {
+        return uri.substring(uri.lastIndexOf('/') + 1);
+    }
+
+    private String toString(Range range) {
+        return       range.getStart().getLine() + ":" + range.getStart().getCharacter() +
+               "-" + range.getEnd().getLine() + ":" + range.getEnd().getCharacter();
     }
 
     private void assertHighlights(List<? extends DocumentHighlight> highlights, String... expected) {
@@ -1207,6 +3235,10 @@
                      stringHighlights);
     }
 
+    private String toURI(File f) {
+        return Utilities.toURI(f).toString();
+    }
+
     private static boolean jdk9Plus() {
         String version = System.getProperty("java.version");
         if (version == null || version.startsWith("1.")) {
diff --git a/java/java.lsp.server/vscode/.vscode/launch.json b/java/java.lsp.server/vscode/.vscode/launch.json
index b1fbaf5..9f7bc80 100644
--- a/java/java.lsp.server/vscode/.vscode/launch.json
+++ b/java/java.lsp.server/vscode/.vscode/launch.json
@@ -25,7 +25,8 @@
 			"runtimeExecutable": "${execPath}",
 			"args": [
 				"--extensionDevelopmentPath=${workspaceFolder}",
-				"--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
+				"--extensionTestsPath=${workspaceFolder}/out/test/suite/index",
+				"${workspaceFolder}/out/test/ws"
 			],
 			"outFiles": [
 				"${workspaceFolder}/out/test/**/*.js"
diff --git a/java/java.lsp.server/vscode/BUILD.md b/java/java.lsp.server/vscode/BUILD.md
index 46d66ec..fd6157b 100644
--- a/java/java.lsp.server/vscode/BUILD.md
+++ b/java/java.lsp.server/vscode/BUILD.md
@@ -62,11 +62,33 @@
 Often it is also important to properly clean everything. Use:
 
 ```bash
-java.lsp.server$ ant clean-vscode-server
+java.lsp.server$ ant clean-vscode-ext
 java.lsp.server$ cd ../..
 netbeans$ ant clean
 ```
 
+### Testing
+
+The `java.lsp.server` module has classical (as other NetBeans modules) tests.
+The most important one is [ServerTest](https://github.com/apache/netbeans/blob/master/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java)
+which simulates LSP communication and checks expected replies. In addition to
+that there are VS Code integration tests - those launch VS Code with the
+VSNetBeans extension and check behavior of the TypeScript integration code:
+
+```bash
+java.lsp.server$ ant build-vscode-ext # first and then
+java.lsp.server$ ant test-vscode-ext
+```
+
+In case you are behind a proxy, you may want to run the tests with
+
+```bash
+java.lsp.server$ npm_config_https_proxy=http://your.proxy.com:port ant test-vscode-ext
+```
+
+when executing the tests for the first time. That shall overcome the proxy
+and download an instance of `code` execute the tests on.
+
 ## Running and Debugging
 
 Have a sample Maven project, open it in NetBeans first and select the main file for both
diff --git a/java/java.lsp.server/vscode/package-lock.json b/java/java.lsp.server/vscode/package-lock.json
index 0e3253f..3ed2b2e 100644
--- a/java/java.lsp.server/vscode/package-lock.json
+++ b/java/java.lsp.server/vscode/package-lock.json
@@ -810,6 +810,11 @@
 				"glob": "^7.1.3"
 			}
 		},
+		"semver": {
+			"version": "6.3.0",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+			"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+		},
 		"set-blocking": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -884,18 +889,18 @@
 			"dev": true
 		},
 		"vscode-debugadapter": {
-			"version": "1.33.0",
-			"resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.33.0.tgz",
-			"integrity": "sha512-GsSD6PyVokjyW+7LU3FRXbu8SINv9muQtMQEDvwB6SIGQfrQzld8kECpQRr71j3mgXW+sTY+44/XMzueeaU6/Q==",
+			"version": "1.42.1",
+			"resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.42.1.tgz",
+			"integrity": "sha512-bICDB8mxReU2kGL13ftjNqeInYtVN3zpRdZKjRZHCpXC/mofrdaj9KRlJsALwcUNivMA9S/LwTZ2n+ros3X0jg==",
 			"requires": {
-				"mkdirp": "^0.5.1",
-				"vscode-debugprotocol": "1.33.0"
+				"mkdirp": "^0.5.5",
+				"vscode-debugprotocol": "1.42.0"
 			}
 		},
 		"vscode-debugprotocol": {
-			"version": "1.33.0",
-			"resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.33.0.tgz",
-			"integrity": "sha512-d+l4lrEz6OP2kmGpweqe37x9H7icAMV8S4m8azTWGAIlNJxBP4rlSTnZa7NMLcbgqWkWG9lTGY7fJ+rSPaW7yg=="
+			"version": "1.42.0",
+			"resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.42.0.tgz",
+			"integrity": "sha512-nVsfVCat9FZlOso5SYB1LQQiFGifTyOALpkpJdudDlRXGTpI3mSFiDYXWaoFm7UcfqTOzn1SC7Hqw4d89btT0w=="
 		},
 		"vscode-jsonrpc": {
 			"version": "5.0.1",
@@ -903,11 +908,12 @@
 			"integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A=="
 		},
 		"vscode-languageclient": {
-			"version": "4.4.2",
-			"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-4.4.2.tgz",
-			"integrity": "sha512-9TUzsg1UM6n1UEyPlWbDf7tK1wJAK7UGFRmGDN8sz4KmbbDiVRh6YicaB/5oRSVTpuV47PdJpYlOl3SJ0RiK1Q==",
+			"version": "6.1.3",
+			"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.1.3.tgz",
+			"integrity": "sha512-YciJxk08iU5LmWu7j5dUt9/1OLjokKET6rME3cI4BRpiF6HZlusm2ZwPt0MYJ0lV5y43sZsQHhyon2xBg4ZJVA==",
 			"requires": {
-				"vscode-languageserver-protocol": "^3.10.3"
+				"semver": "^6.3.0",
+				"vscode-languageserver-protocol": "^3.15.3"
 			}
 		},
 		"vscode-languageserver-protocol": {
diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json
index cc68979..71de400 100644
--- a/java/java.lsp.server/vscode/package.json
+++ b/java/java.lsp.server/vscode/package.json
@@ -25,12 +25,22 @@
 	},
 	"activationEvents": [
 		"onLanguage:java",
+		"workspaceContains:**/*.java",
 		"workspaceContains:pom.xml",
 		"workspaceContains:build.gradle",
 		"onDebug"
 	],
 	"main": "./out/extension.js",
 	"contributes": {
+		"languages": [
+			{
+				"id": "javascript",
+				"mimetypes": [
+					"text/javascript",
+					"application/javascript"
+				]
+			}
+		],
 		"configuration": {
 			"title": "Java",
 			"properties": {
@@ -159,11 +169,10 @@
 	"scripts": {
 		"vscode:prepublish": "npm run compile",
 		"compile": "tsc -p ./",
-		"lint": "eslint src --ext ts",
 		"watch": "tsc -watch -p ./",
-		"pretest": "npm run compile && npm run lint",
 		"test": "node ./out/test/runTest.js",
-		"nbcode": "node ./out/nbcode.js"
+		"nbcode": "node ./out/nbcode.js",
+		"nbjavac": "node ./out/nbcode.js -J-Dnetbeans.close=true --modules --install .*nbjavac.*"
 	},
 	"devDependencies": {
 		"@types/vscode": "^1.47.0",
@@ -176,7 +185,7 @@
 		"vscode-test": "^1.3.0"
 	},
 	"dependencies": {
-		"vscode-languageclient": "4.4.2",
-		"vscode-debugadapter": "1.33.0"
+		"vscode-languageclient": "6.1.3",
+		"vscode-debugadapter": "1.42.1"
 	}
 }
diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts
index 1c85249..c3a78f3 100644
--- a/java/java.lsp.server/vscode/src/extension.ts
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -28,6 +28,8 @@
     StreamInfo,
     Message,
     MessageType,
+    LogMessageNotification,
+    RevealOutputChannelOn
 } from 'vscode-languageclient';
 
 import * as net from 'net';
@@ -36,13 +38,34 @@
 import { ChildProcess } from 'child_process';
 import * as vscode from 'vscode';
 import * as launcher from './nbcode';
-import { StatusMessageRequest, ShowStatusMessageParams  } from './protocol';
+import { StatusMessageRequest, ShowStatusMessageParams, QuickPickRequest, InputBoxRequest } from './protocol';
 
-let client: LanguageClient;
+const API_VERSION : string = "1.0";
+let client: Promise<LanguageClient>;
 let nbProcess : ChildProcess | null = null;
 let debugPort: number = -1;
+let consoleLog: boolean = !!process.env['ENABLE_CONSOLE_LOG'];
 
-function findClusters(myPath : string): string[] {
+function handleLog(log: vscode.OutputChannel, msg: string): void {
+    log.appendLine(msg);
+    if (consoleLog) {
+        console.log(msg);
+    }
+}
+
+function handleLogNoNL(log: vscode.OutputChannel, msg: string): void {
+    log.append(msg);
+    if (consoleLog) {
+        process.stdout.write(msg);
+    }
+}
+
+export function enableConsoleLog() {
+    consoleLog = true;
+    console.log("enableConsoleLog");
+}
+
+export function findClusters(myPath : string): string[] {
     let clusters = [];
     for (let e of vscode.extensions.all) {
         if (e.extensionPath === myPath) {
@@ -108,14 +131,18 @@
     onChange(currentJdk);
 }
 
-export function activate(context: ExtensionContext) {
+interface VSNetBeansAPI {
+    version : string;
+}
+
+export function activate(context: ExtensionContext): VSNetBeansAPI {
     let log = vscode.window.createOutputChannel("Apache NetBeans Language Server");
 
     let conf = workspace.getConfiguration();
     if (conf.get("netbeans.conflict.check")) {
         let e = vscode.extensions.getExtension('redhat.java');
         function disablingFailed(reason: any) {
-            log.appendLine('Disabling some services failed ' + reason);
+            handleLog(log, 'Disabling some services failed ' + reason);
         }
         if (e && workspace.name) {
             vscode.window.showInformationMessage(`redhat.java found at ${e.extensionPath} - Suppressing some services to not clash with Apache NetBeans Language Server.`);
@@ -143,29 +170,42 @@
     context.subscriptions.push(commands.registerCommand('java.workspace.compile', () => {
         return window.withProgress({ location: ProgressLocation.Window }, p => {
             return new Promise(async (resolve, reject) => {
+                let c : LanguageClient = await client;
                 const commands = await vscode.commands.getCommands();
                 if (commands.includes('java.build.workspace')) {
                     p.report({ message: 'Compiling workspace...' });
-                    if (client != null) {
-                        client.outputChannel.show(true);
-                    }
+                    c.outputChannel.show(true);
                     const start = new Date().getTime();
+                    handleLog(log, `starting java.build.workspace`);
                     const res = await vscode.commands.executeCommand('java.build.workspace');
                     const elapsed = new Date().getTime() - start;
+                    handleLog(log, `finished java.build.workspace in ${elapsed} ms with result ${res}`);
                     const humanVisibleDelay = elapsed < 1000 ? 1000 : 0;
                     setTimeout(() => { // set a timeout so user would still see the message when build time is short
                         if (res) {
-                            resolve();
+                            resolve(res);
                         } else {
-                            reject();
+                            reject(res);
                         }
                     }, humanVisibleDelay);
                 } else {
-                    reject();
+                    reject(`cannot compile workspace; client is ${client}`);
                 }
             });
         });
     }));
+    context.subscriptions.push(commands.registerCommand('java.rename.element.at', async (offset) => {
+        const editor = window.activeTextEditor;
+        if (editor) {
+            await commands.executeCommand('editor.action.rename', [
+                editor.document.uri,
+                editor.document.positionAt(offset),
+            ]);
+        }
+    }));
+    return Object.freeze({
+        version : API_VERSION
+    });
 }
 
 /**
@@ -179,29 +219,33 @@
 let activationPending : boolean = false;
 
 function activateWithJDK(specifiedJDK: string | null, context: ExtensionContext, log : vscode.OutputChannel, notifyKill: boolean): void {
+    let setClient : [(c : LanguageClient) => void, (err : any) => void];
+    client = new Promise<LanguageClient>((clientOK, clientErr) => {
+        setClient = [ clientOK, clientErr ];
+    });
     const a : Promise<void> | null = maintenance;
     if (activationPending) {
         // do not activate more than once in parallel.
-        console.log("Server activation requested repeatedly, ignoring...");
+        handleLog(log, "Server activation requested repeatedly, ignoring...");
         return;
     }
     activationPending = true;
     // chain the restart after termination of the former process.
     if (a != null) {
-        console.log("Server activation initiated while in maintenance mode, scheduling after maintenance");
+        handleLog(log, "Server activation initiated while in maintenance mode, scheduling after maintenance");
         a.then(() => killNbProcess(notifyKill, log)).
-            then(() => doActivateWithJDK(specifiedJDK, context, log, notifyKill));
+            then(() => doActivateWithJDK(specifiedJDK, context, log, notifyKill, setClient));
     } else {
-        console.log("Initiating server activation");
+        handleLog(log, "Initiating server activation");
         killNbProcess(notifyKill, log).then(
-            () => doActivateWithJDK(specifiedJDK, context, log, notifyKill)
+            () => doActivateWithJDK(specifiedJDK, context, log, notifyKill, setClient)
         );
     }
 }
 
 function killNbProcess(notifyKill : boolean, log : vscode.OutputChannel, specProcess?: ChildProcess) : Promise<void> {
     const p = nbProcess;
-    console.log("Request to kill LSP server.");
+    handleLog(log, "Request to kill LSP server.");
     if (p && (!specProcess || specProcess == p)) {
         if (notifyKill) {
             vscode.window.setStatusBarMessage("Restarting Apache NetBeans Language Server.", 2000);
@@ -209,10 +253,10 @@
         return new Promise((resolve, reject) => {
             nbProcess = null;
             p.on('close', function(code: number) {
-                console.log("LSP server closed: " + p.pid)
+                handleLog(log, "LSP server closed: " + p.pid)
                 resolve();
             });
-            console.log("Killing LSP server " + p.pid);
+            handleLog(log, "Killing LSP server " + p.pid);
             if (!p.kill()) {
                 reject("Cannot kill");
             }
@@ -222,15 +266,17 @@
         if (specProcess) {
             msg += "Requested kill on " + specProcess.pid + ", ";
         }
-        console.log(msg + "current process is " + (p ? p.pid : "None"));
+        handleLog(log, msg + "current process is " + (p ? p.pid : "None"));
         return new Promise((res, rej) => { res(); });
     }
 }
 
-function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContext, log : vscode.OutputChannel, notifyKill: boolean): void {
+function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContext, log : vscode.OutputChannel, notifyKill: boolean,
+    setClient : [(c : LanguageClient) => void, (err : any) => void]
+): void {
     maintenance = null;
     let restartWithJDKLater : ((time: number, n: boolean) => void) = function restartLater(time: number, n : boolean) {
-        log.appendLine(`Restart of Apache Language Server requested in ${(time / 1000)} s.`);
+        handleLog(log, `Restart of Apache Language Server requested in ${(time / 1000)} s.`);
         setTimeout(() => {
             activateWithJDK(specifiedJDK, context, log, n);
         }, time);
@@ -245,51 +291,53 @@
         verbose: beVerbose
     };
     let launchMsg = `Launching Apache NetBeans Language Server with ${specifiedJDK ? specifiedJDK : 'default system JDK'}`;
-    log.appendLine(launchMsg);
+    handleLog(log, launchMsg);
     vscode.window.setStatusBarMessage(launchMsg, 2000);
 
     let ideRunning = new Promise((resolve, reject) => {
-        let collectedText : string | null = '';
-        function logAndWaitForEnabled(text: string) {
+        let stdOut : string | null = '';
+        function logAndWaitForEnabled(text: string, isOut: boolean) {
             if (p == nbProcess) {
                 activationPending = false;
             }
-            log.append(text);
-            if (collectedText == null) {
+            handleLogNoNL(log, text);
+            if (stdOut == null) {
                 return;
             }
-            collectedText += text;
-            if (collectedText.match(/org.netbeans.modules.java.lsp.server.*Enabled/)) {
+            if (isOut) {
+                stdOut += text;
+            }
+            if (stdOut.match(/org.netbeans.modules.java.lsp.server.*Enabled/)) {
                 resolve(text);
-                collectedText = null;
+                stdOut = null;
             }
         }
         let p = launcher.launch(info, "--modules", "--list");
-        console.log("LSP server launching: " + p.pid);
+        handleLog(log, "LSP server launching: " + p.pid);
         p.stdout.on('data', function(d: any) {
-            logAndWaitForEnabled(d.toString());
+            logAndWaitForEnabled(d.toString(), true);
         });
         p.stderr.on('data', function(d: any) {
-            logAndWaitForEnabled(d.toString());
+            logAndWaitForEnabled(d.toString(), false);
         });
         nbProcess = p;
         p.on('close', function(code: number) {
             if (p == nbProcess && code != 0 && code) {
                 vscode.window.showWarningMessage("Apache NetBeans Language Server exited with " + code);
             }
-            if (collectedText != null) {
-                let match = collectedText.match(/org.netbeans.modules.java.lsp.server[^\n]*/)
+            if (stdOut != null) {
+                let match = stdOut.match(/org.netbeans.modules.java.lsp.server[^\n]*/)
                 if (match?.length == 1) {
-                    log.appendLine(match[0]);
+                    handleLog(log, match[0]);
                 } else {
-                    log.appendLine("Cannot find org.netbeans.modules.java.lsp.server in the log!");
+                    handleLog(log, "Cannot find org.netbeans.modules.java.lsp.server in the log!");
                 }
                 log.show(false);
                 killNbProcess(false, log, p);
                 reject("Apache NetBeans Language Server not enabled!");
             } else {
-                console.log("LSP server " + p.pid + " terminated with " + code);
-                log.appendLine("Exit code " + code);
+                handleLog(log, "LSP server " + p.pid + " terminated with " + code);
+                handleLog(log, "Exit code " + code);
             }
         });
     });
@@ -347,7 +395,8 @@
                 ]
             },
             outputChannel: log,
-            revealOutputChannelOn: 3, // error
+            revealOutputChannelOn: RevealOutputChannelOn.Never,
+            progressOnInitialization: true,
             initializationOptions : {
                 'nbcodeCapabilities' : {
                     'statusBarMessageSupport' : true
@@ -358,7 +407,7 @@
                     return ErrorAction.Continue;
                 },
                 closed : function(): CloseAction {
-                    log.appendLine("Connection to Apache NetBeans Language Server closed.");
+                    handleLog(log, "Connection to Apache NetBeans Language Server closed.");
                     if (!activationPending) {
                         restartWithJDKLater(10000, false);
                     }
@@ -367,23 +416,32 @@
             }
         }
 
-        if (client) {
-            client.stop();
-        }
-        client = new LanguageClient(
+        
+        let c = new LanguageClient(
                 'java',
                 'NetBeans Java',
                 connection,
                 clientOptions
         );
-        client.start();
-        client.onReady().then(() => {
+        handleLog(log, 'Language Client: Starting');
+        c.start();
+        c.onReady().then(() => {
             commands.executeCommand('setContext', 'nbJavaLSReady', true);
-            client.onNotification(StatusMessageRequest.type, showStatusBarMessage);
-        });
+            c.onNotification(StatusMessageRequest.type, showStatusBarMessage);
+            c.onNotification(LogMessageNotification.type, (param) => handleLog(log, param.message));
+            c.onRequest(QuickPickRequest.type, async param => {
+                const selected = await window.showQuickPick(param.items, { placeHolder: param.placeHolder, canPickMany: param.canPickMany });
+                return selected ? Array.isArray(selected) ? selected : [selected] : undefined;
+            });
+            c.onRequest(InputBoxRequest.type, async param => {
+                return await window.showInputBox({ prompt: param.prompt, value: param.value });
+            });
+            handleLog(log, 'Language Client: Ready');
+            setClient[0](c);
+        }).catch(setClient[1]);
     }).catch((reason) => {
         activationPending = false;
-        log.append(reason);
+        handleLog(log, reason);
         window.showErrorMessage('Error initializing ' + reason);
     });
 
@@ -422,15 +480,15 @@
                 if (yes === reply) {
                     vscode.window.setStatusBarMessage("Preparing Apache NetBeans Language Server for additional installation", 2000);
                     restartWithJDKLater = function() {
-                        console.log("Ignoring request for restart of Apache NetBeans Language Server");
+                        handleLog(log, "Ignoring request for restart of Apache NetBeans Language Server");
                     };
                     maintenance = new Promise((resolve, reject) => {
                         const kill : Promise<void> = killNbProcess(false, log);
                         kill.then(() => {
                             let installProcess = launcher.launch(info, "-J-Dnetbeans.close=true", "--modules", "--install", ".*nbjavac.*");
-                            console.log("Launching installation process: " + installProcess.pid);
+                            handleLog(log, "Launching installation process: " + installProcess.pid);
                             let logData = function(d: any) {
-                                log.append(d.toString());
+                                handleLogNoNL(log, d.toString());
                             };
                             installProcess.stdout.on('data', logData);
                             installProcess.stderr.on('data', logData);
@@ -438,8 +496,8 @@
                             // MUST wait on 'close', since stdout is inherited by children. The installProcess dies but
                             // the inherited stream will be closed by the last child dying.
                             installProcess.on('close', function(code: number) {
-                                console.log("Installation completed: " + installProcess.pid);
-                                log.appendLine("Additional Java Support installed with exit code " + code);
+                                handleLog(log, "Installation completed: " + installProcess.pid);
+                                handleLog(log, "Additional Java Support installed with exit code " + code);
                                 // will be actually run after maintenance is resolve()d.
                                 activateWithJDK(specifiedJDK, context, log, notifyKill)
                                 resolve();
@@ -457,10 +515,7 @@
     if (nbProcess != null) {
         nbProcess.kill();
     }
-    if (!client) {
-        return Promise.resolve();
-    }
-    return client.stop();
+    return client.then(c => c.stop());
 }
 
 class NetBeansDebugAdapterDescriptionFactory implements vscode.DebugAdapterDescriptorFactory {
diff --git a/java/java.lsp.server/vscode/src/protocol.ts b/java/java.lsp.server/vscode/src/protocol.ts
index 4b485ba..6f56c6d 100644
--- a/java/java.lsp.server/vscode/src/protocol.ts
+++ b/java/java.lsp.server/vscode/src/protocol.ts
@@ -18,8 +18,10 @@
  */
 'use strict';
 
+import {QuickPickItem} from 'vscode';
 import {
     NotificationType,
+    RequestType,
     ShowMessageParams
 } from 'vscode-languageclient';
 
@@ -33,3 +35,37 @@
 export namespace StatusMessageRequest {
     export const type = new NotificationType<ShowStatusMessageParams, void>('window/showStatusBarMessage');
 };
+
+export interface ShowQuickPickParams {
+    /**
+     * A string to show as placeholder in the input box to guide the user what to pick on.
+     */
+    placeHolder: string;
+    /**
+     * An optional flag to make the picker accept multiple selections.
+     */
+    canPickMany?: boolean;
+    /**
+     * A list of items.
+     */
+    items: QuickPickItem[];
+}
+
+export namespace QuickPickRequest {
+    export const type = new RequestType<ShowQuickPickParams, QuickPickItem[], void, void>('window/showQuickPick');
+}
+
+export interface ShowInputBoxParams {
+    /**
+     * The text to display underneath the input box.
+     */
+    prompt: string;
+    /**
+     * The value to prefill in the input box.
+     */
+    value: string;
+}
+
+export namespace InputBoxRequest {
+    export const type = new RequestType<ShowInputBoxParams, string | undefined, void, void>('window/showInputBox');
+}
diff --git a/java/java.lsp.server/vscode/src/test/runTest.ts b/java/java.lsp.server/vscode/src/test/runTest.ts
index 1eabfa3..5ed5f50 100644
--- a/java/java.lsp.server/vscode/src/test/runTest.ts
+++ b/java/java.lsp.server/vscode/src/test/runTest.ts
@@ -2,22 +2,39 @@
 
 import { runTests } from 'vscode-test';
 
+import * as os from 'os';
+import * as fs from 'fs';
+
 async function main() {
-	try {
-		// The folder containing the Extension Manifest package.json
-		// Passed to `--extensionDevelopmentPath`
-		const extensionDevelopmentPath = path.resolve(__dirname, '../../');
+    try {
+        // The folder containing the Extension Manifest package.json
+        // Passed to `--extensionDevelopmentPath`
+        const extensionDevelopmentPath = path.resolve(__dirname, '../../');
 
-		// The path to test runner
-		// Passed to --extensionTestsPath
-		const extensionTestsPath = path.resolve(__dirname, './suite/index');
+        // The path to test runner
+        // Passed to --extensionTestsPath
+        const extensionTestsPath = path.resolve(__dirname, './suite/index');
 
-		// Download VS Code, unzip it and run the integration test
-		await runTests({ extensionDevelopmentPath, extensionTestsPath });
-	} catch (err) {
-		console.error('Failed to run tests');
-		process.exit(1);
-	}
+        const workspaceDir = path.join(extensionDevelopmentPath, 'out', 'test', 'ws');
+
+        if (!fs.statSync(workspaceDir).isDirectory()) {
+            throw `Expecting ${workspaceDir} to be a directory!`;
+        }
+
+        // Download VS Code, unzip it and run the integration test
+        await runTests({
+            version: "1.50.0",
+            extensionDevelopmentPath,
+            extensionTestsPath,
+            extensionTestsEnv: {
+                'ENABLE_CONSOLE_LOG' : 'true'
+            },
+            launchArgs: [workspaceDir, '--async-stack-traces']
+        });
+    } catch (err) {
+        console.error('Failed to run tests');
+        process.exit(1);
+    }
 }
 
 main();
diff --git a/java/java.lsp.server/vscode/src/test/suite/extension.test.ts b/java/java.lsp.server/vscode/src/test/suite/extension.test.ts
index 08a6f78..0ab9c32 100644
--- a/java/java.lsp.server/vscode/src/test/suite/extension.test.ts
+++ b/java/java.lsp.server/vscode/src/test/suite/extension.test.ts
@@ -1,15 +1,130 @@
 import * as assert from 'assert';
+import * as fs from 'fs';
+import * as path from 'path';
+import { spawn, ChildProcessByStdio, spawnSync, SpawnSyncReturns } from 'child_process';
+import { Readable } from 'stream';
 
 // You can import and use all API from the 'vscode' module
 // as well as import your extension to test it
 import * as vscode from 'vscode';
-// import * as myExtension from '../../extension';
+import * as myExtension from '../../extension';
 
 suite('Extension Test Suite', () => {
-	vscode.window.showInformationMessage('Start all tests.');
+    vscode.window.showInformationMessage('Cleaning up workspace.');
+    let folder: string = assertWorkspace();
+    fs.rmdirSync(folder, { recursive: true });
+    fs.mkdirSync(folder, { recursive: true });
+    vscode.window.showInformationMessage('Start all tests.');
+    myExtension.enableConsoleLog();
 
-	test('Sample test', () => {
-		assert.equal(-1, [1, 2, 3].indexOf(5));
-		assert.equal(-1, [1, 2, 3].indexOf(0));
-	});
+    test('VSNetBeans is present', async () => {
+        let nbcode = vscode.extensions.getExtension('asf.apache-netbeans-java');
+        assert.ok(nbcode, "Apache NetBeans Extension is present");
+        let api = await nbcode.activate();
+        assert.ok(api.version, "Some version is specified");
+
+        let cannotReassignVersion = false;
+        try {
+            api.version = "different";
+        } catch (e) {
+            cannotReassignVersion = true;
+        }
+        assert.ok(cannotReassignVersion, "Cannot reassign value of version");
+    });
+
+    test('Find clusters', async () => {
+        let clusters = myExtension.findClusters('non-existent');
+        assert.strictEqual(clusters.length, 6, 'six clusters found: ' + clusters);
+        let nbcode = vscode.extensions.getExtension('asf.apache-netbeans-java');
+        assert.ok(nbcode);
+        for (let c of clusters) {
+            assert.ok(c.startsWith(nbcode.extensionPath), `All extensions are below ${nbcode.extensionPath}, but: ${c}`);
+        }
+    });
+
+    async function demo(where: number) {
+        let folder: string = assertWorkspace();
+
+        await fs.promises.writeFile(path.join(folder, 'pom.xml'), `
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.netbeans.demo.vscode.t1</groupId>
+    <artifactId>basicapp</artifactId>
+    <version>1.0</version>
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+</project>
+		`);
+
+        let pkg = path.join(folder, 'src', 'main', 'java', 'pkg');
+        let mainJava = path.join(pkg, 'Main.java');
+
+        await fs.promises.mkdir(pkg, { recursive: true });
+
+        await fs.promises.writeFile(mainJava, `
+package pkg;
+class Main {
+	public static void main(String... args) {
+		System.out.println("Hello World!");
+	}
+}
+		`);
+
+        vscode.workspace.saveAll();
+
+        if (where === 6) return;
+
+        try {
+            console.log("Test: invoking compile");
+            let res = await vscode.commands.executeCommand("java.workspace.compile");
+            console.log(`Test: compile finished with ${res}`);
+        } catch (error) {
+            dumpJava();
+            throw error;
+        }
+
+        if (where === 7) return;
+
+        let mainClass = path.join(folder, 'target', 'classes', 'pkg', 'Main.class');
+
+        if (where === 8) return;
+
+        assert.ok(fs.statSync(mainClass).isFile(), "Class created by compilation: " + mainClass);
+    }
+
+    test("Compile workspace6", async() => demo(6));
+//    test("Compile workspace7", async() => demo(7));
+//    test("Compile workspace8", async() => demo(8));
 });
+
+function assertWorkspace(): string {
+    assert.ok(vscode.workspace, "workspace is defined");
+    const dirs = vscode.workspace.workspaceFolders;
+    assert.ok(dirs?.length, "There are some workspace folders: " + dirs);
+    assert.strictEqual(dirs.length, 1, "One folder provided");
+    let folder: string = dirs[0].uri.fsPath;
+    return folder;
+}
+
+async function dumpJava() {
+    const cmd = 'jps';
+    const args = [ '-v' ];
+    console.log(`Running: ${cmd} ${args.join(' ')}`);
+    let p : ChildProcessByStdio<null, Readable, Readable> = spawn(cmd, args, {
+        stdio : ["ignore", "pipe", "pipe"],
+    });
+    let n = await new Promise<number>((r, e) => {
+        p.stdout.on('data', function(d: any) {
+            console.log(d.toString());
+        });
+        p.stderr.on('data', function(d: any) {
+            console.log(d.toString());
+        });
+        p.on('close', function(code: number) {
+            r(code);
+        });
+    });
+    console.log(`${cmd} ${args.join(' ')} finished with code ${n}`);
+}
diff --git a/java/java.lsp.server/vscode/src/test/suite/index.ts b/java/java.lsp.server/vscode/src/test/suite/index.ts
index 7029e38..84c0fbd 100644
--- a/java/java.lsp.server/vscode/src/test/suite/index.ts
+++ b/java/java.lsp.server/vscode/src/test/suite/index.ts
@@ -6,33 +6,36 @@
 	// Create the mocha test
 	const mocha = new Mocha({
 		ui: 'tdd',
-		color: true
+		color: true,
+		timeout: 60000
 	});
 
 	const testsRoot = path.resolve(__dirname, '..');
 
 	return new Promise((c, e) => {
-		glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
-			if (err) {
-				return e(err);
-			}
+		setTimeout(function() {
+			glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
+				if (err) {
+					return e(err);
+				}
 
-			// Add files to the test suite
-			files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
+				// Add files to the test suite
+				files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
 
-			try {
-				// Run the mocha test
-				mocha.run(failures => {
-					if (failures > 0) {
-						e(new Error(`${failures} tests failed.`));
-					} else {
-						c();
-					}
-				});
-			} catch (err) {
-				console.error(err);
-				e(err);
-			}
-		});
+				try {
+					// Run the mocha test
+					mocha.run(failures => {
+						if (failures > 0) {
+							e(new Error(`${failures} tests failed.`));
+						} else {
+							c();
+						}
+					});
+				} catch (err) {
+					console.error(err);
+					e(err);
+				}
+			});
+		}, 3000);
 	});
 }
diff --git a/java/java.lsp.server/vscode/src/test/ws/empty.ts b/java/java.lsp.server/vscode/src/test/ws/empty.ts
new file mode 100644
index 0000000..952376c
--- /dev/null
+++ b/java/java.lsp.server/vscode/src/test/ws/empty.ts
@@ -0,0 +1,2 @@
+export function empty(): any {
+}
diff --git a/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java b/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java
index 7e2baa0..baef5da 100644
--- a/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java
+++ b/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java
@@ -3356,6 +3356,10 @@
     }
     
     private void mapComments(BlockTree block, String inputText, WorkingCopy copy, CommentHandler comments, SourcePositions positions) {
+        if (copy.getFileObject() == null) {
+            // prevent IllegalStateException thrown form AssignComments constructor below
+            return;
+        }
         TokenSequence<JavaTokenId> seq = TokenHierarchy.create(inputText, JavaTokenId.language()).tokenSequence(JavaTokenId.language());
         AssignComments ti = new AssignComments(copy, block, seq, positions);
         ti.scan(block, null);
diff --git a/java/java.sourceui/src/org/netbeans/modules/java/source/ui/JavaSymbolProvider.java b/java/java.sourceui/src/org/netbeans/modules/java/source/ui/JavaSymbolProvider.java
index 2498cc5..8a56856 100644
--- a/java/java.sourceui/src/org/netbeans/modules/java/source/ui/JavaSymbolProvider.java
+++ b/java/java.sourceui/src/org/netbeans/modules/java/source/ui/JavaSymbolProvider.java
@@ -29,6 +29,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import javax.lang.model.element.Element;
@@ -82,7 +83,7 @@
     private static final String CAPTURED_WILDCARD = "<captured wildcard>"; //NOI18N
     private static final String UNKNOWN = "<unknown>"; //NOI18N
 
-    private volatile boolean canceled;
+    private final AtomicBoolean canceled = new AtomicBoolean();
 
     @Override
     public String name() {
@@ -117,110 +118,155 @@
             final Cache cache = scanInProgress ?
                 Cache.create(textToSearch, st) :
                 null;
-            String prefix = null;
-            final int dotIndex = textToSearch.lastIndexOf('.'); //NOI18N
-            if (dotIndex > 0 && dotIndex != textToSearch.length()-1) {
-                prefix = textToSearch.substring(0, dotIndex);
-                textToSearch = textToSearch.substring(dotIndex+1);
-            }
-            final String textToHighLight = textToSearch;
-            ClassIndex.NameKind _kind;
-            boolean _caseSensitive;
-            switch (st) {
-                case PREFIX:
-                    _kind = ClassIndex.NameKind.PREFIX;
-                    _caseSensitive = true;
-                    break;
-                case REGEXP:
-                    _kind = ClassIndex.NameKind.REGEXP;
-                    textToSearch = NameMatcherFactory.wildcardsToRegexp(
-                            removeNonJavaChars(textToSearch),
-                            true);
-                    _caseSensitive = true;
-                    break;
-                case CAMEL_CASE:
-                    _kind = ClassIndex.NameKind.CAMEL_CASE;
-                    _caseSensitive = true;
-                    break;
-                case CASE_INSENSITIVE_CAMEL_CASE:
-                    _kind = ClassIndex.NameKind.CAMEL_CASE_INSENSITIVE;
-                    _caseSensitive = false;
-                    break;
-                case EXACT_NAME:
-                    _kind = ClassIndex.NameKind.SIMPLE_NAME;
-                    _caseSensitive = true;
-                    break;
-                case CASE_INSENSITIVE_PREFIX:
-                    _kind = ClassIndex.NameKind.CASE_INSENSITIVE_PREFIX;
-                    _caseSensitive = false;
-                    break;
-                case CASE_INSENSITIVE_EXACT_NAME:
-                    _kind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP;
-                    _caseSensitive = false;
-                    break;
-                case CASE_INSENSITIVE_REGEXP:
-                    _kind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP;
-                    textToSearch = NameMatcherFactory.wildcardsToRegexp(
-                            removeNonJavaChars(textToSearch),
-                            true);
-                    _caseSensitive = false;
-                    break;
-                default:
-                    throw new IllegalArgumentException();
-            }
-            final String ident = textToSearch;
-            final ClassIndex.NameKind kind = _kind;
-            final boolean caseSensitive = _caseSensitive;
-            final Pair<NameMatcher,Boolean> restriction;
-            if (prefix != null) {
-                restriction = compileName(prefix,caseSensitive);
-                result.setHighlightText(textToHighLight);
-            } else {
-                restriction = null;
-            }
-            try {
-                final ClassIndexManager manager = ClassIndexManager.getDefault();
-
-                Collection<FileObject> roots = QuerySupport.findRoots(
-                        (Project)null,
-                        Collections.singleton(ClassPath.SOURCE),
-                        Collections.<String>emptySet(),
-                        Collections.<String>emptySet());
-
-                final Set<URL> rootUrls = new HashSet<>();
-                for(FileObject root : roots) {
-                    if (canceled) {
-                        return;
-                    }
-                    rootUrls.add(root.toURL());
+            doComputeSymbols(st, textToSearch, new ResultHandler() {
+                private FileObject root;
+                private ProjectInformation projectInfo;
+                private ClassIndexImpl ci;
+                @Override
+                public void setHighlightText(String text) {
+                    result.setHighlightText(text);
                 }
 
-                if (LOGGER.isLoggable(Level.FINE)) {
-                    LOGGER.log(Level.FINE, "Querying following roots:"); //NOI18N
+                @Override
+                public void runRoot(FileObject root, ClassIndexImpl ci, Exec exec) throws IOException, InterruptedException {
+                    try {
+                        Project project = FileOwnerQuery.getOwner(root);
+
+                        this.root = root;
+                        this.projectInfo = project == null ?
+                                null :
+                                project.getLookup().lookup(ProjectInformation.class);   //Intentionally does not use ProjectUtils.getInformation() it does project icon annotation which is expensive
+                        this.ci = ci;
+                        exec.run();
+                    } finally {
+                        this.root = null;
+                        this.projectInfo = null;
+                        this.ci = null;
+                    }
+                }
+
+                @Override
+                public void handleResult(ElementHandle<TypeElement> owner, String ident, boolean caseSensitive) {
+                    final AsyncJavaSymbolDescriptor d = new AsyncJavaSymbolDescriptor(
+                            projectInfo,
+                            root,
+                            ci,
+                            owner,
+                            ident,
+                            caseSensitive);
+                    result.addResult(d);
+                    if (cache != null) {
+                        cache.offer(d);
+                    }
+                }
+            }, true, canceled);
+        } finally {
+            clearCancel();
+        }
+    }
+
+    public static void doComputeSymbols(SearchType st, String textToSearch, ResultHandler handler, boolean async, AtomicBoolean canceled) {
+        String prefix = null;
+        final int dotIndex = textToSearch.lastIndexOf('.'); //NOI18N
+        if (dotIndex > 0 && dotIndex != textToSearch.length()-1) {
+            prefix = textToSearch.substring(0, dotIndex);
+            textToSearch = textToSearch.substring(dotIndex+1);
+        }
+        final String textToHighLight = textToSearch;
+        ClassIndex.NameKind _kind;
+        boolean _caseSensitive;
+        switch (st) {
+            case PREFIX:
+                _kind = ClassIndex.NameKind.PREFIX;
+                _caseSensitive = true;
+                break;
+            case REGEXP:
+                _kind = ClassIndex.NameKind.REGEXP;
+                textToSearch = NameMatcherFactory.wildcardsToRegexp(
+                        removeNonJavaChars(textToSearch),
+                        true);
+                _caseSensitive = true;
+                break;
+            case CAMEL_CASE:
+                _kind = ClassIndex.NameKind.CAMEL_CASE;
+                _caseSensitive = true;
+                break;
+            case CASE_INSENSITIVE_CAMEL_CASE:
+                _kind = ClassIndex.NameKind.CAMEL_CASE_INSENSITIVE;
+                _caseSensitive = false;
+                break;
+            case EXACT_NAME:
+                _kind = ClassIndex.NameKind.SIMPLE_NAME;
+                _caseSensitive = true;
+                break;
+            case CASE_INSENSITIVE_PREFIX:
+                _kind = ClassIndex.NameKind.CASE_INSENSITIVE_PREFIX;
+                _caseSensitive = false;
+                break;
+            case CASE_INSENSITIVE_EXACT_NAME:
+                _kind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP;
+                _caseSensitive = false;
+                break;
+            case CASE_INSENSITIVE_REGEXP:
+                _kind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP;
+                textToSearch = NameMatcherFactory.wildcardsToRegexp(
+                        removeNonJavaChars(textToSearch),
+                        true);
+                _caseSensitive = false;
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+        final String ident = textToSearch;
+        final ClassIndex.NameKind kind = _kind;
+        final boolean caseSensitive = _caseSensitive;
+        final Pair<NameMatcher,Boolean> restriction;
+        if (prefix != null) {
+            restriction = compileName(prefix,caseSensitive);
+            handler.setHighlightText(textToHighLight);
+        } else {
+            restriction = null;
+        }
+        try {
+            final ClassIndexManager manager = ClassIndexManager.getDefault();
+
+            Collection<FileObject> roots = QuerySupport.findRoots(
+                    (Project)null,
+                    Collections.singleton(ClassPath.SOURCE),
+                    Collections.<String>emptySet(),
+                    Collections.<String>emptySet());
+
+            final Set<URL> rootUrls = new HashSet<>();
+            for(FileObject root : roots) {
+                if (canceled.get()) {
+                    return;
+                }
+                rootUrls.add(root.toURL());
+            }
+
+            if (LOGGER.isLoggable(Level.FINE)) {
+                LOGGER.log(Level.FINE, "Querying following roots:"); //NOI18N
+                for (URL url : rootUrls) {
+                    LOGGER.log(Level.FINE, "  {0}", url); //NOI18N
+                }
+                LOGGER.log(Level.FINE, "-------------------------"); //NOI18N
+            }
+            //Perform all queries in single op
+            IndexManager.priorityAccess(new IndexManager.Action<Void>() {
+                @Override
+                public Void run() throws IOException, InterruptedException {
                     for (URL url : rootUrls) {
-                        LOGGER.log(Level.FINE, "  {0}", url); //NOI18N
-                    }
-                    LOGGER.log(Level.FINE, "-------------------------"); //NOI18N
-                }
-                //Perform all queries in single op
-                IndexManager.priorityAccess(new IndexManager.Action<Void>() {
-                    @Override
-                    public Void run() throws IOException, InterruptedException {
-                        for (URL url : rootUrls) {
-                            if (canceled) {
-                                return null;
-                            }
-                            final FileObject root = URLMapper.findFileObject(url);
-                            if (root == null) {
-                                continue;
-                            }
+                        if (canceled.get()) {
+                            return null;
+                        }
+                        final FileObject root = URLMapper.findFileObject(url);
+                        if (root == null) {
+                            continue;
+                        }
 
-                            final Project project = FileOwnerQuery.getOwner(root);
-                            final ProjectInformation projectInfo = project == null ?
-                                    null :
-                                    project.getLookup().lookup(ProjectInformation.class);   //Intentionally does not use ProjectUtils.getInformation() it does project icon annotation which is expensive
-                            final ClassIndexImpl impl = manager.getUsagesQuery(root.toURL(), true);
-                            if (impl != null) {
+                        final ClassIndexImpl impl = manager.getUsagesQuery(root.toURL(), true);
+                        if (impl != null) {
+                            handler.runRoot(root, impl, () -> {
                                 final Map<ElementHandle<TypeElement>,Set<String>> r = new HashMap<>();
                                 impl.getDeclaredElements(ident, kind, DocumentUtil.typeElementConvertor(),r);
                                 if (!r.isEmpty()) {
@@ -228,34 +274,30 @@
                                         final ElementHandle<TypeElement> owner = p.getKey();
                                         for (String symbol : p.getValue()) {
                                             if (matchesRestrictions(owner.getQualifiedName(), symbol, restriction, caseSensitive)) {
-                                                final AsyncJavaSymbolDescriptor d = new AsyncJavaSymbolDescriptor(
-                                                        projectInfo,
-                                                        root,
-                                                        impl,
-                                                        owner,
-                                                        symbol,
-                                                        caseSensitive);
-                                                result.addResult(d);
-                                                if (cache != null) {
-                                                    cache.offer(d);
-                                                }
+                                                handler.handleResult(owner, symbol, caseSensitive);
                                             }
                                         }
                                     }
                                 }
-                            }
+                            });
                         }
-                        return null;
                     }
-                });
-            } catch (IOException ioe) {
-                Exceptions.printStackTrace(ioe);
-            }
-            catch (InterruptedException ie) {
-                return;
-            }
-        } finally {
-            clearCancel();
+                    return null;
+                }
+            });
+        } catch (IOException ioe) {
+            Exceptions.printStackTrace(ioe);
+        } catch (InterruptedException ie) {
+            //ignore
+        }
+    }
+
+    public interface ResultHandler {
+        public void setHighlightText(String text);
+        public void runRoot(FileObject root, ClassIndexImpl ci, Exec exec) throws IOException, InterruptedException;
+        public void handleResult(@NonNull ElementHandle<TypeElement> owner, @NonNull String ident, boolean caseSensitive);
+        public interface Exec {
+            public void run() throws IOException, InterruptedException;
         }
     }
 
@@ -317,7 +359,7 @@
     }
 
     @NonNull
-    static Pair<String,String> getDisplayName (
+    public static Pair<String,String> getDisplayName (
             @NonNull final Element e,
             @NonNull final Element enclosingElement) {
         assert e != null;
@@ -483,7 +525,7 @@
 
     @Override
     public void cancel() {
-        canceled = true;
+        canceled.set(true);
     }
 
     @Override
@@ -493,7 +535,7 @@
     }
 
     private void clearCancel() {
-        canceled = false;
+        canceled.set(false);
     }
 
     private static final class Cache {
@@ -519,7 +561,7 @@
             }
         }
 
-        void offer(@NonNull final AsyncJavaSymbolDescriptor d) {
+        void offer(@NonNull final SymbolDescriptor d) {
             descriptors.add(d);
         }
 
diff --git a/java/maven/nbproject/project.xml b/java/maven/nbproject/project.xml
index f0a6100..231a621 100644
--- a/java/maven/nbproject/project.xml
+++ b/java/maven/nbproject/project.xml
@@ -80,15 +80,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.40</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.netbeans.api.progress.nb</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>1.40</specification-version>
+                        <specification-version>1.57.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/java/maven/src/org/netbeans/modules/maven/actions/CreateLibraryAction.java b/java/maven/src/org/netbeans/modules/maven/actions/CreateLibraryAction.java
index 3daa166..19f5ae6 100644
--- a/java/maven/src/org/netbeans/modules/maven/actions/CreateLibraryAction.java
+++ b/java/maven/src/org/netbeans/modules/maven/actions/CreateLibraryAction.java
@@ -44,7 +44,6 @@
 import org.netbeans.api.annotations.common.CheckForNull;
 import org.netbeans.api.annotations.common.StaticResource;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.api.project.libraries.LibrariesCustomizer;
 import org.netbeans.api.project.libraries.Library;
 import org.netbeans.api.project.libraries.LibraryManager;
@@ -145,7 +144,7 @@
     })
     @SuppressWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") // baseFolder.mkdirs; will throw IOE later from getJarUri
     private static @CheckForNull Library createLibrary(LibraryManager libraryManager, String libraryName, List<Artifact> includeArtifacts, boolean allSourceAndJavadoc, MavenProject project, String copyTo) {
-        ProgressHandle handle = ProgressHandleFactory.createHandle(MSG_Create_Library(),
+        ProgressHandle handle = ProgressHandle.createHandle(MSG_Create_Library(),
                 ProgressTransferListener.cancellable());
         int count = includeArtifacts.size() * (allSourceAndJavadoc ? 3 : 1) + 5;
         handle.start(count);
diff --git a/java/maven/src/org/netbeans/modules/maven/api/NbMavenProject.java b/java/maven/src/org/netbeans/modules/maven/api/NbMavenProject.java
index 8fdf544..95235be 100644
--- a/java/maven/src/org/netbeans/modules/maven/api/NbMavenProject.java
+++ b/java/maven/src/org/netbeans/modules/maven/api/NbMavenProject.java
@@ -40,8 +40,8 @@
 import org.apache.maven.model.building.ModelBuildingException;
 import org.apache.maven.project.MavenProject;
 import org.netbeans.api.annotations.common.NonNull;
-import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
 import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
+import org.netbeans.api.progress.aggregate.BasicAggregateProgressFactory;
 import org.netbeans.api.progress.aggregate.ProgressContributor;
 import org.netbeans.api.project.Project;
 import org.netbeans.modules.maven.NbMavenProjectImpl;
@@ -63,7 +63,6 @@
 import org.openide.util.Lookup;
 import org.openide.util.NbBundle.Messages;
 import org.openide.util.RequestProcessor;
-import org.openide.util.Task;
 import org.openide.util.Utilities;
 
 /**
@@ -206,9 +205,9 @@
                         return;
                     }
                     MavenEmbedder online = EmbedderFactory.getOnlineEmbedder();
-                    AggregateProgressHandle hndl = AggregateProgressFactory.createHandle(Progress_Download(),
+                    AggregateProgressHandle hndl = BasicAggregateProgressFactory.createHandle(Progress_Download(),
                             new ProgressContributor[] {
-                                AggregateProgressFactory.createProgressContributor("zaloha") },  //NOI18N
+                                BasicAggregateProgressFactory.createProgressContributor("zaloha") },  //NOI18N
                             ProgressTransferListener.cancellable(), null);
 
                     boolean ok = true;
@@ -440,10 +439,10 @@
                 Set<Artifact> arts = project.getOriginalMavenProject().getArtifacts();
                 ProgressContributor[] contribs = new ProgressContributor[arts.size()];
                 for (int i = 0; i < arts.size(); i++) {
-                    contribs[i] = AggregateProgressFactory.createProgressContributor("multi-" + i); //NOI18N
+                    contribs[i] = BasicAggregateProgressFactory.createProgressContributor("multi-" + i); //NOI18N
                 }
                 String label = javadoc ? Progress_Javadoc() : Progress_Source();
-                AggregateProgressHandle handle = AggregateProgressFactory.createHandle(label,
+                AggregateProgressHandle handle = BasicAggregateProgressFactory.createHandle(label,
                         contribs, ProgressTransferListener.cancellable(), null);
                 handle.start();
                 try {
diff --git a/java/maven/src/org/netbeans/modules/maven/execute/AbstractMavenExecutor.java b/java/maven/src/org/netbeans/modules/maven/execute/AbstractMavenExecutor.java
index 79fbdc8..1115a40 100644
--- a/java/maven/src/org/netbeans/modules/maven/execute/AbstractMavenExecutor.java
+++ b/java/maven/src/org/netbeans/modules/maven/execute/AbstractMavenExecutor.java
@@ -37,7 +37,6 @@
 import org.netbeans.api.annotations.common.NullAllowed;
 import org.netbeans.api.options.OptionsDisplayer;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.api.project.Project;
 import org.netbeans.modules.maven.api.FileUtilities;
 import org.netbeans.modules.maven.api.NbMavenProject;
@@ -294,7 +293,7 @@
                 return;
             }
             final AtomicReference<Thread> t = new AtomicReference<Thread>();
-            final ProgressHandle handle = ProgressHandleFactory.createHandle(ResumeAction_scanning(), new Cancellable() {
+            final ProgressHandle handle = ProgressHandle.createHandle(ResumeAction_scanning(), new Cancellable() {
                 @Override public boolean cancel() {
                     Thread _t = t.get();
                     if (_t != null) {
diff --git a/java/maven/src/org/netbeans/modules/maven/execute/MavenCommandLineExecutor.java b/java/maven/src/org/netbeans/modules/maven/execute/MavenCommandLineExecutor.java
index e920702..b3aad93 100644
--- a/java/maven/src/org/netbeans/modules/maven/execute/MavenCommandLineExecutor.java
+++ b/java/maven/src/org/netbeans/modules/maven/execute/MavenCommandLineExecutor.java
@@ -57,7 +57,6 @@
 import org.netbeans.api.extexecution.base.Processes;
 import org.netbeans.api.java.platform.JavaPlatform;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.api.project.Project;
 import org.netbeans.modules.maven.NbMavenProjectImpl;
 import org.netbeans.modules.maven.api.Constants;
@@ -203,8 +202,7 @@
         int executionresult = -10;
         final InputOutput ioput = getInputOutput();
         
-        final ProgressHandle handle = ProgressHandleFactory.createHandle(clonedConfig.getTaskDisplayName(), this, new AbstractAction() {
-
+        final ProgressHandle handle = ProgressHandle.createHandle(clonedConfig.getTaskDisplayName(), this, new AbstractAction() {
             @Override
             public void actionPerformed(ActionEvent e) {
                 ioput.select();
diff --git a/java/maven/src/org/netbeans/modules/maven/execute/ui/DebugPluginSourceAction.java b/java/maven/src/org/netbeans/modules/maven/execute/ui/DebugPluginSourceAction.java
index 02a81b4..2332f66 100644
--- a/java/maven/src/org/netbeans/modules/maven/execute/ui/DebugPluginSourceAction.java
+++ b/java/maven/src/org/netbeans/modules/maven/execute/ui/DebugPluginSourceAction.java
@@ -64,7 +64,7 @@
     @NbBundle.Messages(value = "TIT_DEBUG_Plugin=Debugging Plugin Mojo")
     public void actionPerformed(ActionEvent e) {
         final AtomicBoolean cancel = new AtomicBoolean();
-        org.netbeans.api.progress.ProgressUtils.runOffEventDispatchThread(new Runnable() {
+        org.netbeans.api.progress.BaseProgressUtils.runOffEventDispatchThread(new Runnable() {
             @Override
             public void run() {
                 doLoad(cancel);
diff --git a/java/maven/src/org/netbeans/modules/maven/execute/ui/GotoPluginSourceAction.java b/java/maven/src/org/netbeans/modules/maven/execute/ui/GotoPluginSourceAction.java
index 04b53ac..1a8fb04 100644
--- a/java/maven/src/org/netbeans/modules/maven/execute/ui/GotoPluginSourceAction.java
+++ b/java/maven/src/org/netbeans/modules/maven/execute/ui/GotoPluginSourceAction.java
@@ -25,8 +25,8 @@
 import org.apache.maven.artifact.Artifact;
 import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
 import org.apache.maven.artifact.resolver.ArtifactResolutionException;
-import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
 import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
+import org.netbeans.api.progress.aggregate.BasicAggregateProgressFactory;
 import org.netbeans.api.progress.aggregate.ProgressContributor;
 import org.netbeans.api.project.Project;
 import org.netbeans.modules.maven.api.NbMavenProject;
@@ -63,7 +63,7 @@
     @NbBundle.Messages(value = "TIT_GOTO_Plugin=Opening Plugin Mojo Sources")
     public void actionPerformed(ActionEvent e) {
         final AtomicBoolean cancel = new AtomicBoolean();
-        org.netbeans.api.progress.ProgressUtils.runOffEventDispatchThread(new Runnable() {
+        org.netbeans.api.progress.BaseProgressUtils.runOffEventDispatchThread(new Runnable() {
             @Override
             public void run() {
                 doLoad(cancel);
@@ -77,8 +77,8 @@
         Project prj = config.getProject();
         if (prj != null) {
             //todo what about build without project.. it's just create archetype one though..
-            ProgressContributor contributor = AggregateProgressFactory.createProgressContributor("multi-1");
-            AggregateProgressHandle handle = AggregateProgressFactory.createHandle("Downloading plugin sources", new ProgressContributor[]{contributor}, ProgressTransferListener.cancellable(), null);
+            ProgressContributor contributor = BasicAggregateProgressFactory.createProgressContributor("multi-1");
+            AggregateProgressHandle handle = BasicAggregateProgressFactory.createHandle("Downloading plugin sources", new ProgressContributor[]{contributor}, ProgressTransferListener.cancellable(), null);
             handle.start();
             try {
                 ProgressTransferListener.setAggregateHandle(handle);
diff --git a/java/maven/src/org/netbeans/modules/maven/newproject/BasicPanelVisual.java b/java/maven/src/org/netbeans/modules/maven/newproject/BasicPanelVisual.java
index 2f5b873..d06c3f5 100644
--- a/java/maven/src/org/netbeans/modules/maven/newproject/BasicPanelVisual.java
+++ b/java/maven/src/org/netbeans/modules/maven/newproject/BasicPanelVisual.java
@@ -43,8 +43,8 @@
 import org.apache.maven.artifact.versioning.ArtifactVersion;
 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
 import org.netbeans.api.options.OptionsDisplayer;
-import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
 import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
+import org.netbeans.api.progress.aggregate.BasicAggregateProgressFactory;
 import org.netbeans.api.progress.aggregate.ProgressContributor;
 import org.netbeans.modules.maven.api.MavenValidators;
 import org.netbeans.modules.maven.api.archetype.Archetype;
@@ -713,9 +713,9 @@
     @Messages("Handle_Download=Downloading Archetype")
     private Artifact downloadArchetype(Archetype arch) throws ArtifactResolutionException, ArtifactNotFoundException {
         
-        AggregateProgressHandle hndl = AggregateProgressFactory.createHandle(Handle_Download(),
+        AggregateProgressHandle hndl = BasicAggregateProgressFactory.createHandle(Handle_Download(),
                 new ProgressContributor[] {
-                    AggregateProgressFactory.createProgressContributor("zaloha") },  //NOI18N
+                    BasicAggregateProgressFactory.createProgressContributor("zaloha") },  //NOI18N
                 ProgressTransferListener.cancellable(), null);
         synchronized (HANDLE_LOCK) {
            handle = hndl;
diff --git a/java/maven/src/org/netbeans/modules/maven/nodes/DependenciesNode.java b/java/maven/src/org/netbeans/modules/maven/nodes/DependenciesNode.java
index 62cdb40..f30cce6 100644
--- a/java/maven/src/org/netbeans/modules/maven/nodes/DependenciesNode.java
+++ b/java/maven/src/org/netbeans/modules/maven/nodes/DependenciesNode.java
@@ -50,8 +50,8 @@
 import org.apache.maven.project.MavenProject;
 import org.netbeans.api.annotations.common.StaticResource;
 import org.netbeans.api.java.source.SourceUtils;
-import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
 import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
+import org.netbeans.api.progress.aggregate.BasicAggregateProgressFactory;
 import org.netbeans.api.progress.aggregate.ProgressContributor;
 import org.netbeans.api.project.Project;
 import org.netbeans.modules.maven.NbMavenProjectImpl;
@@ -406,10 +406,10 @@
                     Node[] nds = getChildren().getNodes(true);
                     ProgressContributor[] contribs = new ProgressContributor[nds.length];
                     for (int i = 0; i < nds.length; i++) {
-                        contribs[i] = AggregateProgressFactory.createProgressContributor("multi-" + i); //NOI18N
+                        contribs[i] = BasicAggregateProgressFactory.createProgressContributor("multi-" + i); //NOI18N
                     }
                     String label = javadoc ? Progress_Javadoc() : Progress_Source();
-                    AggregateProgressHandle handle = AggregateProgressFactory.createHandle(label, 
+                    AggregateProgressHandle handle = BasicAggregateProgressFactory.createHandle(label, 
                             contribs, ProgressTransferListener.cancellable(), null);
                     handle.start();
                     try {
diff --git a/java/maven/src/org/netbeans/modules/maven/nodes/DependencyNode.java b/java/maven/src/org/netbeans/modules/maven/nodes/DependencyNode.java
index 9e19210..80fcd2d 100644
--- a/java/maven/src/org/netbeans/modules/maven/nodes/DependencyNode.java
+++ b/java/maven/src/org/netbeans/modules/maven/nodes/DependencyNode.java
@@ -75,8 +75,8 @@
 import org.netbeans.api.annotations.common.NonNull;
 import org.netbeans.api.annotations.common.NullAllowed;
 import org.netbeans.api.java.queries.JavadocForBinaryQuery;
-import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
 import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
+import org.netbeans.api.progress.aggregate.BasicAggregateProgressFactory;
 import org.netbeans.api.progress.aggregate.ProgressContributor;
 import org.netbeans.api.project.FileOwnerQuery;
 import org.netbeans.api.project.Project;
@@ -1055,16 +1055,16 @@
             }
             RP.post(new Runnable() {
                 public @Override void run() {
-                    ProgressContributor contributor =AggregateProgressFactory.createProgressContributor("multi-1");
+                    ProgressContributor contributor = BasicAggregateProgressFactory.createProgressContributor("multi-1");
                    
                     String label = javadoc ? Progress_Javadoc() : Progress_Source();
-                    AggregateProgressHandle handle = AggregateProgressFactory.createHandle(label, 
+                    AggregateProgressHandle handle = BasicAggregateProgressFactory.createHandle(label, 
                             new ProgressContributor [] {contributor}, ProgressTransferListener.cancellable(), null);
                     handle.start();
                     try {
                         ProgressTransferListener.setAggregateHandle(handle);
                         for (Data data : actionContext.lookupAll(Data.class)) {
-                            ProgressContributor contributor2 = AggregateProgressFactory.createProgressContributor("multi-1");
+                            ProgressContributor contributor2 = BasicAggregateProgressFactory.createProgressContributor("multi-1");
                             handle.addContributor(contributor2);
                             if (javadoc && !data.hasJavadocInRepository()) {
                                 data.getNode().downloadJavadocSources(contributor2, javadoc);
diff --git a/java/maven/src/org/netbeans/modules/maven/operations/RenameProjectPanel.java b/java/maven/src/org/netbeans/modules/maven/operations/RenameProjectPanel.java
index 534fa22..febe583 100644
--- a/java/maven/src/org/netbeans/modules/maven/operations/RenameProjectPanel.java
+++ b/java/maven/src/org/netbeans/modules/maven/operations/RenameProjectPanel.java
@@ -27,9 +27,7 @@
 import javax.swing.JCheckBox;
 import javax.swing.SwingUtilities;
 import org.apache.maven.project.MavenProject;
-import org.apache.maven.project.ProjectBuildingException;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.api.project.Project;
 import org.netbeans.api.project.ProjectManager;
 import org.netbeans.api.project.ui.OpenProjects;
@@ -265,7 +263,7 @@
                 FileObject pomFO = project.getProjectDirectory().getFileObject("pom.xml"); //NOI18N
                 Utilities.performPOMModelOperations(pomFO, opers);
                 if (folder) {
-                    final ProgressHandle handle = ProgressHandleFactory.createHandle(RenameProject());
+                    final ProgressHandle handle = ProgressHandle.createHandle(RenameProject());
                     //#76559
                     handle.start(MAX_WORK);
                     try {
diff --git a/java/maven/src/org/netbeans/modules/maven/queries/MavenSourceJavadocAttacher.java b/java/maven/src/org/netbeans/modules/maven/queries/MavenSourceJavadocAttacher.java
index 2baa661..1fa51cc 100644
--- a/java/maven/src/org/netbeans/modules/maven/queries/MavenSourceJavadocAttacher.java
+++ b/java/maven/src/org/netbeans/modules/maven/queries/MavenSourceJavadocAttacher.java
@@ -32,8 +32,8 @@
 import org.apache.maven.artifact.repository.ArtifactRepository;
 import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
 import org.netbeans.api.annotations.common.NonNull;
-import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
 import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
+import org.netbeans.api.progress.aggregate.BasicAggregateProgressFactory;
 import org.netbeans.api.progress.aggregate.ProgressContributor;
 import org.netbeans.modules.maven.embedder.EmbedderFactory;
 import org.netbeans.modules.maven.embedder.MavenEmbedder;
@@ -112,8 +112,8 @@
             return Collections.emptyList();
         }
 
-        AggregateProgressHandle hndl = AggregateProgressFactory.createHandle(Bundle.attaching(art.getId()),
-                new ProgressContributor[]{AggregateProgressFactory.createProgressContributor("attach")},
+        AggregateProgressHandle hndl = BasicAggregateProgressFactory.createHandle(Bundle.attaching(art.getId()),
+                new ProgressContributor[]{ BasicAggregateProgressFactory.createProgressContributor("attach")},
                 ProgressTransferListener.cancellable(), null);
         ProgressTransferListener.setAggregateHandle(hndl);
         try {
diff --git a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaRefactoringPlugin.java b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaRefactoringPlugin.java
index fb9d3b6..ff0be12 100644
--- a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaRefactoringPlugin.java
+++ b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaRefactoringPlugin.java
@@ -37,6 +37,7 @@
 import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils;
 import org.netbeans.modules.refactoring.java.plugins.FindVisitor;
 import org.netbeans.modules.refactoring.java.plugins.JavaPluginUtils;
+import org.netbeans.modules.refactoring.java.spi.hooks.JavaModificationResult;
 import org.netbeans.modules.refactoring.spi.ProgressProviderAdapter;
 import org.netbeans.modules.refactoring.spi.RefactoringCommit;
 import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
diff --git a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaModificationResult.java b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/hooks/JavaModificationResult.java
similarity index 84%
rename from java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaModificationResult.java
rename to java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/hooks/JavaModificationResult.java
index e4d0618..f4b0b4a 100644
--- a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaModificationResult.java
+++ b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/hooks/JavaModificationResult.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.netbeans.modules.refactoring.java.spi;
+package org.netbeans.modules.refactoring.java.spi.hooks;
 
 import java.io.File;
 import java.io.IOException;
@@ -28,11 +28,11 @@
  *
  * @author Jan Becicka
  */
-class JavaModificationResult implements ModificationResult {
+public class JavaModificationResult implements ModificationResult {
 
-    private org.netbeans.api.java.source.ModificationResult delegate;
+    public final org.netbeans.api.java.source.ModificationResult delegate;
     
-    JavaModificationResult(org.netbeans.api.java.source.ModificationResult r) {
+    public JavaModificationResult(org.netbeans.api.java.source.ModificationResult r) {
         this.delegate = r;
     }
 
diff --git a/platform/api.progress.nb/apichanges.xml b/platform/api.progress.nb/apichanges.xml
index abaf699..37f5b3a 100644
--- a/platform/api.progress.nb/apichanges.xml
+++ b/platform/api.progress.nb/apichanges.xml
@@ -83,6 +83,20 @@
 
     <!-- ACTUAL CHANGES BEGIN HERE: -->
   <changes>
+      <change id="progressAction">
+          <api name="ProgressSwing"/>
+          <summary>
+              Added support for non-visual default actions for ProgressHandles in headless mode.
+          </summary>
+          <date day="5" month="1" year="2021"/>
+          <author login="sdedic"/>
+          <compatibility binary="compatible" source="compatible" addition="yes" deprecation="yes"/>
+          <description>
+              The default action on <a href="@TOP@/org/netbeans/modules/progress/api/ProgressHandle.html">ProgressHandle</a> is sometimes non-visual: open a file, focus a result, 
+              display an output window associated with the task, etc. Abstract action can be used now with the basic Progress API without
+              dependency on NB- or Swing- specific APIs.
+          </description>
+      </change>
       <change id="swingSplit">
           <api name="ProgressSwing"/>
           <summary>
diff --git a/platform/api.progress.nb/manifest.mf b/platform/api.progress.nb/manifest.mf
index f47dc12..a41f749 100644
--- a/platform/api.progress.nb/manifest.mf
+++ b/platform/api.progress.nb/manifest.mf
@@ -2,5 +2,5 @@
 AutoUpdate-Show-In-Client: false
 OpenIDE-Module: org.netbeans.api.progress.nb
 OpenIDE-Module-Localizing-Bundle: org/netbeans/api/progress/nb/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.57
+OpenIDE-Module-Specification-Version: 1.57.1
 
diff --git a/platform/api.progress.nb/nbproject/project.properties b/platform/api.progress.nb/nbproject/project.properties
index 7098a75..100ba03 100644
--- a/platform/api.progress.nb/nbproject/project.properties
+++ b/platform/api.progress.nb/nbproject/project.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-javac.source=1.6
+javac.source=1.8
 javac.compilerargs=-Xlint -Xlint:-serial
 javadoc.apichanges=${basedir}/apichanges.xml
 javadoc.arch=${basedir}/arch.xml
diff --git a/platform/api.progress.nb/nbproject/project.xml b/platform/api.progress.nb/nbproject/project.xml
index ea18036..959adab 100644
--- a/platform/api.progress.nb/nbproject/project.xml
+++ b/platform/api.progress.nb/nbproject/project.xml
@@ -26,14 +26,6 @@
             <code-name-base>org.netbeans.api.progress.nb</code-name-base>
             <module-dependencies>
                 <dependency>
-                    <code-name-base>org.openide.util.ui</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>9.3</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
                     <code-name-base>org.openide.util</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -63,7 +55,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.45</specification-version>
+                        <specification-version>1.57.1</specification-version>
                     </run-dependency>
                 </dependency>
             </module-dependencies>
@@ -75,6 +67,11 @@
                         <compile-dependency/>
                     </test-dependency>
                     <test-dependency>
+                        <code-name-base>org.openide.util.lookup</code-name-base>
+                        <compile-dependency/>
+                        <test/>
+                    </test-dependency>
+                    <test-dependency>
                         <code-name-base>org.netbeans.modules.nbjunit</code-name-base>
                         <recursive/>
                         <compile-dependency/>
diff --git a/platform/api.progress.nb/src/org/netbeans/api/progress/ProgressHandleFactory.java b/platform/api.progress.nb/src/org/netbeans/api/progress/ProgressHandleFactory.java
index b8d4a2f..ea7d9f4 100644
--- a/platform/api.progress.nb/src/org/netbeans/api/progress/ProgressHandleFactory.java
+++ b/platform/api.progress.nb/src/org/netbeans/api/progress/ProgressHandleFactory.java
@@ -22,8 +22,16 @@
 import javax.swing.Action;
 import javax.swing.JComponent;
 import javax.swing.JLabel;
+import org.netbeans.modules.progress.spi.ExtractedProgressUIWorker;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.ProgressEvent;
+import org.netbeans.modules.progress.spi.ProgressUIWorkerProvider;
+import org.netbeans.modules.progress.spi.SwingController;
 import org.netbeans.modules.progress.spi.UIInternalHandle;
+import org.netbeans.progress.module.TrivialProgressUIWorkerProvider;
+import org.netbeans.progress.module.UIInternalHandleAccessor;
 import org.openide.util.Cancellable;
+import org.openide.util.Lookup;
 
 /**
  * Factory to create various ProgressHandle instances that allow long lasting
@@ -65,41 +73,62 @@
     }
 
     /**
-     * Create a progress ui handle for a long lasting task.
+     * Create a progress ui handle for a long lasting task. Since {@code 1.59}, this method was
+     * migrated to {@link ProgressHandle basic Progress API}.
      * @param linkOutput an <code>Action</code> instance that links the running task in the progress bar
      *                   to an output of the task. The action is assumed to open the apropriate component with the task's output.
      * @param displayName to be shown in the progress UI
      * @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+     * @deprecated Please use {@link ProgressHandle#createHandle(java.lang.String, org.openide.util.Cancellable, javax.swing.Action)}
      */
+    @Deprecated
     public static ProgressHandle createHandle(String displayName, Action linkOutput) {
         return createHandle(displayName, null, linkOutput);
     }
     
     /**
-     * Create a progress ui handle for a long lasting task.
+     * Create a progress UI handle for a long lasting task. Since {@code 1.59}, this method was
+     * migrated to {@link ProgressHandle basic Progress API}. This implementation delegates to
+     * {@link ProgressHandle#createHandle(java.lang.String, org.openide.util.Cancellable, javax.swing.Action)}
+     * to enable smooth transition of older Progress API clients.
      * @param allowToCancel either null, if the task cannot be cancelled or 
      *          an instance of {@link org.openide.util.Cancellable} that will be called when user 
      *          triggers cancel of the task.
      * @param linkOutput an <code>Action</code> instance that links the running task in the progress bar
-     *                   to an output of the task. The action is assumed to open the apropriate component with the task's output.
+     *                   to an output of the task. The action is assumed to open the appropriate component with the task's output.
      * @param displayName to be shown in the progress UI
      * @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+     * @deprecated Please use {@link ProgressHandle#createHandle(java.lang.String, org.openide.util.Cancellable, javax.swing.Action)}
      */
+    @Deprecated
     public static ProgressHandle createHandle(String displayName, Cancellable allowToCancel, Action linkOutput) {
+        return ProgressHandle.createHandle(displayName, allowToCancel, linkOutput);
+    }
+    
+    /**
+     * Create a progress UI handle for a long lasting task.
+     * This call creates a Swing-based implementation. Use only when the Handle is directly used in
+     * Swing UIs. These handles will never work in other presenters.
+     * @param allowToCancel either null, if the task cannot be cancelled or 
+     *          an instance of {@link org.openide.util.Cancellable} that will be called when user 
+     *          triggers cancel of the task.
+     * @param linkOutput an <code>Action</code> instance that links the running task in the progress bar
+     *                   to an output of the task. The action is assumed to open the appropriate component with the task's output.
+     * @param displayName to be shown in the progress UI
+     * @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+     * @since 1.59
+     */
+    public static ProgressHandle createUIHandle(String displayName, Cancellable allowToCancel, Action linkOutput) {
         return new UIInternalHandle(displayName, allowToCancel, true, linkOutput).createProgressHandle();
     }
     
-    private static UIInternalHandle ih(ProgressHandle h) {
-        return (UIInternalHandle)h.getInternalHandle();
-    }
-
     /**
      * Get the progress bar component for use in custom dialogs, the task won't 
      * show in the progress bar anymore.
      * @return the component to use in custom UI.
      */
     public static JComponent createProgressComponent(ProgressHandle handle) {
-        return ih(handle).extractComponent();
+        return ihextract(handle).getProgressComponent();
     }
 
     /**
@@ -109,7 +138,7 @@
      * @since org.netbeans.api.progress 1.8
      */
     public static JLabel createMainLabelComponent(ProgressHandle handle) {
-        return ih(handle).extractMainLabel();
+        return ihextract(handle).getMainLabelComponent();
     }
     
     /**
@@ -119,15 +148,18 @@
      * @since org.netbeans.api.progress 1.8
      */
     public static JLabel createDetailLabelComponent(ProgressHandle handle) {
-        return ih(handle).extractDetailLabel();
+        return ihextract(handle).getDetailLabelComponent();
     }
     
     /**
      * Create a handle for a long lasting task that is not triggered by explicit user action.
      * Such tasks have lower priority in the UI.
+     * Since {@code 1.59}, the functionality moves to basic {@link ProgressHandle Progress API}; this method is retained for smooth transition of older API clients.
      * @param displayName to be shown in the progress UI
      * @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+     * @deprecated Use {@link ProgressHandle#createSystemHandle(java.lang.String, org.openide.util.Cancellable)}.
      */
+    @Deprecated
     public static ProgressHandle createSystemHandle(String displayName) {
         return createSystemHandle(displayName, null, null);
     }
@@ -135,18 +167,22 @@
     /**
      * Create a cancelable handle for a task that is not triggered by explicit user action.
      * Such tasks have lower priority in the UI.
+     * Since {@code 1.59}, the functionality moves to basic {@link ProgressHandle Progress API}; this method is retained for smooth transition of older API clients.
      * @param displayName to be shown in the progress UI
      * @param allowToCancel either null, if the task cannot be cancelled or 
      *          an instance of {@link org.openide.util.Cancellable} that will be called when user 
      *          triggers cancel of the task.
      * @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+     * @deprecated Use {@link ProgressHandle#createSystemHandle(java.lang.String, org.openide.util.Cancellable)}.
      */
     public static ProgressHandle createSystemHandle(String displayName, Cancellable allowToCancel) {
         return createSystemHandle(displayName, allowToCancel, null);
     }
     
     /**
-     * Create a progress ui handle for a task that is not triggered by explicit user action.
+     * Create a progress UI handle for a task that is not triggered by explicit user action.
+     * Starting from {@code 1.59}, this method is fully superseded by {@link ProgressHandle#createSystemHandle(java.lang.String, org.openide.util.Cancellable, javax.swing.Action)}.
+     * Since {@code 1.59}, the functionality moves to basic {@link ProgressHandle Progress API}; this method is retained for smooth transition of older API clients.
      * @param allowToCancel either null, if the task cannot be cancelled or 
      *          an instance of {@link org.openide.util.Cancellable} that will be called when user 
      *          triggers cancel of the task.
@@ -154,9 +190,138 @@
      *                   to an output of the task. The action is assumed to open the apropriate component with the task's output.
      * @param displayName to be shown in the progress UI
      * @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
-     *
+     * @deprecated Please use {@link ProgressHandle#createSystemHandle(java.lang.String, org.openide.util.Cancellable, javax.swing.Action)}
+     * @Deprecated
      */
     public static ProgressHandle createSystemHandle(String displayName, Cancellable allowToCancel, Action linkOutput) {
+        return ProgressHandle.createSystemHandle(displayName, allowToCancel, linkOutput);
+    }    
+
+    /**
+     * Create a progress UI handle for a task that is not triggered by explicit user action.
+     * This call creates a Swing-based implementation. Use only when the Handle is directly used in
+     * Swing UIs. These handles will never work in other presenters.
+     * @param allowToCancel either null, if the task cannot be cancelled or 
+     *          an instance of {@link org.openide.util.Cancellable} that will be called when user 
+     *          triggers cancel of the task.
+     * @param linkOutput an <code>Action</code> instance that links the running task in the progress bar
+     *                   to an output of the task. The action is assumed to open the apropriate component with the task's output.
+     * @param displayName to be shown in the progress UI
+     * @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+     * @since 1.59
+     */
+    public static ProgressHandle createSystemUIHandle(String displayName, Cancellable allowToCancel, Action linkOutput) {
         return new UIInternalHandle(displayName, allowToCancel, false, linkOutput).createProgressHandle();
     }    
+
+    private static ProgressUIWorkerProvider TRIVIAL_PROVIDER = new TrivialProgressUIWorkerProvider();
+    
+    private static ExtractedProgressUIWorker ihextract(ProgressHandle h) {
+        InternalHandle ih = h.getInternalHandle();
+        if (ih instanceof UIInternalHandle) {
+            return new UIHandleExtractor((UIInternalHandle)ih);
+        } else {
+            // fallback for non-UIInternalHandles. Let the environment to create
+            // a suitable component representation.
+            ProgressUIWorkerProvider prov = Lookup.getDefault().lookup(ProgressUIWorkerProvider.class);
+            if (prov == null) {
+                prov = TRIVIAL_PROVIDER;
+            }
+            ExtractedProgressUIWorker worker = prov.extractProgressWorker(ih);
+            if (worker != null) {
+                return new ForeignExtractor(ih, worker);
+            } else {
+                return null;
+            }
+        }
+    }
+    
+    /**
+     * Creates UI components for InternalHandles that are not UI ones. Even though the
+     * handle is not implemented in the standard way, SWing JComponents can be created for it;
+     * the handle can be also marked as {@link InternalHandle#isCustomPlaced()}, so potential displayed
+     * can check and remove it from the originally intended display.
+     * <p>
+     * This path is mainly used if an alternate ProgressHandle display (@link ProgressEnvironment} implementation) is 
+     * in place, but GUI code still wants to embed Swing components for the handle in the UI.
+     */
+    private static class ForeignExtractor implements ExtractedProgressUIWorker {
+        private final InternalHandle ih;
+        private final ExtractedProgressUIWorker del;
+
+        public ForeignExtractor(InternalHandle ih, ExtractedProgressUIWorker del) {
+            this.del = del;
+            this.ih = ih;
+        }
+        
+        void customPlaced() {
+            boolean wasCustomPlaced = ih.isCustomPlaced();
+            UIInternalHandleAccessor acc = UIInternalHandleAccessor.instance();
+            acc.markCustomPlaced(ih);
+            if (!wasCustomPlaced) {
+                acc.setController(ih, new SwingController(del));
+            }
+        }
+
+        @Override
+        public JComponent getProgressComponent() {
+            customPlaced();
+            return del.getProgressComponent();
+        }
+
+        @Override
+        public JLabel getMainLabelComponent() {
+            customPlaced();
+            return del.getMainLabelComponent();
+        }
+
+        @Override
+        public JLabel getDetailLabelComponent() {
+            customPlaced();
+            return del.getDetailLabelComponent();
+        }
+
+        @Override
+        public void processProgressEvent(ProgressEvent event) {
+            del.processProgressEvent(event);
+        }
+
+        @Override
+        public void processSelectedProgressEvent(ProgressEvent event) {
+            del.processSelectedProgressEvent(event);
+        }
+    }
+    
+    private static class UIHandleExtractor implements ExtractedProgressUIWorker {
+        private final UIInternalHandle uiih;
+
+        public UIHandleExtractor(UIInternalHandle uiih) {
+            this.uiih = uiih;
+        }
+        
+        @Override
+        public JComponent getProgressComponent() {
+            return uiih.extractComponent();
+        }
+
+        @Override
+        public JLabel getMainLabelComponent() {
+            return uiih.extractMainLabel();
+        }
+
+        @Override
+        public JLabel getDetailLabelComponent() {
+            return uiih.extractDetailLabel();
+        }
+
+        @Override
+        public void processProgressEvent(ProgressEvent event) {
+            throw new UnsupportedOperationException("Never called.");
+        }
+
+        @Override
+        public void processSelectedProgressEvent(ProgressEvent event) {
+            throw new UnsupportedOperationException("Never called.");
+        }
+    }
 }
diff --git a/platform/api.progress.nb/src/org/netbeans/api/progress/aggregate/AggregateProgressFactory.java b/platform/api.progress.nb/src/org/netbeans/api/progress/aggregate/AggregateProgressFactory.java
index fcf59ab..7da3e37 100644
--- a/platform/api.progress.nb/src/org/netbeans/api/progress/aggregate/AggregateProgressFactory.java
+++ b/platform/api.progress.nb/src/org/netbeans/api/progress/aggregate/AggregateProgressFactory.java
@@ -22,6 +22,7 @@
 import javax.swing.Action;
 import javax.swing.JComponent;
 import javax.swing.JLabel;
+import org.netbeans.api.progress.ProgressHandle;
 import org.netbeans.api.progress.ProgressHandleFactory;
 import org.openide.util.Cancellable;
 
@@ -47,12 +48,12 @@
      *                   to an output of the task. The action is assumed to open the apropriate component with the task's output.
      * @param displayName to be shown in the progress UI
      * @return an instance of <code>ProgressHandle</code>, initialized but not started.
-     *
+     * @deprecated use {@link BasicAggregateProgressFactory#createHandle(java.lang.String, org.netbeans.api.progress.aggregate.ProgressContributor[], org.openide.util.Cancellable, javax.swing.Action)}.
      */
+    @Deprecated
     public static AggregateProgressHandle createHandle(String displayName, ProgressContributor[] contributors, 
                                                        Cancellable allowToCancel, Action linkOutput) {
-        return doCreateHandle(displayName, contributors, allowToCancel, false,
-                ProgressHandleFactory.createHandle(displayName, allowToCancel, linkOutput));
+        return BasicAggregateProgressFactory.createHandle(displayName, contributors, allowToCancel, linkOutput);
     }
     
     /**
@@ -65,12 +66,12 @@
      *                   to an output of the task. The action is assumed to open the apropriate component with the task's output.
      * @param displayName to be shown in the progress UI
      * @return an instance of <code>ProgressHandle</code>, initialized but not started.
-     *
+     * @deprecated use {@link BasicAggregateProgressFactory#createSystemHandle(java.lang.String, org.netbeans.api.progress.aggregate.ProgressContributor[], org.openide.util.Cancellable, javax.swing.Action)}.
      */
+    @Deprecated
     public static AggregateProgressHandle createSystemHandle(String displayName, ProgressContributor[] contributors, 
                                                        Cancellable allowToCancel, Action linkOutput) {
-        return doCreateHandle(displayName, contributors, allowToCancel, true,
-                ProgressHandleFactory.createSystemHandle(displayName, allowToCancel, linkOutput));
+        return BasicAggregateProgressFactory.createSystemHandle(displayName, contributors, allowToCancel, linkOutput);
     }  
     
     /**
diff --git a/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/ProgressUIWorkerProvider.java b/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/ProgressUIWorkerProvider.java
index e21df17..8d3341e 100644
--- a/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/ProgressUIWorkerProvider.java
+++ b/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/ProgressUIWorkerProvider.java
@@ -29,4 +29,14 @@
     public ProgressUIWorkerWithModel getDefaultWorker();
     
     public ExtractedProgressUIWorker getExtractedComponentWorker();
+
+    /**
+     * Provides an extracted worker instance for the given internal handle.
+     * @param handle internal handle for the worker.
+     * @return the worker instance, possibly {@code null}.
+     * @since 1.59
+     */
+    public default ExtractedProgressUIWorker extractProgressWorker(InternalHandle handle) {
+        return getExtractedComponentWorker();
+    }
 }
diff --git a/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/UIInternalHandle.java b/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/UIInternalHandle.java
index edb7dd9..81201a2 100644
--- a/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/UIInternalHandle.java
+++ b/platform/api.progress.nb/src/org/netbeans/modules/progress/spi/UIInternalHandle.java
@@ -19,13 +19,16 @@
 package org.netbeans.modules.progress.spi;
 
 import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import javax.swing.Action;
 import javax.swing.JComponent;
 import javax.swing.JLabel;
+import org.netbeans.api.progress.ProgressHandle;
 import static org.netbeans.modules.progress.spi.InternalHandle.STATE_INITIALIZED;
 import org.netbeans.progress.module.TrivialProgressUIWorkerProvider;
+import org.netbeans.progress.module.UIInternalHandleAccessor;
 import org.openide.util.Cancellable;
 import org.openide.util.Lookup;
 
@@ -37,12 +40,13 @@
 public final class UIInternalHandle extends InternalHandle {
     private static final Logger LOG = Logger.getLogger(UIInternalHandle.class.getName());
     
-    private final Action viewAction;
+    private ActionListener viewAction;
     private ExtractedProgressUIWorker component;
     private boolean customPlaced1 = false;
     private boolean customPlaced2 = false;
     private boolean customPlaced3 = false;
-
+    private ProgressHandle handle;
+    
     public UIInternalHandle(String displayName, 
                    Cancellable cancel,
                    boolean userInitiated,
@@ -70,6 +74,16 @@
         viewAction.actionPerformed(new ActionEvent(viewAction, ActionEvent.ACTION_PERFORMED, "performView"));
     }
 
+    @Override
+    public boolean requestAction(String actionCommand, Action al) {
+        if (actionCommand != ProgressHandle.ACTION_VIEW) {
+            // no UI atm
+            return false;
+        }
+        viewAction = (Action)al;
+        return true;
+    }
+
     private void createExtractedWorker() {
         if (component == null) {
             ProgressUIWorkerProvider prov = Lookup.getDefault().lookup(ProgressUIWorkerProvider.class);
@@ -77,7 +91,7 @@
                 LOG.log(Level.CONFIG, "Using fallback trivial progress implementation");
                 prov = new TrivialProgressUIWorkerProvider();
             }
-            component = prov.getExtractedComponentWorker();
+            component = prov.extractProgressWorker(this);
             setController(new SwingController(component));
         }
     }
@@ -121,4 +135,18 @@
         return component.getMainLabelComponent();
     }
 
+
+    static {
+        UIInternalHandleAccessor.setInstance(new UIInternalHandleAccessor() {
+            @Override
+            public void setController(InternalHandle h, Controller c) {
+                h.setController(c);
+            }
+
+            @Override
+            public void markCustomPlaced(InternalHandle h) {
+                h.markCustomPlaced();
+            }
+        });
+    }
 }
diff --git a/platform/api.progress.nb/src/org/netbeans/progress/module/UIInternalHandleAccessor.java b/platform/api.progress.nb/src/org/netbeans/progress/module/UIInternalHandleAccessor.java
new file mode 100644
index 0000000..a593278
--- /dev/null
+++ b/platform/api.progress.nb/src/org/netbeans/progress/module/UIInternalHandleAccessor.java
@@ -0,0 +1,51 @@
+/*
+ * 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.progress.module;
+
+import org.netbeans.modules.progress.spi.Controller;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.UIInternalHandle;
+
+/**
+ *
+ * @author sdedic
+ */
+public abstract class UIInternalHandleAccessor {
+    private static UIInternalHandleAccessor INSTANCE;
+    
+    public static void setInstance(UIInternalHandleAccessor acc) {
+        if (INSTANCE != null) {
+            throw new IllegalStateException();
+        }
+        INSTANCE = acc;
+    }
+    
+    public static UIInternalHandleAccessor instance() {
+        return INSTANCE;
+    }
+    
+    public abstract void setController(InternalHandle h, Controller c);
+    
+    public abstract void markCustomPlaced(InternalHandle h);
+    
+    static {
+        // force creation -> accessor is registered.
+        new UIInternalHandle("", null, false, null);
+    }
+}
diff --git a/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/ProgressHandleFactoryTest.java b/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/ProgressHandleFactoryTest.java
index b8b22e4..c37eafb 100644
--- a/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/ProgressHandleFactoryTest.java
+++ b/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/ProgressHandleFactoryTest.java
@@ -20,6 +20,7 @@
 package org.netbeans.api.progress;
 
 import javax.swing.JComponent;
+import javax.swing.JLabel;
 import javax.swing.JProgressBar;
 import javax.swing.SwingUtilities;
 import javax.swing.Timer;
@@ -28,11 +29,14 @@
 import org.netbeans.junit.NbTestCase;
 import org.netbeans.junit.RandomlyFails;
 import org.netbeans.modules.progress.spi.Controller;
+import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.progress.spi.ProgressEnvironment;
 import org.netbeans.modules.progress.spi.UIInternalHandle;
 import org.netbeans.modules.progress.spi.ProgressEvent;
 import org.netbeans.modules.progress.spi.ProgressUIWorker;
 import org.netbeans.modules.progress.spi.SwingController;
 import org.openide.util.Cancellable;
+import org.openide.util.test.MockLookup;
 
 /**
  *
@@ -179,5 +183,60 @@
 
     }
      
+
+    /**
+     * Checks that handles produced by other env than org.netbeans.modules.progress.ui module
+     * can still get some +- suitable Progress components.
+     */
+    public void testUnexpectedInternalHandleExtraction() throws Exception {
+        MockLookup.setLayersAndInstances(new StrangeEnvironment());
+        
+        ProgressHandle handle = ProgressHandle.createHandle("task 1");
+        assertFalse(handle.getInternalHandle() instanceof UIInternalHandle);
+        
+        InternalHandle ih = handle.getInternalHandle();
+        assertFalse(ih.isCustomPlaced());
+        
+        // now attempt to extract a component from it:
+        JLabel l = ProgressHandleFactory.createMainLabelComponent(handle);
+        assertNotNull(l);
+        // the handle changed its placement:
+        assertTrue(ih.isCustomPlaced());
+    }
+
+    /**
+     * Checks semantics of the InternalHandle + extraction after the progress starts.
+     */
+    public void testUnexpectedInternalHandleExtractFailsAfterStart() throws Exception {
+        MockLookup.setLayersAndInstances(new StrangeEnvironment());
+        ProgressHandle handle = ProgressHandle.createHandle("task 1");
+        handle.start(100);
+        
+        // should throw an exception:
+        try {
+            JLabel l = ProgressHandleFactory.createMainLabelComponent(handle);
+            fail("Exppected ISE.");
+        } catch (IllegalStateException ex) {
+            // OK
+        }
+    }
     
+    public class StrangeHandle extends InternalHandle {
+        public StrangeHandle(String displayName, Cancellable cancel, boolean userInitiated) {
+            super(displayName, cancel, userInitiated);
+        }
+    }
+    
+    public class StrangeEnvironment implements ProgressEnvironment {
+
+        @Override
+        public ProgressHandle createHandle(String displayname, Cancellable c, boolean userInit) {
+            return new StrangeHandle(displayname, c, userInit).createProgressHandle();
+        }
+
+        @Override
+        public Controller getController() {
+            return SwingController.getDefault();
+        }
+    }
 }
diff --git a/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/TestProgressEnvironment.java b/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/TestProgressEnvironment.java
new file mode 100644
index 0000000..fa547d4
--- /dev/null
+++ b/platform/api.progress.nb/test/unit/src/org/netbeans/api/progress/TestProgressEnvironment.java
@@ -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.
+ */
+package org.netbeans.api.progress;
+
+import java.util.concurrent.Callable;
+import junit.framework.AssertionFailedError;
+import org.netbeans.modules.progress.spi.Controller;
+import org.netbeans.modules.progress.spi.ProgressEnvironment;
+import org.openide.util.Cancellable;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author sdedic
+ */
+@ServiceProvider(service = ProgressEnvironment.class, position = 0)
+public class TestProgressEnvironment implements ProgressEnvironment {
+    public volatile ProgressEnvironment delegate;
+
+    public TestProgressEnvironment() {
+    }
+    
+    public ProgressEnvironment delegate() {
+        if (delegate != null) {
+            return delegate;
+        }
+        return Lookup.getDefault().lookupAll(ProgressEnvironment.class).stream().
+                filter(i -> i != this).findFirst().orElseThrow(() -> new AssertionFailedError());
+    }
+
+    @Override
+    public ProgressHandle createHandle(String displayname, Cancellable c, boolean userInit) {
+        return delegate().createHandle(displayname, c, userInit);
+    }
+
+    @Override
+    public Controller getController() {
+        return delegate().getController();
+    }
+    
+    public static void withEnvironment(ProgressEnvironment instance, Callable<Void> c) throws Exception {
+        // will fail on CCE if this is not the 1st.
+        TestProgressEnvironment e = (TestProgressEnvironment)Lookup.getDefault().lookup(TestProgressEnvironment.class);
+        try {
+            e.delegate = instance;
+            c.call();
+        } finally {
+            e.delegate = null;
+        }
+    }
+}
diff --git a/platform/api.progress/apichanges.xml b/platform/api.progress/apichanges.xml
index 6dd10e6..436c7be 100644
--- a/platform/api.progress/apichanges.xml
+++ b/platform/api.progress/apichanges.xml
@@ -83,6 +83,22 @@
 
     <!-- ACTUAL CHANGES BEGIN HERE: -->
   <changes>
+      <change id="progressAction">
+          <api name="progress_api"/>
+          <summary>
+              Added support for non-visual default actions for ProgressHandles in headless mode.
+          </summary>
+          <date day="5" month="1" year="2021"/>
+          <author login="sdedic"/>
+          <compatibility binary="compatible" source="compatible" addition="yes"/>
+          <description>
+              The default action on <a href="@TOP@/org/netbeans/modules/progress/api/ProgressHandle.html">ProgressHandle</a> is sometimes non-visual: open a file, focus a result, 
+              display an output window associated with the task, etc. Abstract action can be used now with the basic Progress API without
+              dependency on NB- or Swing- specific APIs.
+          </description>
+          <class package="org.netbeans.modules.progress.api" name="ProgressHandle"/>
+          <class package="org.netbeans.modules.progress.spi" name="InternalHandle"/>
+      </change>
       <change id="progresshandle.time">
           <api name="progress_api"/>
           <summary>
diff --git a/platform/api.progress/manifest.mf b/platform/api.progress/manifest.mf
index 8654ce7..70bc401 100644
--- a/platform/api.progress/manifest.mf
+++ b/platform/api.progress/manifest.mf
@@ -3,5 +3,5 @@
 OpenIDE-Module-Localizing-Bundle: org/netbeans/progress/module/resources/Bundle.properties
 OpenIDE-Module-Recommends: org.netbeans.modules.progress.spi.ProgressUIWorkerProvider, org.netbeans.modules.progress.spi.RunOffEDTProvider
 AutoUpdate-Essential-Module: true
-OpenIDE-Module-Specification-Version: 1.57
+OpenIDE-Module-Specification-Version: 1.57.1
 
diff --git a/platform/api.progress/src/org/netbeans/api/progress/ProgressHandle.java b/platform/api.progress/src/org/netbeans/api/progress/ProgressHandle.java
index 11f9ae3..d748246 100644
--- a/platform/api.progress/src/org/netbeans/api/progress/ProgressHandle.java
+++ b/platform/api.progress/src/org/netbeans/api/progress/ProgressHandle.java
@@ -19,8 +19,12 @@
 
 package org.netbeans.api.progress;
 
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.function.Consumer;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import javax.swing.Action;
 import org.netbeans.modules.progress.spi.InternalHandle;
 import org.netbeans.progress.module.DefaultHandleFactory;
 import org.netbeans.progress.module.ProgressApiAccessor;
@@ -39,7 +43,12 @@
  * @author Milos Kleint (mkleint@netbeans.org)
  */
 public final class ProgressHandle implements AutoCloseable {
-
+    /**
+     * The action command generated by user "activation" of a {@link ProgressHandle}'s
+     * representation.
+     */
+    public static final String ACTION_VIEW = "performView"; // NOI18N
+    
     private static final Logger LOG = Logger.getLogger(ProgressHandle.class.getName());
 
     private InternalHandle internal;
@@ -77,6 +86,48 @@
     public static ProgressHandle createSystemHandle(String displayName, Cancellable allowToCancel) {
         return DefaultHandleFactory.get().createHandle(displayName, allowToCancel, false);
     }
+    
+    /**
+     * Creates a potentially cancellable handle, which can deliver an event if the user 'triggers' the handle. 
+     * How the handle will be presented, and what the "trigger" gesture is depends on the {@link Controller} 
+     * implementation. The user may make an action on the handle (other than cancel), which will
+     * be reflected back. Typically used to focus or display some part of the UI. 
+     * If {@code triggerCallback} is provided, it should handle at least {@link #ACTION_VIEW} action command
+     * {@link ActionEvent#getActionCommand}.
+     * 
+     * @param viewAction callback on user's trigger.
+     * @param displayName to be shown in the progress UI
+     * @param allowToCancel either null, if the task cannot be cancelled or 
+     *          an instance of {@link org.openide.util.Cancellable} that will be called when user 
+     *          triggers cancel of the task.
+     * @return an instance of {@link org.netbeans.api.progress.ProgressHandle}, initialized but not started.
+     * @since 1.59
+     */
+    public static ProgressHandle createHandle(String displayName, Cancellable allowToCancel, Action viewAction) {
+        ProgressHandle h = DefaultHandleFactory.get().createHandle(displayName, allowToCancel, true);
+        if (viewAction != null) {
+            h.addDefaultAction(viewAction);
+        }
+        return h;
+    }
+
+    /**
+     * Creates a possibly cancellable handle, <b>not initiated</b> by the user, e.g. for indexing, 
+     * repository or other system-launched tasks. See documentation for {@link #createHandle(java.lang.String, org.openide.util.Cancellable, javax.swing.Action) }
+     * for more details.
+     * @param displayName to be shown in the progress UI
+     * @param allowToCancel either {@code null}, if the task cannot be cancelled or an {@link Cancellable} instance
+     * that will be called when the user requests cancel.
+     * @param viewAction listener that will be triggered when the progress is 'activated'.
+     * @return created ProgressHandle instance
+     */
+    public static ProgressHandle createSystemHandle(String displayName, Cancellable allowToCancel, Action viewAction) {
+        ProgressHandle h = DefaultHandleFactory.get().createHandle(displayName, allowToCancel, false);
+        if (viewAction != null) {
+            h.addDefaultAction(viewAction);
+        }
+        return h;
+    }
 
     /** Creates a new instance of ProgressHandle */
     ProgressHandle(InternalHandle internal) {
@@ -241,6 +292,16 @@
     }
     
     /**
+     * Adds an action to the progress indicator. 
+     * @param action describes the action
+     * @return true, if actions are supported by the progress implementation.
+     * @since 1.59
+     */
+    public final boolean addDefaultAction(Action action ) {
+        return internal.requestAction(ACTION_VIEW, action );
+    }
+    
+    /**
      * for unit testing only..
      */
     @PatchedPublic
diff --git a/platform/api.progress/src/org/netbeans/api/progress/aggregate/BasicAggregateProgressFactory.java b/platform/api.progress/src/org/netbeans/api/progress/aggregate/BasicAggregateProgressFactory.java
index 5a12910..16b233a 100644
--- a/platform/api.progress/src/org/netbeans/api/progress/aggregate/BasicAggregateProgressFactory.java
+++ b/platform/api.progress/src/org/netbeans/api/progress/aggregate/BasicAggregateProgressFactory.java
@@ -19,6 +19,7 @@
 
 package org.netbeans.api.progress.aggregate;
 
+import javax.swing.Action;
 import org.netbeans.api.progress.ProgressHandle;
 import org.openide.util.Cancellable;
 
@@ -53,6 +54,42 @@
         return new ProgressContributor(trackingId);
     }
     
+    /**
+     * Create an aggregating progress ui handle for a long lasting task.
+     * @param contributors the initial set of progress indication contributors that are aggregated in the UI.
+     * @param allowToCancel either null, if the task cannot be cancelled or 
+     *          an instance of {@link org.openide.util.Cancellable} that will be called when user 
+     *          triggers cancel of the task.
+     * @param linkOutput an <code>Action</code> instance that links the running task in the progress bar
+     *                   to an output of the task. The action is assumed to open the apropriate component with the task's output.
+     * @param displayName to be shown in the progress UI
+     * @return an instance of <code>ProgressHandle</code>, initialized but not started.
+     * @since 1.59
+     */
+    public static AggregateProgressHandle createSystemHandle(String displayName, ProgressContributor[] contributors, 
+                                                       Cancellable allowToCancel, Action linkOutput) {
+        return doCreateHandle(displayName, contributors, allowToCancel, true,
+                ProgressHandle.createSystemHandle(displayName, allowToCancel, linkOutput));
+    }  
+    
+    /**
+     * Create an aggregating progress ui handle for a long lasting task.
+     * @param contributors the initial set of progress indication contributors that are aggregated in the UI.
+     * @param allowToCancel either null, if the task cannot be cancelled or 
+     *          an instance of {@link org.openide.util.Cancellable} that will be called when user 
+     *          triggers cancel of the task.
+     * @param linkOutput an <code>Action</code> instance that links the running task in the progress bar
+     *                   to an output of the task. The action is assumed to open the apropriate component with the task's output.
+     * @param displayName to be shown in the progress UI
+     * @return an instance of <code>ProgressHandle</code>, initialized but not started.
+     * @since 1.59
+     */
+    public static AggregateProgressHandle createHandle(String displayName, ProgressContributor[] contributors, 
+                                                       Cancellable allowToCancel, Action linkOutput) {
+        return doCreateHandle(displayName, contributors, allowToCancel, false,
+                ProgressHandle.createHandle(displayName, allowToCancel, linkOutput));
+    }
+    
     protected static AggregateProgressHandle doCreateHandle(String displayName, ProgressContributor[] contributors, 
                                                        Cancellable allowToCancel, boolean systemHandle, ProgressHandle h) {
         return new AggregateProgressHandle(displayName, contributors, allowToCancel, systemHandle,
diff --git a/platform/api.progress/src/org/netbeans/modules/progress/spi/Controller.java b/platform/api.progress/src/org/netbeans/modules/progress/spi/Controller.java
index 1ed5831..b465e55 100644
--- a/platform/api.progress/src/org/netbeans/modules/progress/spi/Controller.java
+++ b/platform/api.progress/src/org/netbeans/modules/progress/spi/Controller.java
@@ -34,6 +34,7 @@
  * @since org.netbeans.api.progress/1 1.18
  */
 public class Controller {
+    private static final Logger LOG = Logger.getLogger(Controller.class.getName());
     
     // non-private so that it can be accessed from the tests
     public static Controller defaultInstance;
@@ -310,14 +311,17 @@
                 boolean isShort = (stamp - event.getSource().getTimeStampStarted()) < event.getSource().getInitialDelay();
                 if (event.getType() == ProgressEvent.TYPE_START) {
                     if (event.getSource().isCustomPlaced() || !isShort) {
+                        LOG.log(Level.FINER, "Adding to model {0}", event);
                         model.addHandle(event.getSource());
                     } else {
+                        LOG.log(Level.FINER, "Short-start: {0}", event);
                         justStarted.add(event.getSource());
                     }
                 }
                 else if (event.getType() == ProgressEvent.TYPE_FINISH &&
                        (! justStarted.contains(event.getSource()))) 
                 {
+                    LOG.log(Level.FINER, "Removed from model: {0}", event);
                     model.removeHandle(event.getSource());
                 }
                 ProgressEvent lastEvent = map.get(event.getSource());
@@ -326,16 +330,22 @@
                 {
                     // if task quits really fast, ignore..
                     // defined 'really fast' as being shorter than initial delay
+                    LOG.log(Level.FINER, "Short task ended: {0}", event);
                     map.remove(event.getSource());
                     justStarted.remove(event.getSource());
                 } else {
                     if (lastEvent != null) {
                         // preserve last message
+                        if (LOG.isLoggable(Level.FINER)) {
+                            LOG.log(Level.FINE, "Merging event " + event.toString());
+                        }
                         event.copyMessageFromEarlier(lastEvent);
                         // preserve the switched state
                         if (lastEvent.isSwitched()) {
                             event.markAsSwitched();
                         }
+                        LOG.log(Level.FINER, "Event merged with {0} to {1}", 
+                                new Object [] { lastEvent, event} );
                     }
                     map.put(event.getSource(), event);
                 }
@@ -351,9 +361,12 @@
                 if (diff >= hndl.getInitialDelay()) {
                     model.addHandle(hndl);
                 } else {
-                    eventQueue.add(new ProgressEvent(hndl, ProgressEvent.TYPE_START, isWatched(hndl)));
+                    ProgressEvent stE; 
+                    eventQueue.add(stE = new ProgressEvent(hndl, ProgressEvent.TYPE_START, isWatched(hndl)));
+                    LOG.log(Level.FINER, "Repost start event: {0}", stE);
                     ProgressEvent evnt = map.remove(hndl);
                     if (evnt.getType() != ProgressEvent.TYPE_START) {
+                        LOG.log(Level.FINER, "Repost queued event: {0}", evnt);
                         eventQueue.add(evnt);
                     }
                     hasShortOne = true;
@@ -369,6 +382,7 @@
         }
         while (it.hasNext()) {
             ProgressEvent event = it.next();
+            LOG.log(Level.FINER, "Dispatching: {0}", event);
             if (selected == event.getSource()) {
                 component.processSelectedProgressEvent(event);
             }
diff --git a/platform/api.progress/src/org/netbeans/modules/progress/spi/InternalHandle.java b/platform/api.progress/src/org/netbeans/modules/progress/spi/InternalHandle.java
index b7e73fd..f09f993 100644
--- a/platform/api.progress/src/org/netbeans/modules/progress/spi/InternalHandle.java
+++ b/platform/api.progress/src/org/netbeans/modules/progress/spi/InternalHandle.java
@@ -24,6 +24,7 @@
 import java.lang.reflect.Method;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import javax.swing.Action;
 import org.netbeans.api.progress.ProgressHandle;
 import org.netbeans.progress.module.*;
 import org.openide.modules.PatchedPublic;
@@ -53,6 +54,8 @@
     private final boolean userInitiated;
     private int initialDelay = Controller.INITIAL_DELAY;
     private Controller controller;
+    private ProgressHandle handle;
+    private boolean customPlaced;
     
     public static final int STATE_INITIALIZED = 0;
     public static final int STATE_RUNNING = 1;
@@ -85,10 +88,23 @@
      * @since 1.40
      */
     public final ProgressHandle createProgressHandle() {
-        if (del != null) {
-            return ProgressApiAccessor.getInstance().create(del);
+        synchronized (this) {
+            if (handle != null) {
+                return handle;
+            }
         }
-        return ProgressApiAccessor.getInstance().create(this);
+        ProgressHandle h;
+        if (del != null) {
+            h = ProgressApiAccessor.getInstance().create(del);
+        } else {
+            h = ProgressApiAccessor.getInstance().create(this);
+        }
+        synchronized (this) {
+            if (handle == null) {
+                handle = h;
+            }
+            return handle;
+        }
     }
     
     public String getDisplayName() {
@@ -126,7 +142,7 @@
         if (del != null) {
             return del.isCustomPlaced();
         }
-        return false;
+        return customPlaced;
     }
     
     public final boolean isUserInitialized() {
@@ -182,7 +198,7 @@
     
     public boolean isInSleepMode() {
         if (del != null) {
-            return isInSleepMode();
+            return del.isInSleepMode();
         }
         return timeSleepy == timeLastProgress;
     }
@@ -369,6 +385,21 @@
         }
         controller.explicitSelection(this);
     }
+
+    /**
+     * Request a interaction callback to be attached to the Handle. The 
+     * implementation decides if the callback is permitted and desirable. One command,
+     * {@link ProgressHandle#ACTION_VIEW} is defined as a default command (action) for
+     * the progress handle presentation. Implementations are free to ignore request
+     * for adding actions.
+     * @param actionCommand command to bind the action for.
+     * @param action action instance
+     * @return true, if the handle agrees to support the action.
+     * @since 1.59
+     */
+    public boolean requestAction(String actionCommand, Action action) {
+        return false;
+    }
     
     public synchronized void requestDisplayNameChange(String newDisplayName) {
         if (del != null) {
@@ -479,4 +510,22 @@
         }
         compatInit = m;
     }
+    
+    /**
+     * Marks this handle as custom-placed. Handle should be marked as custom-placed
+     * if some controller overtakes (part of) handle's presentation.
+     * @since 1.59
+     */
+    protected final void markCustomPlaced() {
+        if (getState() != STATE_INITIALIZED) {
+            throw new IllegalStateException();
+        }
+        customPlaced = true;
+    }
+    
+    @Override
+    public String toString() {
+        return "H@" + Integer.toHexString(System.identityHashCode(this)) + 
+                "[\"" + getDisplayName() + "\", state: " + state + "]";
+    }
 }
diff --git a/platform/api.progress/src/org/netbeans/modules/progress/spi/ProgressEvent.java b/platform/api.progress/src/org/netbeans/modules/progress/spi/ProgressEvent.java
index b01ea88..ab18c5b 100644
--- a/platform/api.progress/src/org/netbeans/modules/progress/spi/ProgressEvent.java
+++ b/platform/api.progress/src/org/netbeans/modules/progress/spi/ProgressEvent.java
@@ -134,4 +134,27 @@
         return displayName;
     }
     
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("EV[").append(getSource());
+        sb.append(", disp: ").append(displayName);
+        
+        String tName;
+        switch (type) {
+            case TYPE_FINISH: tName = "finish"; break;
+            case TYPE_PROGRESS: tName = "progress"; break;
+            case TYPE_REQUEST_STOP: tName = "stop"; break;
+            case TYPE_SILENT: tName = "silent"; break;
+            case TYPE_START: tName = "start"; break;
+            case TYPE_SWITCH: tName = "switch"; break;
+            default: tName = "" + type;
+        }
+        sb.append(", type: ").append(tName);
+        sb.append(", pctDone: ").append(String.format("%3.2f", percentageDone));
+        sb.append(", message: ").append(message);
+        sb.append(", disp: ").append(displayName);
+        sb.append("]");
+        return sb.toString();
+    }
 }
diff --git a/platform/autoupdate.services/nbproject/project.xml b/platform/autoupdate.services/nbproject/project.xml
index 344a9d0..69602df 100644
--- a/platform/autoupdate.services/nbproject/project.xml
+++ b/platform/autoupdate.services/nbproject/project.xml
@@ -31,15 +31,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.40</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.netbeans.api.progress.nb</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>1.40</specification-version>
+                        <specification-version>1.57.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/OperationSupportImpl.java b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/OperationSupportImpl.java
index f9d433b..f0225e2 100644
--- a/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/OperationSupportImpl.java
+++ b/platform/autoupdate.services/src/org/netbeans/modules/autoupdate/services/OperationSupportImpl.java
@@ -37,7 +37,6 @@
 import org.netbeans.api.autoupdate.OperationContainer.OperationInfo;
 import org.netbeans.api.autoupdate.OperationSupport.Restarter;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.spi.autoupdate.CustomInstaller;
 import org.netbeans.spi.autoupdate.CustomUninstaller;
 import org.openide.LifecycleManager;
@@ -531,10 +530,10 @@
             }
             assert containerForUpdate.listInvalid().isEmpty();
             
-            Validator v = containerForUpdate.getSupport().doDownload(ProgressHandleFactory.createHandle(OperationSupportImpl.class.getName()), null, false);
-            Installer i = containerForUpdate.getSupport().doValidate(v, ProgressHandleFactory.createHandle(OperationSupportImpl.class.getName()));
+            Validator v = containerForUpdate.getSupport().doDownload(ProgressHandle.createHandle(OperationSupportImpl.class.getName()), null, false);
+            Installer i = containerForUpdate.getSupport().doValidate(v, ProgressHandle.createHandle(OperationSupportImpl.class.getName()));
             InstallSupportImpl installSupportImpl = Trampoline.API.impl(containerForUpdate.getSupport());
-            Boolean needRestart = installSupportImpl.doInstall(i, ProgressHandleFactory.createHandle(OperationSupportImpl.class.getName()), true);
+            Boolean needRestart = installSupportImpl.doInstall(i, ProgressHandle.createHandle(OperationSupportImpl.class.getName()), true);
             return needRestart;
         }
         @Override
@@ -583,7 +582,7 @@
                     }
                     CustomInstaller installer = impl.getInstallInfo ().getCustomInstaller ();
                     assert installer != null : "CustomInstaller must found for " + impl.getUpdateElement ();
-                    ProgressHandle handle = ProgressHandleFactory.createHandle("Installing " + impl.getDisplayName());
+                    ProgressHandle handle = ProgressHandle.createHandle("Installing " + impl.getDisplayName());
                     //handle.start();
                     success = installer.install (impl.getCodeName (),
                             impl.getSpecificationVersion () == null ? null : impl.getSpecificationVersion ().toString (),
@@ -660,7 +659,7 @@
                     progress.progress(NbBundle.getMessage(OperationSupportImpl.class, "OperationSupportImpl_Custom_Uninstall", impl.getDisplayName()), ++index);
                     CustomUninstaller uninstaller = impl.getNativeItem ().getUpdateItemDeploymentImpl ().getCustomUninstaller ();
                     assert uninstaller != null : "CustomInstaller must found for " + impl.getUpdateElement ();
-                    ProgressHandle handle = ProgressHandleFactory.createHandle("Installing " + impl.getDisplayName());
+                    ProgressHandle handle = ProgressHandle.createHandle("Installing " + impl.getDisplayName());
                     success = uninstaller.uninstall (impl.getCodeName (),
                             impl.getSpecificationVersion () == null ? null : impl.getSpecificationVersion ().toString (),
                             handle);
diff --git a/platform/openide.loaders/nbproject/project.xml b/platform/openide.loaders/nbproject/project.xml
index 0a5bd43..61ee28b 100644
--- a/platform/openide.loaders/nbproject/project.xml
+++ b/platform/openide.loaders/nbproject/project.xml
@@ -40,7 +40,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.40</specification-version>
+                        <specification-version>1.57.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/platform/openide.loaders/src/org/openide/loaders/DataObject.java b/platform/openide.loaders/src/org/openide/loaders/DataObject.java
index a01591e..98dd639 100644
--- a/platform/openide.loaders/src/org/openide/loaders/DataObject.java
+++ b/platform/openide.loaders/src/org/openide/loaders/DataObject.java
@@ -1722,7 +1722,7 @@
             } else {
                 can = null;
             }
-            ProgressHandle ph = ProgressHandleFactory.createHandle(name, can);
+            ProgressHandle ph = ProgressHandle.createHandle(name, can);
             ph.setInitialDelay(500);
             ph.start();
             this.progressHandle = ph;
diff --git a/platform/openide.loaders/src/org/openide/loaders/DataTransferSupport.java b/platform/openide.loaders/src/org/openide/loaders/DataTransferSupport.java
index bf470ec..ad1982b 100644
--- a/platform/openide.loaders/src/org/openide/loaders/DataTransferSupport.java
+++ b/platform/openide.loaders/src/org/openide/loaders/DataTransferSupport.java
@@ -127,7 +127,7 @@
                     @Override
                     public void run() {
                         java.lang.String n = org.openide.awt.Actions.cutAmpersand(getName());
-                        org.netbeans.api.progress.ProgressHandle h = org.netbeans.api.progress.ProgressHandleFactory.createHandle(n);
+                        org.netbeans.api.progress.ProgressHandle h = org.netbeans.api.progress.ProgressHandle.createHandle(n);
 
                         h.start();
                         h.switchToIndeterminate();
diff --git a/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProgressUI.java b/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProgressUI.java
index fd93608..32efb81 100644
--- a/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProgressUI.java
+++ b/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProgressUI.java
@@ -36,9 +36,9 @@
     @Override
     public ProgressHandle createHandle(String displayname, Cancellable c, boolean userInit) {
         if (userInit) {
-            return ProgressHandleFactory.createHandle(displayname, c, null);
+            return ProgressHandleFactory.createUIHandle(displayname, c, null);
         } else {
-            return ProgressHandleFactory.createSystemHandle(displayname, c, null);
+            return ProgressHandleFactory.createSystemUIHandle(displayname, c, null);
         }
     }
 
diff --git a/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProviderImpl.java b/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProviderImpl.java
index 5dffcd8..8423b1b 100644
--- a/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProviderImpl.java
+++ b/platform/progress.ui/src/org/netbeans/modules/progress/ui/ProviderImpl.java
@@ -19,7 +19,10 @@
 
 package org.netbeans.modules.progress.ui;
 
+import java.util.Map;
+import java.util.WeakHashMap;
 import org.netbeans.modules.progress.spi.ExtractedProgressUIWorker;
+import org.netbeans.modules.progress.spi.InternalHandle;
 import org.netbeans.modules.progress.spi.ProgressUIWorkerProvider;
 import org.netbeans.modules.progress.spi.ProgressUIWorkerWithModel;
 
@@ -30,16 +33,26 @@
 @org.openide.util.lookup.ServiceProvider(service=org.netbeans.modules.progress.spi.ProgressUIWorkerProvider.class)
 public class ProviderImpl implements ProgressUIWorkerProvider {
     
+    private final Map<InternalHandle, NbProgressBar>  progresses = new WeakHashMap<>();
+    
     /** Creates a new instance of ProviderImpl */
     public ProviderImpl() {
     }
 
+    @Override
     public ProgressUIWorkerWithModel getDefaultWorker() {
         return new StatusLineComponent();
     }
 
+    @Override
     public ExtractedProgressUIWorker getExtractedComponentWorker() {
         return new NbProgressBar();
     }
-    
+
+    @Override
+    public ExtractedProgressUIWorker extractProgressWorker(InternalHandle handle) {
+        synchronized (this) {
+            return progresses.computeIfAbsent(handle, (h) -> new NbProgressBar());
+        }
+    }
 }
diff --git a/webcommon/html.angular/nbproject/project.xml b/webcommon/html.angular/nbproject/project.xml
index c52923f..d74de4d 100644
--- a/webcommon/html.angular/nbproject/project.xml
+++ b/webcommon/html.angular/nbproject/project.xml
@@ -40,15 +40,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.40</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.netbeans.api.progress.nb</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>1.40</specification-version>
+                        <specification-version>1.57.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/webcommon/html.angular/src/org/netbeans/modules/html/angular/AngularDoc.java b/webcommon/html.angular/src/org/netbeans/modules/html/angular/AngularDoc.java
index 918cea1..7503955 100644
--- a/webcommon/html.angular/src/org/netbeans/modules/html/angular/AngularDoc.java
+++ b/webcommon/html.angular/src/org/netbeans/modules/html/angular/AngularDoc.java
@@ -32,7 +32,6 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.modules.html.angular.model.Directive;
 import org.openide.modules.Places;
 import org.openide.util.Enumerations;
@@ -83,7 +82,7 @@
         LOG.fine("start loading doc"); //NOI18N
         Directive[] dirs = Directive.values();
         directives = Enumerations.array(dirs);
-        progress = ProgressHandleFactory.createHandle(Bundle.doc_building());
+        progress = ProgressHandle.createHandle(Bundle.doc_building());
         progress.start(dirs.length);
 
         buildDoc();
diff --git a/webcommon/html.knockout/nbproject/project.xml b/webcommon/html.knockout/nbproject/project.xml
index 19459eb..0b25b87 100644
--- a/webcommon/html.knockout/nbproject/project.xml
+++ b/webcommon/html.knockout/nbproject/project.xml
@@ -48,15 +48,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.40</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.netbeans.api.progress.nb</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>1.40</specification-version>
+                        <specification-version>1.57.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/webcommon/html.knockout/src/org/netbeans/modules/html/knockout/KODoc.java b/webcommon/html.knockout/src/org/netbeans/modules/html/knockout/KODoc.java
index 3383715..17dc997 100644
--- a/webcommon/html.knockout/src/org/netbeans/modules/html/knockout/KODoc.java
+++ b/webcommon/html.knockout/src/org/netbeans/modules/html/knockout/KODoc.java
@@ -36,7 +36,6 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.modules.html.knockout.model.Binding;
 import org.openide.modules.Places;
 import org.openide.util.NbBundle;
@@ -86,7 +85,7 @@
         items.addAll(Arrays.asList(Binding.values()));
         
         bindings = items.iterator();        
-        progress = ProgressHandleFactory.createHandle(Bundle.doc_building());
+        progress = ProgressHandle.createHandle(Bundle.doc_building());
         progress.start(items.size());
 
         buildDoc();
diff --git a/webcommon/javascript2.nodejs/nbproject/project.xml b/webcommon/javascript2.nodejs/nbproject/project.xml
index a0e4b2d..cd7a7d3 100644
--- a/webcommon/javascript2.nodejs/nbproject/project.xml
+++ b/webcommon/javascript2.nodejs/nbproject/project.xml
@@ -40,15 +40,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.40</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.netbeans.api.progress.nb</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>1.40</specification-version>
+                        <specification-version>1.57.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/webcommon/javascript2.nodejs/src/org/netbeans/modules/javascript2/nodejs/editor/NodeJsDataProvider.java b/webcommon/javascript2.nodejs/src/org/netbeans/modules/javascript2/nodejs/editor/NodeJsDataProvider.java
index db1a150..d8c9b6a 100644
--- a/webcommon/javascript2.nodejs/src/org/netbeans/modules/javascript2/nodejs/editor/NodeJsDataProvider.java
+++ b/webcommon/javascript2.nodejs/src/org/netbeans/modules/javascript2/nodejs/editor/NodeJsDataProvider.java
@@ -50,7 +50,6 @@
 import org.json.simple.JSONObject;
 import org.json.simple.JSONValue;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.netbeans.api.project.FileOwnerQuery;
 import org.netbeans.api.project.Project;
 import org.netbeans.modules.csl.api.Documentation;
@@ -634,7 +633,7 @@
     private void startLoading() {
         LOG.fine("start loading doc"); //NOI18N
         loadingStarted = true;
-        progress = ProgressHandleFactory.createHandle(Bundle.doc_building());
+        progress = ProgressHandle.createHandle(Bundle.doc_building());
         progress.start(1);
     }
     
diff --git a/webcommon/javascript2.requirejs/nbproject/project.xml b/webcommon/javascript2.requirejs/nbproject/project.xml
index 1bdf5a9..b8b6495 100644
--- a/webcommon/javascript2.requirejs/nbproject/project.xml
+++ b/webcommon/javascript2.requirejs/nbproject/project.xml
@@ -40,15 +40,7 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.40</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.netbeans.api.progress.nb</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>1.40</specification-version>
+                        <specification-version>1.57.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/webcommon/javascript2.requirejs/src/org/netbeans/modules/javascript2/requirejs/RequireJsDataProvider.java b/webcommon/javascript2.requirejs/src/org/netbeans/modules/javascript2/requirejs/RequireJsDataProvider.java
index d041e6e..2a64b22 100644
--- a/webcommon/javascript2.requirejs/src/org/netbeans/modules/javascript2/requirejs/RequireJsDataProvider.java
+++ b/webcommon/javascript2.requirejs/src/org/netbeans/modules/javascript2/requirejs/RequireJsDataProvider.java
@@ -41,7 +41,6 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
 import org.openide.modules.Places;
 import org.openide.util.NbBundle;
 import org.openide.util.RequestProcessor;
@@ -151,7 +150,7 @@
     private void startLoading() {
         LOG.fine("start loading doc"); //NOI18N
 
-        progress = ProgressHandleFactory.createHandle(Bundle.doc_building());
+        progress = ProgressHandle.createHandle(Bundle.doc_building());
         progress.start(1);
 
     }
diff --git a/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptDataObjectDataObject.java b/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptDataObjectDataObject.java
index 1c2de60..25a6c0b 100644
--- a/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptDataObjectDataObject.java
+++ b/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptDataObjectDataObject.java
@@ -100,8 +100,13 @@
     @ActionReference(
             path = "Editors/application/x-typescript/Popup",
             id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.WhereUsedAction"),
-            position = 1400,
-            separatorAfter = 1450
+            position = 1400
+    ),
+    @ActionReference(
+            path = "Editors/application/x-typescript/Popup",
+            id = @ActionID(category = "Refactoring", id = "org.netbeans.modules.refactoring.api.ui.RenameAction"),
+            position = 1500,
+            separatorAfter = 1550
     )
 })
 @GrammarRegistration(mimeType="application/x-typescript", grammar="TypeScript.tmLanguage.json")