Merge pull request #2272 from pepness/jakartaee8-javadoc

[NETBEANS-4635] - Add JakartaEE 8 Platform module
diff --git a/README.md b/README.md
index f359efa..e95338f 100644
--- a/README.md
+++ b/README.md
@@ -29,8 +29,8 @@
    * TravisCI:
      * [![Build Status](https://travis-ci.org/apache/netbeans.svg?branch=master)](https://travis-ci.org/apache/netbeans)
    * Apache Jenkins: 
-     * Linux: [![Build Status](https://builds.apache.org/view/M-R/view/NetBeans/job/netbeans-linux/badge/icon)](https://builds.apache.org/view/M-R/view/NetBeans/job/netbeans-linux/)
-     * Windows: [![Build Status](https://builds.apache.org/view/M-R/view/NetBeans/job/netbeans-windows/badge/icon)](https://builds.apache.org/view/M-R/view/NetBeans/job/netbeans-windows/)
+     * Linux: [![Build Status](https://ci-builds.apache.org/job/Netbeans/job/netbeans-linux/badge/icon)](https://ci-builds.apache.org/job/Netbeans/job/netbeans-linux/)
+     * Windows: [![Build Status](https://ci-builds.apache.org/job/Netbeans/job/netbeans-windows/badge/icon)](https://ci-builds.apache.org/job/Netbeans/job/netbeans-windows)
 
 ### Requirements
 
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/UnconfiguredHint.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/UnconfiguredHint.java
index 76b1911..59d9f77 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/UnconfiguredHint.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/UnconfiguredHint.java
@@ -87,12 +87,14 @@
             return ;
         }
         List<ErrorDescription> errors = new ArrayList<>();
-        String ccls = Utils.settings().get(Utils.KEY_CCLS_PATH, null);
-        if (ccls == null || !new File(ccls).canExecute() || !new File(ccls).isFile()) {
-            errors.add(ErrorDescriptionFactory.createErrorDescription(Severity.WARNING, "ccls not configured!", Collections.singletonList(new ConfigureCCLS()), doc, 0));
+        String ccls = Utils.getCCLSPath();
+        String clangd = Utils.getCLANGDPath();
+        if ((ccls == null || !new File(ccls).canExecute() || !new File(ccls).isFile()) &&
+            (clangd == null || !new File(clangd).canExecute() || !new File(clangd).isFile())) {
+            errors.add(ErrorDescriptionFactory.createErrorDescription(Severity.WARNING, "Neither ccls nor clangd configured!", Collections.singletonList(new ConfigureCCLS()), doc, 0));
         } else {
             Project prj = FileOwnerQuery.getOwner(file);
-            if (LanguageServerImpl.getProjectSettings(prj) == null) {
+            if (prj != null && LanguageServerImpl.getCompileCommandsDir(prj) == null) {
                 errors.add(ErrorDescriptionFactory.createErrorDescription(Severity.WARNING, "compile commands not configured", doc, 0));
             }
         }
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/Utils.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/Utils.java
index 0b3199d..a617786 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/Utils.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/Utils.java
@@ -18,8 +18,19 @@
  */
 package org.netbeans.modules.cpplite.editor;
 
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import java.util.prefs.Preferences;
+import org.openide.filesystems.FileUtil;
 import org.openide.util.NbPreferences;
+import org.openide.util.Parameters;
 
 /**
  *
@@ -27,8 +38,91 @@
  */
 public class Utils {
     public static final String KEY_CCLS_PATH = "ccls";
+    public static final String KEY_CLANGD_PATH = "clangd";
+
+    private static final String[] CCLS_NAMES = new String[] {"ccls"};
+    private static final String[] CLANGD_NAMES = new String[] {"clangd-10", "clangd", "clangd-9"};
 
     public static Preferences settings() {
         return NbPreferences.forModule(Utils.class);
     }
+
+    private static List<String> cclsAutodetectedPaths;
+
+    public static synchronized String getCCLSPath() {
+        String path = settings().get(KEY_CCLS_PATH, null);
+        if (path == null || path.isEmpty()) {
+            if (cclsAutodetectedPaths == null) {
+                cclsAutodetectedPaths = findFileOnUsersPath(CCLS_NAMES);
+            }
+            if (!cclsAutodetectedPaths.isEmpty()) {
+                path = cclsAutodetectedPaths.get(0);
+            }
+        }
+        if (path == null || path.isEmpty()) {
+            return null;
+        }
+        return path;
+    }
+
+    private static List<String> clangdAutodetectedPaths;
+
+    public static synchronized String getCLANGDPath() {
+        String path = settings().get(KEY_CLANGD_PATH, null);
+        if (path == null || path.isEmpty()) {
+            if (clangdAutodetectedPaths == null) {
+                clangdAutodetectedPaths = findFileOnUsersPath(CLANGD_NAMES);
+            }
+            if (!clangdAutodetectedPaths.isEmpty()) {
+                path = clangdAutodetectedPaths.get(0);
+            }
+        }
+        if (path == null || path.isEmpty()) {
+            return null;
+        }
+        return path;
+    }
+
+    //TODO: copied from webcommon/javascript.nodejs/src/org/netbeans/modules/javascript/nodejs/util/FileUtils.java:
+    /**
+     * Find all the files (absolute path) with the given "filename" on user's PATH.
+     * <p>
+     * This method is suitable for *nix as well as windows.
+     * @param filenames the name of a file to find, more names can be provided.
+     * @return list of absolute paths of found files (order preserved according to input names).
+     * @see #findFileOnUsersPath(String)
+     */
+    public static List<String> findFileOnUsersPath(String... filenames) {
+        Parameters.notNull("filenames", filenames); // NOI18N
+
+        String path = System.getenv("PATH"); // NOI18N
+        LOGGER.log(Level.FINE, "PATH: [{0}]", path);
+        if (path == null) {
+            return Collections.<String>emptyList();
+        }
+        // on linux there are usually duplicities in PATH
+        Set<String> dirs = new LinkedHashSet<>(Arrays.asList(path.split(File.pathSeparator)));
+        LOGGER.log(Level.FINE, "PATH dirs: {0}", dirs);
+        List<String> found = new ArrayList<>(dirs.size() * filenames.length);
+        for (String filename : filenames) {
+            Parameters.notNull("filename", filename); // NOI18N
+            for (String dir : dirs) {
+                File file = new File(dir, filename);
+                if (file.isFile()) {
+                    String absolutePath = FileUtil.normalizeFile(file).getAbsolutePath();
+                    LOGGER.log(Level.FINE, "File ''{0}'' found", absolutePath);
+                    // not optimal but should be ok
+                    if (!found.contains(absolutePath)) {
+                        LOGGER.log(Level.FINE, "File ''{0}'' added to found files", absolutePath);
+                        found.add(absolutePath);
+                    }
+                }
+            }
+        }
+        LOGGER.log(Level.FINE, "Found files: {0}", found);
+        return found;
+    }
+
+    private static final Logger LOGGER = Logger.getLogger(Utils.class.getName());
+
 }
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 03dc969..ff58afe 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
@@ -18,12 +18,14 @@
  */
 package org.netbeans.modules.cpplite.editor.lsp;
 
+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.lang.ProcessBuilder.Redirect;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -31,6 +33,8 @@
 import java.util.logging.Logger;
 import java.util.prefs.PreferenceChangeEvent;
 import java.util.prefs.PreferenceChangeListener;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
 import org.netbeans.api.editor.mimelookup.MimeRegistration;
 import org.netbeans.api.editor.mimelookup.MimeRegistrations;
 import org.netbeans.api.project.Project;
@@ -38,11 +42,11 @@
 import org.netbeans.modules.cpplite.editor.file.MIMETypes;
 import org.netbeans.modules.lsp.client.spi.ServerRestarter;
 import org.netbeans.modules.lsp.client.spi.LanguageServerProvider;
-import org.openide.util.Exceptions;
 import org.openide.util.Lookup;
 import org.netbeans.modules.cpplite.editor.spi.CProjectConfigurationProvider;
 import org.netbeans.modules.cpplite.editor.spi.CProjectConfigurationProvider.ProjectConfiguration;
 import org.openide.filesystems.FileUtil;
+import org.openide.modules.Places;
 
 /**
  *
@@ -74,32 +78,45 @@
         Utils.settings().addPreferenceChangeListener(new PreferenceChangeListener() {
             @Override
             public void preferenceChange(PreferenceChangeEvent evt) {
-                if (evt.getKey() == null || Utils.KEY_CCLS_PATH.equals(evt.getKey())) {
+                if (evt.getKey() == null || Utils.KEY_CCLS_PATH.equals(evt.getKey()) || Utils.KEY_CLANGD_PATH.equals(evt.getKey())) {
+                    prj2Server.remove(prj);
                     restarter.restart();
                     Utils.settings().removePreferenceChangeListener(this);
                 }
             }
         });
-        String ccls = Utils.settings().get(Utils.KEY_CCLS_PATH, null);
-        if (ccls != null) {
+        String ccls = Utils.getCCLSPath();
+        String clangd = Utils.getCLANGDPath();
+        if (ccls != null || clangd != null) {
             return prj2Server.computeIfAbsent(prj, (Project p) -> {
                 try {
                     List<String> command = new ArrayList<>();
-                    command.add(ccls);
 
-                    List<String> cat = getProjectSettings(prj);
-
-                    if (cat != null) {
-                        StringBuilder initOpt = new StringBuilder();
-                        initOpt.append("--init={\"compilationDatabaseCommand\":\"");
-                        String sep = "";
-                        for (String c : cat) {
-                            initOpt.append(sep);
-                            initOpt.append(c);
-                            sep = " ";
+                    CProjectConfigurationProvider config = getProjectSettings(prj);
+                    config.addChangeListener(new ChangeListener() {
+                        @Override
+                        public void stateChanged(ChangeEvent e) {
+                            prj2Server.remove(prj);
+                            restarter.restart();
+                            config.removeChangeListener(this);
                         }
-                        initOpt.append("\"}");
-                        command.add(initOpt.toString());
+                    });
+                    File compileCommandDirs = getCompileCommandsDir(config);
+
+                    if (compileCommandDirs != null) {
+                        if (ccls != null) {
+                            command.add(ccls);
+                            StringBuilder initOpt = new StringBuilder();
+                            initOpt.append("--init={\"compilationDatabaseDirectory\":\"");
+                            initOpt.append(compileCommandDirs.getAbsolutePath());
+                            initOpt.append("\"}");
+                            command.add(initOpt.toString());
+                        } else {
+                            command.add(clangd);
+                            command.add("--compile-commands-dir=" + compileCommandDirs.getAbsolutePath());
+                            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);
                     }
@@ -113,25 +130,60 @@
         return null;
     }
     
-    public static List<String> getProjectSettings(Project prj) {
-        CProjectConfigurationProvider configProvider = prj.getLookup().lookup(CProjectConfigurationProvider.class);
-
-        if (configProvider != null) {
-            ProjectConfiguration config = configProvider.getProjectConfiguration();
-            if (config != null && config.commandJsonCommand != null) {
-                return configProvider.getProjectConfiguration().commandJsonCommand;
-            } else if (config != null && configProvider.getProjectConfiguration().commandJsonPath != null) {
-                //TODO: Linux independent!
-                return Arrays.asList("cat", configProvider.getProjectConfiguration().commandJsonPath);
-            }
-            return null;
-        } else if (prj.getProjectDirectory().getFileObject("compile_commands.json") != null) {
-            //TODO: Linux independent!
-            return Arrays.asList("cat", FileUtil.toFile(prj.getProjectDirectory().getFileObject("compile_commands.json")).getAbsolutePath());
-        } else {
-            return null;
-        }
+    public static File getCompileCommandsDir(Project prj) {
+        return getCompileCommandsDir(getProjectSettings(prj));
     }
+
+    private static CProjectConfigurationProvider getProjectSettings(Project prj) {
+        CProjectConfigurationProvider configProvider = prj.getLookup().lookup(CProjectConfigurationProvider.class);
+        if (configProvider == null) {
+            configProvider = new CProjectConfigurationProvider() {
+                @Override
+                public ProjectConfiguration getProjectConfiguration() {
+                    return new ProjectConfiguration(new File(FileUtil.toFile(prj.getProjectDirectory()), "compile_commands.json").getAbsolutePath());
+                }
+                @Override
+                public void addChangeListener(ChangeListener listener) {
+                }
+                @Override
+                public void removeChangeListener(ChangeListener listener) {
+                }
+            };
+        }
+        return configProvider;
+    }
+
+    private static int tempDirIndex = 0;
+
+    private static File getCompileCommandsDir(CProjectConfigurationProvider configProvider) {
+        ProjectConfiguration config = configProvider.getProjectConfiguration();
+
+        if (config.commandJsonCommand != null || configProvider.getProjectConfiguration().commandJsonPath != null) {
+            File tempFile = Places.getCacheSubfile("cpplite/compile_commands/" + tempDirIndex++ + "/compile_commands.json");
+            if (config.commandJsonCommand != null) {
+                try {
+                    new ProcessBuilder(config.commandJsonCommand).redirectOutput(tempFile).redirectError(Redirect.INHERIT).start().waitFor();
+                } catch (IOException | InterruptedException ex) {
+                    LOG.log(Level.WARNING, null, ex);
+                    return null;
+                }
+            } else {
+                File commandsPath = new File(configProvider.getProjectConfiguration().commandJsonPath);
+                if (commandsPath.canRead()) {
+                    try (InputStream in = new FileInputStream(commandsPath);
+                         OutputStream out = new FileOutputStream(tempFile)) {
+                        FileUtil.copy(in, out);
+                    } catch (IOException ex) {
+                        LOG.log(Level.WARNING, null, ex);
+                        return null;
+                    }
+                }
+            }
+            return tempFile.getParentFile();
+        }
+        return null;
+    }
+
     private static class CopyInput extends InputStream {
 
         private final InputStream delegate;
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/options/Bundle.properties b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/options/Bundle.properties
index ae14b37..48ee5b5 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/options/Bundle.properties
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/options/Bundle.properties
@@ -16,5 +16,9 @@
 # under the License.
 
 CPPLitePanel.jLabel1.text=CCLS Location:
-CPPLitePanel.jButton1.text=...
 CPPLitePanel.cclsPath.text=
+CPPLitePanel.jLabel2.text=clangd Location:
+CPPLitePanel.jLabel3.text=<html><body>Please provide a path to either the <a href="https://github.com/MaskRay/ccls">ccls</a> or the <a href="https://clangd.llvm.org/">clangd</a> language protocol servers.\n<br>\nThese will be used by the editor to provide features like code completion.
+CPPLitePanel.clangdPath.text=
+CPPLitePanel.cclsBrowse.text=...
+CPPLitePanel.clangdBrowse.text=...
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/options/CPPLitePanel.form b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/options/CPPLitePanel.form
index e5d211c..95574a8 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/options/CPPLitePanel.form
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/options/CPPLitePanel.form
@@ -37,21 +37,50 @@
   <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 min="-2" max="-2" attributes="0"/>
-              <Component id="cclsPath" max="32767" attributes="0"/>
-              <EmptySpace min="-2" max="-2" attributes="0"/>
-              <Component id="jButton1" min="-2" max="-2" attributes="0"/>
+          <Group type="102" attributes="0">
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Group type="102" attributes="0">
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <Component id="jLabel1" alignment="0" min="-2" max="-2" attributes="0"/>
+                          <Component id="jLabel2" alignment="0" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <Component id="cclsPath" max="32767" attributes="0"/>
+                          <Component id="clangdPath" max="32767" attributes="0"/>
+                      </Group>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <Component id="clangdBrowse" min="-2" max="-2" attributes="0"/>
+                          <Component id="cclsBrowse" min="-2" max="-2" attributes="0"/>
+                      </Group>
+                  </Group>
+                  <Group type="102" attributes="0">
+                      <Component id="jLabel3" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
+                  </Group>
+              </Group>
+              <EmptySpace max="-2" 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="cclsPath" alignment="3" min="-2" max="-2" attributes="0"/>
-              <Component id="jButton1" alignment="3" min="-2" max="-2" attributes="0"/>
+          <Group type="102" alignment="1" attributes="0">
+              <Component id="jLabel3" min="-2" max="-2" attributes="0"/>
+              <EmptySpace type="separate" max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="3" attributes="0">
+                  <Component id="jLabel1" alignment="3" min="-2" max="-2" attributes="0"/>
+                  <Component id="cclsPath" alignment="3" min="-2" max="-2" attributes="0"/>
+                  <Component id="cclsBrowse" alignment="3" min="-2" max="-2" attributes="0"/>
+              </Group>
+              <EmptySpace max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Component id="jLabel2" min="-2" max="-2" attributes="0"/>
+                  <Component id="clangdPath" min="-2" max="-2" attributes="0"/>
+                  <Component id="clangdBrowse" min="-2" max="-2" attributes="0"/>
+              </Group>
+              <EmptySpace max="32767" attributes="0"/>
           </Group>
       </Group>
     </DimensionLayout>
@@ -71,10 +100,44 @@
         </Property>
       </Properties>
     </Component>
-    <Component class="javax.swing.JButton" name="jButton1">
+    <Component class="javax.swing.JButton" name="cclsBrowse">
       <Properties>
         <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-          <ResourceString bundle="org/netbeans/modules/cpplite/editor/lsp/options/Bundle.properties" key="CPPLitePanel.jButton1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+          <ResourceString bundle="org/netbeans/modules/cpplite/editor/lsp/options/Bundle.properties" key="CPPLitePanel.cclsBrowse.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+      <Events>
+        <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cclsBrowseActionPerformed"/>
+      </Events>
+    </Component>
+    <Component class="javax.swing.JLabel" name="jLabel2">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/netbeans/modules/cpplite/editor/lsp/options/Bundle.properties" key="CPPLitePanel.jLabel2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+    <Component class="javax.swing.JTextField" name="clangdPath">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/netbeans/modules/cpplite/editor/lsp/options/Bundle.properties" key="CPPLitePanel.clangdPath.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+    <Component class="javax.swing.JButton" name="clangdBrowse">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/netbeans/modules/cpplite/editor/lsp/options/Bundle.properties" key="CPPLitePanel.clangdBrowse.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+      <Events>
+        <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="clangdBrowseActionPerformed"/>
+      </Events>
+    </Component>
+    <Component class="javax.swing.JLabel" name="jLabel3">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/netbeans/modules/cpplite/editor/lsp/options/Bundle.properties" key="CPPLitePanel.jLabel3.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
         </Property>
       </Properties>
     </Component>
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/options/CPPLitePanel.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/options/CPPLitePanel.java
index a24edfd..864d1c9 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/options/CPPLitePanel.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/options/CPPLitePanel.java
@@ -18,6 +18,9 @@
  */
 package org.netbeans.modules.cpplite.editor.lsp.options;
 
+import java.io.File;
+import javax.swing.JFileChooser;
+import javax.swing.JTextField;
 import javax.swing.event.DocumentEvent;
 import javax.swing.event.DocumentListener;
 import org.netbeans.modules.cpplite.editor.Utils;
@@ -29,7 +32,7 @@
     CPPLitePanel(CPPLiteOptionsPanelController controller) {
         this.controller = controller;
         initComponents();
-        cclsPath.getDocument().addDocumentListener(new DocumentListener() {
+        DocumentListener pathsModified = new DocumentListener() {
             @Override
             public void insertUpdate(DocumentEvent e) {
                 controller.changed();
@@ -42,8 +45,9 @@
             public void changedUpdate(DocumentEvent e) {
                 controller.changed();
             }
-        });
-        // TODO listen to changes in form fields and call controller.changed()
+        };
+        cclsPath.getDocument().addDocumentListener(pathsModified);
+        clangdPath.getDocument().addDocumentListener(pathsModified);
     }
 
     /**
@@ -56,40 +60,101 @@
 
         jLabel1 = new javax.swing.JLabel();
         cclsPath = new javax.swing.JTextField();
-        jButton1 = new javax.swing.JButton();
+        cclsBrowse = new javax.swing.JButton();
+        jLabel2 = new javax.swing.JLabel();
+        clangdPath = new javax.swing.JTextField();
+        clangdBrowse = new javax.swing.JButton();
+        jLabel3 = new javax.swing.JLabel();
 
         org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(CPPLitePanel.class, "CPPLitePanel.jLabel1.text")); // NOI18N
 
         cclsPath.setText(org.openide.util.NbBundle.getMessage(CPPLitePanel.class, "CPPLitePanel.cclsPath.text")); // NOI18N
 
-        org.openide.awt.Mnemonics.setLocalizedText(jButton1, org.openide.util.NbBundle.getMessage(CPPLitePanel.class, "CPPLitePanel.jButton1.text")); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(cclsBrowse, org.openide.util.NbBundle.getMessage(CPPLitePanel.class, "CPPLitePanel.cclsBrowse.text")); // NOI18N
+        cclsBrowse.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                cclsBrowseActionPerformed(evt);
+            }
+        });
+
+        org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(CPPLitePanel.class, "CPPLitePanel.jLabel2.text")); // NOI18N
+
+        clangdPath.setText(org.openide.util.NbBundle.getMessage(CPPLitePanel.class, "CPPLitePanel.clangdPath.text")); // NOI18N
+
+        org.openide.awt.Mnemonics.setLocalizedText(clangdBrowse, org.openide.util.NbBundle.getMessage(CPPLitePanel.class, "CPPLitePanel.clangdBrowse.text")); // NOI18N
+        clangdBrowse.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                clangdBrowseActionPerformed(evt);
+            }
+        });
+
+        org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(CPPLitePanel.class, "CPPLitePanel.jLabel3.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(cclsPath)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jButton1))
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addGroup(layout.createSequentialGroup()
+                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                            .addComponent(jLabel1)
+                            .addComponent(jLabel2))
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                            .addComponent(cclsPath)
+                            .addComponent(clangdPath))
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                            .addComponent(clangdBrowse)
+                            .addComponent(cclsBrowse)))
+                    .addGroup(layout.createSequentialGroup()
+                        .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                        .addGap(0, 0, Short.MAX_VALUE)))
+                .addContainerGap())
         );
         layout.setVerticalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
-                .addComponent(jLabel1)
-                .addComponent(cclsPath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
-                .addComponent(jButton1))
+            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+                .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                .addGap(18, 18, 18)
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(jLabel1)
+                    .addComponent(cclsPath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                    .addComponent(cclsBrowse))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(jLabel2)
+                    .addComponent(clangdPath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                    .addComponent(clangdBrowse))
+                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
         );
     }// </editor-fold>//GEN-END:initComponents
 
+    private void cclsBrowseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cclsBrowseActionPerformed
+        showFileChooser(cclsPath);
+    }//GEN-LAST:event_cclsBrowseActionPerformed
+
+    private void clangdBrowseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clangdBrowseActionPerformed
+        showFileChooser(clangdPath);
+    }//GEN-LAST:event_clangdBrowseActionPerformed
+
+    private void showFileChooser(JTextField path) {
+        JFileChooser fc = new JFileChooser();
+        fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+        fc.setSelectedFile(new File(path.getText()));
+        if (fc.showDialog(this, "Select") == JFileChooser.APPROVE_OPTION) {
+            path.setText(fc.getSelectedFile().getAbsolutePath());
+        }
+    }
     void load() {
-        cclsPath.setText(Utils.settings().get(Utils.KEY_CCLS_PATH, ""));
+        cclsPath.setText(Utils.getCCLSPath());
+        clangdPath.setText(Utils.getCLANGDPath());
     }
 
     void store() {
         Utils.settings().put(Utils.KEY_CCLS_PATH, cclsPath.getText());
+        Utils.settings().put(Utils.KEY_CLANGD_PATH, clangdPath.getText());
     }
 
     boolean valid() {
@@ -98,8 +163,12 @@
     }
 
     // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.JButton cclsBrowse;
     private javax.swing.JTextField cclsPath;
-    private javax.swing.JButton jButton1;
+    private javax.swing.JButton clangdBrowse;
+    private javax.swing.JTextField clangdPath;
     private javax.swing.JLabel jLabel1;
+    private javax.swing.JLabel jLabel2;
+    private javax.swing.JLabel jLabel3;
     // End of variables declaration//GEN-END:variables
 }
diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/spi/CProjectConfigurationProvider.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/spi/CProjectConfigurationProvider.java
index 5faacaf..a5ad8c5 100644
--- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/spi/CProjectConfigurationProvider.java
+++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/spi/CProjectConfigurationProvider.java
@@ -19,6 +19,7 @@
 package org.netbeans.modules.cpplite.editor.spi;
 
 import java.util.List;
+import javax.swing.event.ChangeListener;
 
 /**
  *
@@ -27,6 +28,8 @@
 public interface CProjectConfigurationProvider {
 
     public ProjectConfiguration getProjectConfiguration();
+    public void addChangeListener(ChangeListener listener);
+    public void removeChangeListener(ChangeListener listener);
 
     //TODO: factory, accessor
     //TODO: listen on changes
diff --git a/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteCProjectConfigurationProvider.java b/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteCProjectConfigurationProvider.java
index 76e1b94..49b6327 100644
--- a/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteCProjectConfigurationProvider.java
+++ b/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteCProjectConfigurationProvider.java
@@ -18,8 +18,12 @@
  */
 package org.netbeans.modules.cpplite.project;
 
+import java.util.prefs.PreferenceChangeEvent;
+import java.util.prefs.PreferenceChangeListener;
 import java.util.prefs.Preferences;
+import javax.swing.event.ChangeListener;
 import org.netbeans.modules.cpplite.editor.spi.CProjectConfigurationProvider;
+import org.openide.util.ChangeSupport;
 
 /**
  *
@@ -27,12 +31,33 @@
  */
 public class CPPLiteCProjectConfigurationProvider implements CProjectConfigurationProvider {
 
+    private final ChangeSupport cs = new ChangeSupport(this);
     private final Preferences mainPrefs;
 
     public CPPLiteCProjectConfigurationProvider(Preferences mainPrefs) {
         this.mainPrefs = mainPrefs;
+        this.mainPrefs.addPreferenceChangeListener(new PreferenceChangeListener() {
+            @Override
+            public void preferenceChange(PreferenceChangeEvent evt) {
+                if (evt.getKey() == null ||
+                    CPPLiteProject.KEY_COMPILE_COMMANDS.equals(evt.getKey()) ||
+                    CPPLiteProject.KEY_COMPILE_COMMANDS_EXECUTABLE.equals(evt.getKey())) {
+                    cs.fireChange();
+                }
+            }
+        });
     }
-    
+
+    @Override
+    public void addChangeListener(ChangeListener listener) {
+        cs.addChangeListener(listener);
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener listener) {
+        cs.removeChangeListener(listener);
+    }
+
     @Override
     public ProjectConfiguration getProjectConfiguration() {
         String path = mainPrefs.get(CPPLiteProject.KEY_COMPILE_COMMANDS, null);
diff --git a/ide/editor/src/org/netbeans/modules/editor/resources/completion/record.png b/ide/editor/src/org/netbeans/modules/editor/resources/completion/record.png
new file mode 100644
index 0000000..220e6f3
--- /dev/null
+++ b/ide/editor/src/org/netbeans/modules/editor/resources/completion/record.png
Binary files differ
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 a572e48..99adf1a 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
@@ -28,10 +28,12 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
@@ -61,6 +63,7 @@
 import org.netbeans.api.project.FileOwnerQuery;
 import org.netbeans.api.project.Project;
 import org.netbeans.modules.lsp.client.bindings.LanguageClientImpl;
+import org.netbeans.modules.lsp.client.bindings.TextDocumentSyncServerCapabilityHandler;
 import org.netbeans.modules.lsp.client.options.MimeTypeInfo;
 import org.netbeans.modules.lsp.client.spi.ServerRestarter;
 import org.netbeans.modules.lsp.client.spi.LanguageServerProvider;
@@ -71,6 +74,7 @@
 import org.openide.util.Exceptions;
 import org.openide.util.NbBundle.Messages;
 import org.openide.util.RequestProcessor;
+import org.openide.util.RequestProcessor.Task;
 import org.openide.util.lookup.Lookups;
 
 /**
@@ -85,6 +89,7 @@
     private static final Map<Project, Map<String, LSPBindings>> project2MimeType2Server = new WeakHashMap<>();
     private static final Map<FileObject, Map<String, LSPBindings>> workspace2Extension2Server = new HashMap<>();
     private final Map<FileObject, Map<BackgroundTask, RequestProcessor.Task>> backgroundTasks = new WeakHashMap<>();
+    private final Set<FileObject> openedFiles = new HashSet<>();
 
     public static synchronized LSPBindings getBindings(FileObject file) {
         for (Entry<FileObject, Map<String, LSPBindings>> e : workspace2Extension2Server.entrySet()) {
@@ -120,8 +125,15 @@
                                                    if (p != null) {
                                                        LSPBindings b = project2MimeType2Server.getOrDefault(p, Collections.emptyMap()).remove(mimeType);
 
-                                                       if (b != null && 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();
+                                                           }
                                                        }
                                                    }
                                                }
@@ -147,6 +159,7 @@
                                                        b = new LSPBindings(server, result, LanguageServerProviderAccessor.getINSTANCE().getProcess(desc));
                                                        lci.setBindings(b);
                                                        LanguageServerProviderAccessor.getINSTANCE().setBindings(desc, b);
+                                                       TextDocumentSyncServerCapabilityHandler.refreshOpenedFilesInServers();
                                                        return b;
                                                    } catch (InterruptedException | ExecutionException ex) {
                                                        LOG.log(Level.WARNING, null, ex);
@@ -264,7 +277,7 @@
         if (bindings == null)
             return ;
 
-        RequestProcessor.Task req = bindings.backgroundTasks.computeIfAbsent(file, f -> new LinkedHashMap<>()).remove(task);
+        RequestProcessor.Task req = bindings.backgroundTasksMapFor(file).remove(task);
 
         if (req != null) {
             req.cancel();
@@ -285,7 +298,7 @@
         if (bindings == null)
             return ;
 
-        RequestProcessor.Task req = bindings.backgroundTasks.computeIfAbsent(file, f -> Collections.emptyMap()).get(task);
+        RequestProcessor.Task req = bindings.backgroundTasksMapFor(file).get(task);
 
         if (req != null) {
             WORKER.post(req, DELAY);
@@ -293,7 +306,15 @@
     }
 
     public void scheduleBackgroundTasks(FileObject file) {
-        backgroundTasks.computeIfAbsent(file, f -> new IdentityHashMap<>()).values().stream().forEach(this::scheduleBackgroundTask);
+        backgroundTasksMapFor(file).values().stream().forEach(this::scheduleBackgroundTask);
+    }
+
+    private Map<BackgroundTask, Task> backgroundTasksMapFor(FileObject file) {
+        return backgroundTasks.computeIfAbsent(file, f -> new IdentityHashMap<>());
+    }
+
+    public Set<FileObject> getOpenedFiles() {
+        return openedFiles;
     }
 
     public interface BackgroundTask {
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CompletionProviderImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CompletionProviderImpl.java
index 4a1ea3e..f3c0fb6 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CompletionProviderImpl.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CompletionProviderImpl.java
@@ -54,11 +54,15 @@
 import org.eclipse.lsp4j.jsonrpc.messages.Either;
 import org.netbeans.api.editor.completion.Completion;
 import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.api.lexer.Token;
+import org.netbeans.api.lexer.TokenHierarchy;
+import org.netbeans.api.lexer.TokenSequence;
 import org.netbeans.editor.BaseDocument;
 import org.netbeans.editor.Utilities;
 import org.netbeans.modules.editor.NbEditorUtilities;
 import org.netbeans.modules.lsp.client.LSPBindings;
 import org.netbeans.modules.lsp.client.Utils;
+import org.netbeans.modules.textmate.lexer.TextmateTokenId;
 import org.netbeans.spi.editor.completion.CompletionDocumentation;
 import org.netbeans.spi.editor.completion.CompletionProvider;
 import org.netbeans.spi.editor.completion.CompletionResultSet;
@@ -223,8 +227,9 @@
                             public void processKeyEvent(KeyEvent ke) {
                                 if (ke.getID() == KeyEvent.KEY_TYPED) {
                                     String commitText = String.valueOf(ke.getKeyChar());
+                                    List<String> commitCharacters = i.getCommitCharacters();
 
-                                    if (i.getCommitCharacters().contains(commitText)) {
+                                    if (commitCharacters != null && commitCharacters.contains(commitText)) {
                                         commit(commitText);
                                         ke.consume();
                                         if (isTriggerCharacter(server, commitText)) {
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/NavigatorPanelImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/NavigatorPanelImpl.java
index 6c22f2e..a47072f 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/NavigatorPanelImpl.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/NavigatorPanelImpl.java
@@ -170,7 +170,7 @@
         }
 
         private static Children createChildren(String currentFileUri, DocumentSymbol sym) {
-            if (sym.getChildren().isEmpty()) {
+            if (sym.getChildren() == null || sym.getChildren().isEmpty()) {
                 return LEAF;
             }
             return new Keys<DocumentSymbol>() {
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java
index bab8e44..f4aa6bd 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java
@@ -86,39 +86,7 @@
 
             Document doc = opened.getDocument();
 
-            WORKER.post(() -> {
-                LSPBindings server = LSPBindings.getBindings(file);
-
-                if (server == null)
-                    return ; //ignore
-
-                doc.putProperty(HyperlinkProviderImpl.class, Boolean.TRUE);
-
-                String uri = Utils.toURI(file);
-                String[] text = new String[1];
-
-                doc.render(() -> {
-                    try {
-                        text[0] = doc.getText(0, doc.getLength());
-                    } catch (BadLocationException ex) {
-                        Exceptions.printStackTrace(ex);
-                        text[0] = "";
-                    }
-                });
-
-                TextDocumentItem textDocumentItem = new TextDocumentItem(uri,
-                                                                         FileUtil.getMIMEType(file),
-                                                                         0,
-                                                                         text[0]);
-
-                server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(textDocumentItem));
-                if (opened.getClientProperty(MarkOccurrences.class) == null) {
-                    MarkOccurrences mo = new MarkOccurrences(opened);
-                    LSPBindings.addBackgroundTask(file, mo);
-                    opened.putClientProperty(MarkOccurrences.class, mo);
-                }
-                server.scheduleBackgroundTasks(file);
-            });
+            ensureOpenedInServer(opened);
 
             doc.addDocumentListener(new DocumentListener() { //XXX: listener
                 int version; //XXX: proper versioning!
@@ -190,7 +158,6 @@
 
                             if (typingModification && oldText.isEmpty() && event.length == 1) {
                                 if (newText.equals("}") || newText.equals("\n")) {
-                                    System.err.println("going to compute indent:");
                                     List<TextEdit> edits = new ArrayList<>();
                                     doc.render(() -> {
                                         if (documentVersion != DocumentUtilities.getDocumentVersion(doc))
@@ -216,12 +183,68 @@
         }
     }
 
+    private void ensureOpenedInServer(JTextComponent opened) {
+        FileObject file = NbEditorUtilities.getFileObject(opened.getDocument());
+
+        if (file == null)
+            return; //ignore
+
+        Document doc = opened.getDocument();
+        WORKER.post(() -> {
+            LSPBindings server = LSPBindings.getBindings(file);
+
+            if (server == null)
+                return ; //ignore
+
+            if (!server.getOpenedFiles().add(file)) {
+                //already opened:
+                return ;
+            }
+
+            doc.putProperty(HyperlinkProviderImpl.class, Boolean.TRUE);
+
+            String uri = Utils.toURI(file);
+            String[] text = new String[1];
+
+            doc.render(() -> {
+                try {
+                    text[0] = doc.getText(0, doc.getLength());
+                } catch (BadLocationException ex) {
+                    Exceptions.printStackTrace(ex);
+                    text[0] = "";
+                }
+            });
+
+            TextDocumentItem textDocumentItem = new TextDocumentItem(uri,
+                                                                     FileUtil.getMIMEType(file),
+                                                                     0,
+                                                                     text[0]);
+
+            server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(textDocumentItem));
+            if (opened.getClientProperty(MarkOccurrences.class) == null) {
+                MarkOccurrences mo = new MarkOccurrences(opened);
+                LSPBindings.addBackgroundTask(file, mo);
+                opened.putClientProperty(MarkOccurrences.class, mo);
+            }
+            server.scheduleBackgroundTasks(file);
+        });
+    }
+
+    public static void refreshOpenedFilesInServers() {
+        SwingUtilities.invokeLater(() -> {
+            assert SwingUtilities.isEventDispatchThread();
+            for (JTextComponent c : EditorRegistry.componentList()) {
+                h.ensureOpenedInServer(c);
+            }
+        });
+    }
+
+    private static final TextDocumentSyncServerCapabilityHandler h = new TextDocumentSyncServerCapabilityHandler();
     @OnStart
     public static class Init implements Runnable {
 
         @Override
         public void run() {
-            TextDocumentSyncServerCapabilityHandler h = new TextDocumentSyncServerCapabilityHandler();
             EditorRegistry.addPropertyChangeListener(evt -> h.handleChange());
             SwingUtilities.invokeLater(() -> h.handleChange());
         }
diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/customizer/Bundle.properties b/java/gradle.java/src/org/netbeans/modules/gradle/java/customizer/Bundle.properties
index 8089bb4..53c6c77 100644
--- a/java/gradle.java/src/org/netbeans/modules/gradle/java/customizer/Bundle.properties
+++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/customizer/Bundle.properties
@@ -31,3 +31,4 @@
 CompileOptionsPanel.cbIncludeOpenProjects.text=Include Open Projects
 CompileOptionsPanel.lbIncludeOpenProjects.text=<html>Create a composite project from this project and the other open Gradle projects by generating '--include-build' parameters.
 SourceSetPanel.jScrollPane4.TabConstraints.tabTitle=Annotation Processors
+SourceSetPanel.jScrollPane6.TabConstraints.tabTitle=Compiler Args
diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/customizer/SourceSetPanel.form b/java/gradle.java/src/org/netbeans/modules/gradle/java/customizer/SourceSetPanel.form
index 9fec81e..2d24624 100644
--- a/java/gradle.java/src/org/netbeans/modules/gradle/java/customizer/SourceSetPanel.form
+++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/customizer/SourceSetPanel.form
@@ -134,8 +134,8 @@
           <SubComponents>
             <Component class="javax.swing.JTree" name="trSources">
               <Properties>
-                <Property name="model" type="javax.swing.tree.TreeModel" editor="org.netbeans.modules.form.editors2.TreeModelEditor">
-                  <TreeModel code=""/>
+                <Property name="model" type="javax.swing.tree.TreeModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
+                  <Connection code="sourcesModel" type="code"/>
                 </Property>
                 <Property name="rootVisible" type="boolean" value="false"/>
               </Properties>
@@ -211,6 +211,32 @@
             </Component>
           </SubComponents>
         </Container>
+        <Container class="javax.swing.JScrollPane" name="jScrollPane6">
+          <AuxValues>
+            <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
+          </AuxValues>
+          <Constraints>
+            <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
+              <JTabbedPaneConstraints tabName="Compiler Args">
+                <Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+                  <ResourceString bundle="org/netbeans/modules/gradle/java/customizer/Bundle.properties" key="SourceSetPanel.jScrollPane6.TabConstraints.tabTitle" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+                </Property>
+              </JTabbedPaneConstraints>
+            </Constraint>
+          </Constraints>
+
+          <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+          <SubComponents>
+            <Component class="javax.swing.JTree" name="trCompilerArgs">
+              <Properties>
+                <Property name="model" type="javax.swing.tree.TreeModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
+                  <Connection code="argumentsModel" type="code"/>
+                </Property>
+                <Property name="rootVisible" type="boolean" value="false"/>
+              </Properties>
+            </Component>
+          </SubComponents>
+        </Container>
       </SubComponents>
     </Container>
     <Component class="javax.swing.JTextField" name="tfSourceLevel">
diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/customizer/SourceSetPanel.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/customizer/SourceSetPanel.java
index 1f6c370..f4d4e98 100644
--- a/java/gradle.java/src/org/netbeans/modules/gradle/java/customizer/SourceSetPanel.java
+++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/customizer/SourceSetPanel.java
@@ -51,7 +51,10 @@
 
     final Path relativeRoot;
     final GradleJavaSourceSet sourceSet;
-    DefaultMutableTreeNode sourcesRoot = new DefaultMutableTreeNode(new Object());
+    private final DefaultMutableTreeNode sourcesRoot = new DefaultMutableTreeNode(new Object());
+    private final DefaultTreeModel sourcesModel = new DefaultTreeModel(sourcesRoot, true);
+    private final DefaultMutableTreeNode argumentsRoot = new DefaultMutableTreeNode(new Object());
+    private final DefaultTreeModel argumentsModel = new DefaultTreeModel(argumentsRoot, true);
 
     /**
      * Creates new form SourceSetPanel
@@ -82,20 +85,21 @@
         for (GradleJavaSourceSet.SourceType type : GradleJavaSourceSet.SourceType.values()) {
             if (!sourceSet.getSourceDirs(type).isEmpty()) {
                 DefaultMutableTreeNode typeNode = new DefaultMutableTreeNode(type);
-                sourcesRoot.add(typeNode);
+                sourcesModel.insertNodeInto(typeNode, sourcesRoot, sourcesRoot.getChildCount());
                 for (File dir : sourceSet.getSourceDirs(type)) {
-                    typeNode.add(new DefaultMutableTreeNode(dir, false));
+                    sourcesModel.insertNodeInto(new DefaultMutableTreeNode(dir, false), typeNode, typeNode.getChildCount());
                 }
+                trSources.expandPath(new TreePath(typeNode.getPath()));
+            }
+            if (!sourceSet.getCompilerArgs(type).isEmpty()) {
+                DefaultMutableTreeNode typeNode = new DefaultMutableTreeNode(type, true);
+                argumentsModel.insertNodeInto(typeNode, argumentsRoot, argumentsRoot.getChildCount());
+                for (String compilerArg : sourceSet.getCompilerArgs(type)) {
+                    argumentsModel.insertNodeInto(new DefaultMutableTreeNode(compilerArg, false), typeNode, typeNode.getChildCount());
+                }
+                trCompilerArgs.expandPath(new TreePath(typeNode.getPath()));
             }
         }
-        trSources.setModel(new DefaultTreeModel(sourcesRoot, true));
-        DefaultMutableTreeNode currentNode = sourcesRoot;
-        do {
-            if (currentNode.getLevel() <= 1) {
-                trSources.expandPath(new TreePath(currentNode.getPath()));
-            }
-            currentNode = currentNode.getNextNode();
-        } while (currentNode != null);
         trSources.setCellRenderer(new MyTreeCellRenderer());
 
         DefaultListModel<File> compileModel = new DefaultListModel<>();
@@ -141,6 +145,8 @@
         lsRuntime = new javax.swing.JList<>();
         jScrollPane4 = new javax.swing.JScrollPane();
         lsAnnotationProcessors = new javax.swing.JList<>();
+        jScrollPane6 = new javax.swing.JScrollPane();
+        trCompilerArgs = new javax.swing.JTree();
         tfSourceLevel = new javax.swing.JTextField();
         tfOutputResources = new javax.swing.JTextField();
         tfOutputClasses = new javax.swing.JTextField();
@@ -153,8 +159,7 @@
 
         jTabbedPane1.setTabPlacement(javax.swing.JTabbedPane.BOTTOM);
 
-        javax.swing.tree.DefaultMutableTreeNode treeNode1 = new javax.swing.tree.DefaultMutableTreeNode("root");
-        trSources.setModel(new javax.swing.tree.DefaultTreeModel(treeNode1));
+        trSources.setModel(sourcesModel);
         trSources.setRootVisible(false);
         jScrollPane1.setViewportView(trSources);
 
@@ -172,6 +177,12 @@
 
         jTabbedPane1.addTab(org.openide.util.NbBundle.getMessage(SourceSetPanel.class, "SourceSetPanel.jScrollPane4.TabConstraints.tabTitle"), jScrollPane4); // NOI18N
 
+        trCompilerArgs.setModel(argumentsModel);
+        trCompilerArgs.setRootVisible(false);
+        jScrollPane6.setViewportView(trCompilerArgs);
+
+        jTabbedPane1.addTab(org.openide.util.NbBundle.getMessage(SourceSetPanel.class, "SourceSetPanel.jScrollPane6.TabConstraints.tabTitle"), jScrollPane6); // NOI18N
+
         tfSourceLevel.setEditable(false);
         tfSourceLevel.setText(org.openide.util.NbBundle.getMessage(SourceSetPanel.class, "SourceSetPanel.tfSourceLevel.text")); // NOI18N
 
@@ -278,6 +289,7 @@
     private javax.swing.JScrollPane jScrollPane2;
     private javax.swing.JScrollPane jScrollPane3;
     private javax.swing.JScrollPane jScrollPane4;
+    private javax.swing.JScrollPane jScrollPane6;
     private javax.swing.JTabbedPane jTabbedPane1;
     private javax.swing.JList<File> lsAnnotationProcessors;
     private javax.swing.JList<File> lsCompile;
@@ -285,6 +297,7 @@
     private javax.swing.JTextField tfOutputClasses;
     private javax.swing.JTextField tfOutputResources;
     private javax.swing.JTextField tfSourceLevel;
+    private javax.swing.JTree trCompilerArgs;
     private javax.swing.JTree trSources;
     // End of variables declaration//GEN-END:variables
 }
diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/FileBuiltQueryImpl.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/FileBuiltQueryImpl.java
index a66390b..2fbc76f 100644
--- a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/FileBuiltQueryImpl.java
+++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/FileBuiltQueryImpl.java
@@ -32,8 +32,10 @@
 import java.util.WeakHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import javax.swing.event.ChangeListener;
+import org.netbeans.api.java.source.SourceUtils;
 import org.netbeans.api.project.Project;
 import org.netbeans.api.queries.FileBuiltQuery;
+import static org.netbeans.modules.gradle.java.api.GradleJavaSourceSet.SourceType.JAVA;
 import org.netbeans.spi.project.ProjectServiceProvider;
 import org.netbeans.spi.project.ui.ProjectOpenedHook;
 import org.netbeans.spi.queries.FileBuiltQueryImplementation;
@@ -119,8 +121,13 @@
             if (sourceSet != null) {
                 String relFile = sourceSet.relativePath(f);
                 String relClass = relFile.substring(0, relFile.lastIndexOf('.')) + ".class"; //NOI18N
+                String moduleRoot = null;
+                File moduleInfo = sourceSet.findResource("module-info.java", false, JAVA); //NOI18N
+                if (moduleInfo != null && sourceSet.getCompilerArgs(JAVA).contains("--module-source-path")) {
+                    moduleRoot = SourceUtils.parseModuleName(FileUtil.toFileObject(moduleInfo));
+                }
                 try {
-                    ret = new StatusImpl(file, sourceSet.getOutputClassDirs(), relClass);
+                    ret = new StatusImpl(file, sourceSet.getOutputClassDirs(), relClass, moduleRoot);
                 } catch (DataObjectNotFoundException ex) {}
             }
 
@@ -147,6 +154,7 @@
         private final DataObject source;
         private final Set<File> roots;
         private final String relClass;
+        private final String moduleName;
         private final PropertyChangeListener pcl  = new PropertyChangeListener() {
             @Override
             public void propertyChange(PropertyChangeEvent evt) {
@@ -180,13 +188,15 @@
         };
         boolean status;
 
-        public StatusImpl(FileObject source, Set<File> roots, String relClass) throws DataObjectNotFoundException {
+        public StatusImpl(FileObject source, Set<File> roots, String relClass, String moduleName) throws DataObjectNotFoundException {
             this.roots = roots;
             this.relClass = relClass;
             this.source = DataObject.find(source);
+            this.moduleName = moduleName;
             this.source.addPropertyChangeListener(WeakListeners.propertyChange(pcl, this.source));
             for (File root : roots) {
-                FileUtil.addFileChangeListener(listener, FileUtil.normalizeFile(new File(root, relClass)));
+                File moduleRoot = moduleName == null ? root : new File(root, moduleName);
+                FileUtil.addFileChangeListener(listener, FileUtil.normalizeFile(new File(moduleRoot, relClass)));
             }
             checkBuilt();
         }
@@ -211,7 +221,8 @@
             boolean built = false;
             if (fo != null) {
                 for (File root : roots) {
-                    File target = FileUtil.normalizeFile(new File(root, relClass));
+                    File moduleRoot = moduleName == null ? root : new File(root, moduleName);
+                    File target = FileUtil.normalizeFile(new File(moduleRoot, relClass));
                     if (target.exists()) {
                         long sourceTime = fo.lastModified().getTime();
                         long targetTime = target.lastModified();
diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleSourceForBinary.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleSourceForBinary.java
index 2222875..4a5a4ff 100644
--- a/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleSourceForBinary.java
+++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/queries/GradleSourceForBinary.java
@@ -19,6 +19,8 @@
 
 package org.netbeans.modules.gradle.java.queries;
 
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import org.netbeans.modules.gradle.java.api.GradleJavaSourceSet;
 import org.netbeans.modules.gradle.java.api.GradleJavaSourceSet.SourceType;
 import static org.netbeans.modules.gradle.java.api.GradleJavaSourceSet.SourceType.*;
@@ -38,12 +40,15 @@
 import javax.swing.event.ChangeListener;
 import org.netbeans.api.java.queries.SourceForBinaryQuery;
 import org.netbeans.api.project.Project;
+import static org.netbeans.modules.gradle.api.NbGradleProject.Quality.*;
 import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation;
 import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
 import org.netbeans.spi.project.ProjectServiceProvider;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileUtil;
+import org.openide.util.ChangeSupport;
 import org.openide.util.Utilities;
+import org.openide.util.WeakListeners;
 
 /**
  *
@@ -65,15 +70,17 @@
         if (ret == null) {
             try {
                 NbGradleProject watcher = NbGradleProject.get(project);
-                if (watcher.getQuality().atLeast(NbGradleProject.Quality.FULL)) {
+                if (watcher.getQuality().atLeast(FALLBACK)) {
                     GradleJavaProject prj = GradleJavaProject.get(project);
                     switch (binaryRoot.getProtocol()) {
                         case "file": {  //NOI18N
                             File root = FileUtil.normalizeFile(Utilities.toFile(binaryRoot.toURI()));
                             for (GradleJavaSourceSet ss : prj.getSourceSets().values()) {
+                                File outputDir = ss.getCompilerArgs(JAVA).contains("--module-source-path") ? //NOI18N
+                                        root.getParentFile() : root;
                                 for (File dir : ss.getOutputClassDirs()) {
-                                    if (root.equals(dir)) {
-                                        ret = new Res(project, ss.getName(), EnumSet.of(JAVA, GROOVY, SCALA));
+                                    if (outputDir.equals(dir)) {
+                                        ret = new Res(project, ss.getName(), EnumSet.of(JAVA, GROOVY, SCALA, GENERATED));
                                         break;
                                     }
                                 }
@@ -118,11 +125,19 @@
         private final Project project;
         private final String sourceSet;
         private final Set<SourceType> sourceTypes;
+        private final PropertyChangeListener listener;
+        private final ChangeSupport support = new ChangeSupport(this);
 
         public Res(Project project, String sourceSet, Set<SourceType> sourceTypes) {
             this.project = project;
             this.sourceSet = sourceSet;
             this.sourceTypes = sourceTypes;
+            listener = (PropertyChangeEvent evt) -> {
+                if (NbGradleProject.PROP_PROJECT_INFO.equals(evt.getPropertyName())) {
+                    support.fireChange();
+                }
+            };
+            NbGradleProject.get(project).addPropertyChangeListener(WeakListeners.propertyChange(listener, project));
         }
 
         @Override
@@ -152,10 +167,16 @@
 
         @Override
         public void addChangeListener(ChangeListener l) {
+            synchronized (support) {
+                support.addChangeListener(l);
+            }
         }
 
         @Override
         public void removeChangeListener(ChangeListener l) {
+            synchronized (support) {
+                support.removeChangeListener(l);
+            }
         }
 
     }
diff --git a/java/java.completion/src/org/netbeans/modules/java/completion/JavaCompletionTask.java b/java/java.completion/src/org/netbeans/modules/java/completion/JavaCompletionTask.java
index b6bbb7f..ef40212 100644
--- a/java/java.completion/src/org/netbeans/modules/java/completion/JavaCompletionTask.java
+++ b/java/java.completion/src/org/netbeans/modules/java/completion/JavaCompletionTask.java
@@ -199,7 +199,7 @@
     private static final String WHILE_KEYWORD = "while"; //NOI18N
     private static final String WITH_KEYWORD = "with"; //NOI18N
     private static final String YIELD_KEYWORD = "yield"; //NOI18N
-    
+    private static final String RECORD_KEYWORD = "record"; //NOI18N
     private static final String JAVA_LANG_CLASS = "java.lang.Class"; //NOI18N
     private static final String JAVA_LANG_OBJECT = "java.lang.Object"; //NOI18N
     private static final String JAVA_LANG_ITERABLE = "java.lang.Iterable"; //NOI18N
@@ -237,9 +237,9 @@
     private static final SourceVersion SOURCE_VERSION_RELEASE_10;
     private static final SourceVersion SOURCE_VERSION_RELEASE_11;
     private static final SourceVersion SOURCE_VERSION_RELEASE_13;
-
+    private static final SourceVersion SOURCE_VERSION_RELEASE_14;
     static {
-        SourceVersion r10, r11, r13;
+        SourceVersion r10, r11, r13, r14;
 
         try {
             r10 = SourceVersion.valueOf("RELEASE_10");
@@ -256,10 +256,16 @@
         } catch (IllegalArgumentException ex) {
             r13 = null;
         }
+         try {
+            r14 = SourceVersion.valueOf("RELEASE_14");
+        } catch (IllegalArgumentException ex) {
+            r14 = null;
+        }
 
         SOURCE_VERSION_RELEASE_10 = r10;
         SOURCE_VERSION_RELEASE_11 = r11;
         SOURCE_VERSION_RELEASE_13 = r13;
+        SOURCE_VERSION_RELEASE_14 = r14;
     }
 
     private final ItemFactory<T> itemFactory;
@@ -502,6 +508,8 @@
             default:
                 if (path.getLeaf().getKind().toString().equals(TreeShims.SWITCH_EXPRESSION)) {
                     insideSwitch(env);
+                } else if (TreeShims.isRecord(path.getLeaf())) {
+                    insideRecord(env);
                 }
                 break;
         }
@@ -726,7 +734,11 @@
             addPackages(env, null, false);
         }
         if (options.contains(Options.ALL_COMPLETION)) {
-            addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE), null);
+            EnumSet<ElementKind> classKinds = EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE);
+            if (isRecordSupported(env)) {
+                classKinds.add(TreeShims.getRecordKind());
+            }
+            addTypes(env, classKinds, null);
         } else {
             hasAdditionalClasses = true;
         }
@@ -748,7 +760,7 @@
         int idx = headerText.indexOf('{'); //NOI18N
         if (idx >= 0) {
             addKeywordsForClassBody(env);
-            addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+            addClassTypes(env, null);
             addElementCreators(env);
             return;
         }
@@ -866,7 +878,7 @@
             addClassModifiers(env, cls.getModifiers().getFlags());
         } else {
             addMemberModifiers(env, cls.getModifiers().getFlags(), false);
-            addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+            addClassTypes(env, null);
         }
     }
 
@@ -919,7 +931,7 @@
                 }
                 boolean isLocal = !TreeUtilities.CLASS_TREE_KINDS.contains(parent.getKind());
                 addMemberModifiers(env, var.getModifiers().getFlags(), isLocal);
-                addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+                addClassTypes(env, null);
                 ModifiersTree mods = var.getModifiers();
                 if (mods.getFlags().isEmpty() && mods.getAnnotations().isEmpty()) {
                     addElementCreators(env);
@@ -1023,7 +1035,7 @@
             switch (lastToken.token().id()) {
                 case LPAREN:
                     addMemberModifiers(env, Collections.<Modifier>emptySet(), true);
-                    addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+                    addClassTypes(env, null);
                     break;
                 case RPAREN:
                     Tree mthParent = path.getParentPath().getLeaf();
@@ -1062,13 +1074,13 @@
                 case GTGTGT:
                     addPrimitiveTypeKeywords(env);
                     addKeyword(env, VOID_KEYWORD, SPACE, false);
-                    addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+                    addClassTypes(env, null);
                     break;
                 case COMMA:
                     switch (state) {
                         case 3:
                             addMemberModifiers(env, Collections.<Modifier>emptySet(), true);
-                            addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+                            addClassTypes(env, null);
                             break;
                         case 4:
                             if (!options.contains(Options.ALL_COMPLETION) && mth.getBody() != null) {
@@ -1110,7 +1122,7 @@
         switch (state) {
             case 0:
                 addMemberModifiers(env, mth.getModifiers().getFlags(), false);
-                addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+                addClassTypes(env, null);
                 break;
             case 1:
                 if (((TypeParameterTree) lastTree).getBounds().isEmpty()) {
@@ -1183,10 +1195,10 @@
             addClassModifiers(env, m);
         } else if (parent.getKind() != Tree.Kind.VARIABLE || grandParent == null || TreeUtilities.CLASS_TREE_KINDS.contains(grandParent.getKind())) {
             addMemberModifiers(env, m, false);
-            addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+            addClassTypes(env, null);
         } else if (parent.getKind() == Tree.Kind.VARIABLE && grandParent.getKind() == Tree.Kind.METHOD) {
             addMemberModifiers(env, m, true);
-            addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+            addClassTypes(env, null);
         } else {
             localResult(env);
             addKeywordsForBlock(env);
@@ -1296,7 +1308,7 @@
         if (pos >= 0 && pos < offset) {
             insideExpression(env, new TreePath(env.getPath(), att.getUnderlyingType()));
         } else {
-            addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+            addClassTypes(env, null);
         }
     }
 
@@ -1452,7 +1464,7 @@
                             }
                         }
                     }
-                    addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+                    addClassTypes(env, null);
                     break;
                 case QUESTION:
                     addKeyword(env, EXTENDS_KEYWORD, SPACE, false);
@@ -1471,7 +1483,7 @@
         String text = env.getController().getText().substring(blockPos, offset);
         if (text.indexOf('{') < 0) { //NOI18N
             addMemberModifiers(env, Collections.singleton(STATIC), false);
-            addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+            addClassTypes(env, null);
             return;
         }
         StatementTree last = null;
@@ -1592,9 +1604,11 @@
                     break;
                 case LT:
                 case COMMA:
+                    addClassTypes(env, null);
+                    break;
                 case EXTENDS:
                 case SUPER:
-                    addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+                    addClassTypes(env, null);
                     break;
             }
         } else if (lastNonWhitespaceTokenId != JavaTokenId.STAR) {
@@ -1922,7 +1936,7 @@
                     break;
                 case LT:
                 case COMMA:
-                    addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+                    addClassTypes(env, null);
                     break;
             }
         }
@@ -1941,7 +1955,7 @@
                 case COMMA:
                     if (let.getParameters().isEmpty()
                             || env.getController().getTrees().getSourcePositions().getStartPosition(path.getCompilationUnit(), let.getParameters().get(0).getType()) >= 0) {
-                        addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+                        addClassTypes(env, null);
                         addPrimitiveTypeKeywords(env);
                         addKeyword(env, FINAL_KEYWORD, SPACE, false);
                     }
@@ -1984,7 +1998,7 @@
         }
         addLocalMembersAndVars(env);
         addValueKeywords(env);
-        addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+        addClassTypes(env,null);
         addPrimitiveTypeKeywords(env);
     }
 
@@ -2057,7 +2071,11 @@
                         env.insideNew();
                     }
                     if (encl == null) {
-                        addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE), base);
+                        EnumSet<ElementKind> classKinds = EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE);
+                        if (isRecordSupported(env)) {
+                            classKinds.add(TreeShims.getRecordKind());
+                        }
+                        addTypes(env, classKinds, base);
                     } else {
                         TypeMirror enclType = controller.getTrees().getTypeMirror(new TreePath(path, nc.getEnclosingExpression()));
                         if (enclType != null && enclType.getKind() == TypeKind.DECLARED) {
@@ -2074,7 +2092,7 @@
                     }
                     addLocalMembersAndVars(env);
                     addValueKeywords(env);
-                    addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+                    addClassTypes(env,null);
                     addPrimitiveTypeKeywords(env);
                     break;
                 case GT:
@@ -2241,7 +2259,7 @@
             TokenSequence<JavaTokenId> last = findLastNonWhitespaceToken(env, fl, offset);
             if (last != null && last.token().id() == JavaTokenId.LPAREN) {
                 addLocalFieldsAndVars(env);
-                addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+                addClassTypes(env,null);
                 addPrimitiveTypeKeywords(env);
             }
         } else {
@@ -2471,7 +2489,7 @@
                 }
             }
             addLocalMembersAndVars(env);
-            addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+            addClassTypes(env,null);
             addPrimitiveTypeKeywords(env);
             addValueKeywords(env);
         } else {
@@ -2483,7 +2501,7 @@
         InstanceOfTree iot = (InstanceOfTree) env.getPath().getLeaf();
         TokenSequence<JavaTokenId> ts = findLastNonWhitespaceToken(env, iot, env.getOffset());
         if (ts != null && ts.token().id() == JavaTokenId.INSTANCEOF) {
-            addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+            addClassTypes(env, null);
         }
     }
 
@@ -3008,10 +3026,193 @@
             }
         }
     }
+    
+    private void addClassTypes(final Env env, DeclaredType baseType) throws IOException{
+        EnumSet<ElementKind> classKinds = EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER);
+        if (isRecordSupported(env)) {
+            classKinds.add(TreeShims.getRecordKind());
+        }
+        addTypes(env, classKinds, baseType);
+    }
+
+    private boolean isRecordSupported(final Env env) {
+        return (SOURCE_VERSION_RELEASE_14 != null && env.getController().getSourceVersion().compareTo(SOURCE_VERSION_RELEASE_14) >= 0);
+    }
+
+    private void insideRecord(Env env) throws IOException {
+        int offset = env.getOffset();
+        env.insideClass();
+        TreePath path = env.getPath();
+        ClassTree cls = (ClassTree) path.getLeaf();
+        CompilationController controller = env.getController();
+        SourcePositions sourcePositions = env.getSourcePositions();
+        CompilationUnitTree root = env.getRoot();
+        int startPos = (int) sourcePositions.getEndPosition(root, cls.getModifiers());
+        if (startPos <= 0) {
+            startPos = (int) sourcePositions.getStartPosition(root, cls);
+        }
+        String headerText = controller.getText().substring(startPos, offset);
+        int idx = headerText.indexOf('{'); //NOI18N
+        if (idx >= 0) {
+            addKeywordsForClassBody(env);
+            addClassTypes(env, null);
+            addElementCreators(env);
+            return;
+        }
+        TreeUtilities tu = controller.getTreeUtilities();
+        Tree lastImpl = null;
+        for (Tree impl : cls.getImplementsClause()) {
+            int implPos = (int) sourcePositions.getEndPosition(root, impl);
+            if (implPos == Diagnostic.NOPOS || offset <= implPos) {
+                break;
+            }
+            lastImpl = impl;
+            startPos = implPos;
+        }
+        if (lastImpl != null) {
+            TokenSequence<JavaTokenId> last = findLastNonWhitespaceToken(env, startPos, offset);
+            if (last != null && last.token().id() == JavaTokenId.COMMA) {
+                controller.toPhase(Phase.ELEMENTS_RESOLVED);
+                env.addToExcludes(controller.getTrees().getElement(path));
+                addTypes(env, EnumSet.of(INTERFACE, ANNOTATION_TYPE), null);
+            }
+            return;
+        }
+        List<? extends Tree> members = cls.getMembers();
+
+        Tree lastParam = null;
+        for (Tree member : members) {
+            if (member.getKind() == Tree.Kind.VARIABLE) {
+                ModifiersTree modifiers = ((VariableTree) member).getModifiers();
+                Set<Modifier> modifierSet = modifiers.getFlags();
+
+                if (!modifierSet.contains(Modifier.STATIC)) {
+                    int paramPos = (int) sourcePositions.getEndPosition(root, member);
+                    if (paramPos == Diagnostic.NOPOS || offset <= paramPos) {
+                        break;
+                    }
+                    lastParam = member;
+                    startPos = paramPos;
+                }
+            }
+
+            if (lastParam != null) {
+                TokenSequence<JavaTokenId> first = findFirstNonWhitespaceToken(env, startPos, offset);
+                if (first != null && first.token().id() == JavaTokenId.COMMA) {
+                    controller.toPhase(Phase.ELEMENTS_RESOLVED);
+                    env.addToExcludes(controller.getTrees().getElement(path));
+                    addTypes(env, EnumSet.of(INTERFACE, ANNOTATION_TYPE), null);
+                    return;
+                }
+                if (first != null && first.token().id() == JavaTokenId.RPAREN) {
+                    first = nextNonWhitespaceToken(first);
+                    if (!tu.isInterface(cls) && first.token().id() == JavaTokenId.LBRACE) {
+                        addKeyword(env, IMPLEMENTS_KEYWORD, SPACE, false);
+                    }
+
+                }
+                return;
+            }
+
+        }
+
+        TypeParameterTree lastTypeParam = null;
+        for (TypeParameterTree tp : cls.getTypeParameters()) {
+            int tpPos = (int) sourcePositions.getEndPosition(root, tp);
+            if (tpPos == Diagnostic.NOPOS || offset <= tpPos) {
+                break;
+            }
+            lastTypeParam = tp;
+            startPos = tpPos;
+        }
+
+        TokenSequence<JavaTokenId> lastNonWhitespaceToken = findLastNonWhitespaceToken(env, startPos, offset);
+        if (lastNonWhitespaceToken != null) {
+            switch (lastNonWhitespaceToken.token().id()) {
+                case LPAREN:
+                    addMemberModifiers(env, Collections.<Modifier>emptySet(), true);
+                    addClassTypes(env, null);
+                    break;
+                case IMPLEMENTS:
+                    controller.toPhase(Phase.ELEMENTS_RESOLVED);
+                    env.addToExcludes(controller.getTrees().getElement(path));
+                    addTypes(env, EnumSet.of(INTERFACE, ANNOTATION_TYPE), null);
+                    break;
+                case RPAREN:
+                    if (!tu.isAnnotation(cls)) {
+                        if (!tu.isInterface(cls)) {
+                            addKeyword(env, IMPLEMENTS_KEYWORD, SPACE, false);
+                        }
+                    }
+                    break;
+            }
+            return;
+        }
+
+        if (lastTypeParam != null) {
+            TokenSequence<JavaTokenId> first = findFirstNonWhitespaceToken(env, startPos, offset);
+
+            if (first != null && (first.token().id() == JavaTokenId.GT
+                    || first.token().id() == JavaTokenId.GTGT
+                    || first.token().id() == JavaTokenId.GTGTGT)) {
+                first = nextNonWhitespaceToken(first);
+
+                TokenSequence<JavaTokenId> last = findLastNonWhitespaceToken(env, first.offset(), offset);
+                TokenSequence<JavaTokenId> old = first;
+                first = nextNonWhitespaceToken(first);
+                if (last != null && first.token().id() == last.token().id()) {
+                    first = nextNonWhitespaceToken(first);
+                } else {
+                    first = old;
+                }
+
+                if (first != null && first.offset() < offset) {
+                    if (first.token().id() == JavaTokenId.EXTENDS) {
+                        controller.toPhase(Phase.ELEMENTS_RESOLVED);
+                        env.afterExtends();
+                        env.addToExcludes(controller.getTrees().getElement(path));
+                        addTypes(env, tu.isInterface(cls) ? EnumSet.of(INTERFACE, ANNOTATION_TYPE) : EnumSet.of(CLASS), null);
+                        return;
+                    }
+                    if (first.token().id() == JavaTokenId.IMPLEMENTS) {
+                        controller.toPhase(Phase.ELEMENTS_RESOLVED);
+                        env.addToExcludes(controller.getTrees().getElement(path));
+                        addTypes(env, EnumSet.of(INTERFACE, ANNOTATION_TYPE), null);
+                        return;
+                    }
+                } else if (!tu.isAnnotation(cls)) {
+
+                    if (!tu.isInterface(cls) && first.token().id() == JavaTokenId.LBRACE) {
+                        addKeyword(env, IMPLEMENTS_KEYWORD, SPACE, false);
+                    } else if (!tu.isInterface(cls) && first.token().id() == JavaTokenId.RPAREN) {
+                        controller.toPhase(Phase.ELEMENTS_RESOLVED);
+                        env.addToExcludes(controller.getTrees().getElement(path));
+                        addTypes(env, EnumSet.of(INTERFACE, ANNOTATION_TYPE), null);
+                    }
+                    return;
+                }
+            } else if (lastTypeParam.getBounds().isEmpty()) {
+                addKeyword(env, EXTENDS_KEYWORD, SPACE, false);
+            }
+            return;
+        }
+
+        lastNonWhitespaceToken = findLastNonWhitespaceToken(env, (int) sourcePositions.getStartPosition(root, cls), offset);
+        if (lastNonWhitespaceToken != null && lastNonWhitespaceToken.token().id() == JavaTokenId.AT) {
+            addKeyword(env, INTERFACE_KEYWORD, SPACE, false);
+            addTypes(env, EnumSet.of(ANNOTATION_TYPE), null);
+        } else if (path.getParentPath().getLeaf().getKind() == Tree.Kind.COMPILATION_UNIT) {
+            addClassModifiers(env, cls.getModifiers().getFlags());
+        } else {
+            addMemberModifiers(env, cls.getModifiers().getFlags(), false);
+            addClassTypes(env, null);
+        }
+
+    }
 
     private void localResult(Env env) throws IOException {
         addLocalMembersAndVars(env);
-        addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+        addClassTypes(env, null);
         addPrimitiveTypeKeywords(env);
     }
 
@@ -3092,7 +3293,7 @@
                     break;
             }
         }
-        addTypes(env, EnumSet.of(CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, TYPE_PARAMETER), null);
+        addClassTypes(env, null);
     }
 
     private void addLocalMembersAndVars(final Env env) throws IOException {
@@ -3793,6 +3994,9 @@
     }
 
     private void addPackageContent(final Env env, PackageElement pe, EnumSet<ElementKind> kinds, DeclaredType baseType, boolean insideNew, boolean srcOnly) throws IOException {
+        if (isRecordSupported(env)) {
+            kinds.add(TreeShims.getRecordKind());
+        }
         Set<? extends TypeMirror> smartTypes = options.contains(Options.ALL_COMPLETION) ? null : getSmartTypes(env);
         CompilationController controller = env.getController();
         Elements elements = controller.getElements();
@@ -4306,6 +4510,9 @@
             kws.add(ENUM_KEYWORD);
             kws.add(FINAL_KEYWORD);
             kws.add(INTERFACE_KEYWORD);
+            if (isRecordSupported(env)) {
+                kws.add(RECORD_KEYWORD);
+            }
         }
         boolean beforeAnyClass = true;
         boolean beforePublicClass = true;
@@ -4374,6 +4581,9 @@
                 && env.getController().getTreeUtilities().getPathElementOfKind(Tree.Kind.INTERFACE, env.getPath()) != null) {
             results.add(itemFactory.createKeywordItem(DEFAULT_KEYWORD, SPACE, anchorOffset, false));
         }
+        if (isRecordSupported(env)) {
+            results.add(itemFactory.createKeywordItem(RECORD_KEYWORD, SPACE, anchorOffset, false));
+        }
         addPrimitiveTypeKeywords(env);
     }
 
@@ -4389,6 +4599,9 @@
                 results.add(itemFactory.createKeywordItem(kw, SPACE, anchorOffset, false));
             }
         }
+        if (isRecordSupported(env)) {
+            results.add(itemFactory.createKeywordItem(RECORD_KEYWORD, SPACE, anchorOffset, false));
+        }
         if (Utilities.startsWith(RETURN_KEYWORD, prefix)) {
             TreePath tp = env.getController().getTreeUtilities().getPathElementOfKind(EnumSet.of(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION), env.getPath());
             String postfix = SPACE;
@@ -4573,6 +4786,9 @@
         kws.add(CLASS_KEYWORD);
         kws.add(INTERFACE_KEYWORD);
         kws.add(ENUM_KEYWORD);
+        if (isRecordSupported(env)) {
+            kws.add(RECORD_KEYWORD);
+        }
         for (String kw : kws) {
             if (Utilities.startsWith(kw, prefix)) {
                 results.add(itemFactory.createKeywordItem(kw, SPACE, anchorOffset, false));
@@ -4627,6 +4843,9 @@
             kws.add(CLASS_KEYWORD);
             kws.add(INTERFACE_KEYWORD);
             kws.add(ENUM_KEYWORD);
+            if (isRecordSupported(env)) {
+                kws.add(RECORD_KEYWORD);
+            }
         }
         for (String kw : kws) {
             if (Utilities.startsWith(kw, prefix)) {
diff --git a/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/override.pass b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/override.pass
new file mode 100644
index 0000000..be68102
--- /dev/null
+++ b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/override.pass
@@ -0,0 +1 @@
+Override
diff --git a/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/record.pass b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/record.pass
new file mode 100644
index 0000000..d4141f0
--- /dev/null
+++ b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/record.pass
@@ -0,0 +1 @@
+record
diff --git a/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/typesRecordLocalMembersAndVars.pass b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/typesRecordLocalMembersAndVars.pass
new file mode 100644
index 0000000..7bc4924
--- /dev/null
+++ b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/typesRecordLocalMembersAndVars.pass
@@ -0,0 +1,8 @@
+R
+R1
+Readable
+ReflectiveOperationException
+Runnable
+Runtime
+RuntimeException
+RuntimePermission
\ No newline at end of file
diff --git a/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/typesRecordStaticMembersAndVars.pass b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/typesRecordStaticMembersAndVars.pass
new file mode 100644
index 0000000..7bc4924
--- /dev/null
+++ b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/typesRecordStaticMembersAndVars.pass
@@ -0,0 +1,8 @@
+R
+R1
+Readable
+ReflectiveOperationException
+Runnable
+Runtime
+RuntimeException
+RuntimePermission
\ No newline at end of file
diff --git a/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/14/typesRecordLocalMembersAndVars.pass b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/14/typesRecordLocalMembersAndVars.pass
new file mode 100644
index 0000000..6acc0ac
--- /dev/null
+++ b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/14/typesRecordLocalMembersAndVars.pass
@@ -0,0 +1,7 @@
+Readable
+Record
+ReflectiveOperationException
+Runnable
+Runtime
+RuntimeException
+RuntimePermission
\ No newline at end of file
diff --git a/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/14/typesRecordStaticMembersAndVars.pass b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/14/typesRecordStaticMembersAndVars.pass
new file mode 100644
index 0000000..6acc0ac
--- /dev/null
+++ b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/14/typesRecordStaticMembersAndVars.pass
@@ -0,0 +1,7 @@
+Readable
+Record
+ReflectiveOperationException
+Runnable
+Runtime
+RuntimeException
+RuntimePermission
\ No newline at end of file
diff --git a/java/java.lsp.server/vscode/src/VerifyJDK11.java b/java/java.completion/test/unit/data/org/netbeans/modules/java/completion/data/Records.java
similarity index 76%
copy from java/java.lsp.server/vscode/src/VerifyJDK11.java
copy to java/java.completion/test/unit/data/org/netbeans/modules/java/completion/data/Records.java
index 80e5928..e1cb7ae 100644
--- a/java/java.lsp.server/vscode/src/VerifyJDK11.java
+++ b/java/java.completion/test/unit/data/org/netbeans/modules/java/completion/data/Records.java
@@ -16,9 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-public class VerifyJDK11 {
-    public static void main(String[] args) {
-        if (Runtime.version().compareTo(Runtime.Version.parse("11-build")) < 0)
-            throw new IllegalStateException("Not JDK 11+.");
-    }
+package test; 
+
+public class Test {
+
+ public record Records <R extends Number, R1 > ( )  {
+
+    public static 
 }
+    
+    public static void main(String args )
+    {
+        record Record1(@Ov )  {}
+        
+        record Rec( )  {}        
+    }
+    }
diff --git a/java/java.completion/test/unit/src/org/netbeans/modules/java/completion/JavaCompletionTask114FeaturesTest.java b/java/java.completion/test/unit/src/org/netbeans/modules/java/completion/JavaCompletionTask114FeaturesTest.java
index 1f4b212..4cdfe27 100644
--- a/java/java.completion/test/unit/src/org/netbeans/modules/java/completion/JavaCompletionTask114FeaturesTest.java
+++ b/java/java.completion/test/unit/src/org/netbeans/modules/java/completion/JavaCompletionTask114FeaturesTest.java
@@ -48,7 +48,36 @@
     public void testBindingUse() throws Exception {
         performTest("GenericMethodInvocation", 1231, "boolean b = argO instanceof String str && st", "BindingUse.pass", SOURCE_LEVEL);
     }
+    
 
+    public void testBeforeLeftRecordBraces() throws Exception {
+        performTest("Records", 896, null, "implementsKeyword.pass", SOURCE_LEVEL);
+    }
+        
+        public void testBeforeRecParamsLeftParen() throws Exception {
+        performTest("Records", 892, null, "empty.pass", SOURCE_LEVEL);
+    }
+
+    public void testInsideRecParams() throws Exception {
+        performTest("Records", 894, "R", "typesRecordLocalMembersAndVars.pass", SOURCE_LEVEL);
+    }
+    
+    public void testAfterTypeParamInRecParam() throws Exception {
+        performTest("Records", 890, null, "extendsKeyword.pass", SOURCE_LEVEL);
+    }
+    
+    public void testInsideRecAfterStaticKeyWord() throws Exception {
+        performTest("Records", 918, "R", "typesRecordStaticMembersAndVars.pass", SOURCE_LEVEL);
+    }
+    
+    public void testAnnotationInRecordParam() throws Exception {
+        performTest("Records", 999, null, "override.pass", SOURCE_LEVEL);
+    } 
+    
+    public void testRecordKeywordInsideClass() throws Exception {
+        performTest("Records", 1014, "rec", "record.pass", SOURCE_LEVEL);
+    } 
+    
     public void noop() {
     }
 
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 4fd3a3a..f50ccdb 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, null, null, null, false);
+                        target[0] = new GoToTarget(-1, -1, null, null, null, false);
                         return;
                     }
                     
@@ -210,7 +210,7 @@
                         if (url != null) {
                             HtmlBrowser.URLDisplayer.getDefault().showURL(url);
                         } else {
-                            target[0] = new GoToTarget(-1, null, null, null, false);
+                            target[0] = new GoToTarget(-1, -1, null, null, null, false);
                         }
                     } else {
                         target[0] = computeGoToTarget(controller, resolved, offset);
@@ -255,20 +255,23 @@
             if (startPos != (-1)) {
                 //check if the caret is inside the declaration itself, as jump in this case is not very usefull:
                 if (isCaretInsideDeclarationName(controller, tree, elpath, offset)) {
-                    return new GoToTarget(-1, null, null, null, false);
+                    return new GoToTarget(-1, -1, null, null, null, false);
                 } else {
+                    long endPos = controller.getTrees().getSourcePositions().getEndPosition(controller.getCompilationUnit(), tree);
                     //#71272: it is necessary to translate the offset:
                     return new GoToTarget(controller.getSnapshot().getOriginalOffset((int) startPos),
+                                          controller.getSnapshot().getOriginalOffset((int) endPos),
                                           null,
                                           null,
                                           controller.getElementUtilities().getElementName(resolved.resolved, false).toString(),
                                           true);
                 }
             } else {
-                return new GoToTarget(-1, null, null, null, false);
+                return new GoToTarget(-1, -1, null, null, null, false);
             }
         } else {
             return new GoToTarget(-1,
+                                  -1,
                                   controller.getClasspathInfo(),
                                   ElementHandle.create(resolved.resolved),
                                   controller.getElementUtilities().getElementName(resolved.resolved, false).toString(),
@@ -278,13 +281,15 @@
 
     public static final class GoToTarget {
         public final int offsetToOpen;
+        public final int endPos;
         public final ClasspathInfo cpInfo;
         public final ElementHandle elementToOpen;
         public final String displayNameForError;
         public final boolean success;
 
-        public GoToTarget(int offsetToOpen, ClasspathInfo cpInfo, ElementHandle elementToOpen, String displayNameForError, boolean success) {
+        public GoToTarget(int offsetToOpen, int endPos, ClasspathInfo cpInfo, ElementHandle elementToOpen, String displayNameForError, boolean success) {
             this.offsetToOpen = offsetToOpen;
+            this.endPos = endPos;
             this.cpInfo = cpInfo;
             this.elementToOpen = elementToOpen;
             this.displayNameForError = displayNameForError;
diff --git a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionItem.java b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionItem.java
index 39c1387..181e5e0 100644
--- a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionItem.java
+++ b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionItem.java
@@ -120,7 +120,9 @@
             case ANNOTATION_TYPE:
                 return new AnnotationTypeItem(info, elem, type, 0, substitutionOffset, referencesCount, isDeprecated, insideNew, addSimpleName, smartType, autoImportEnclosingType, whiteList);
             default:
-                throw new IllegalArgumentException("kind=" + elem.getKind());
+                if(elem.getKind().name().equals(TreeShims.RECORD))
+                    return new RecordItem(info, elem, type, 0, substitutionOffset, referencesCount, isDeprecated, insideNew, addSimpleName, smartType, autoImportEnclosingType, whiteList);
+                else throw new IllegalArgumentException("kind=" + elem.getKind());
         }
     }
 
@@ -1313,7 +1315,25 @@
             return icon;
         }
     }
+    
+    static class RecordItem extends ClassItem {
 
+        private static final String RECORD = "org/netbeans/modules/editor/resources/completion/record.png"; // NOI18N
+        private static ImageIcon icon;
+
+        private RecordItem(CompilationInfo info, TypeElement elem, DeclaredType type, int dim, int substitutionOffset, ReferencesCount referencesCount, boolean isDeprecated, boolean insideNew, boolean addSimpleName, boolean smartType, boolean autoImport, WhiteListQuery.WhiteList whiteList) {
+            super(info, elem, type, dim, substitutionOffset, referencesCount, isDeprecated, insideNew, false, addSimpleName, smartType, autoImport, whiteList);
+        }
+
+        @Override
+        protected ImageIcon getBaseIcon() {
+            if (icon == null) {
+                icon = ImageUtilities.loadImageIcon(RECORD, false);
+            }
+            return icon;
+        }
+    }
+    
     static class AnnotationTypeItem extends ClassItem {
 
         private static final String ANNOTATION = "org/netbeans/modules/editor/resources/completion/annotation_type.png"; // NOI18N
diff --git a/java/java.editor/src/org/netbeans/modules/editor/java/TypingCompletion.java b/java/java.editor/src/org/netbeans/modules/editor/java/TypingCompletion.java
index d8867d0..af6d8cc 100644
--- a/java/java.editor/src/org/netbeans/modules/editor/java/TypingCompletion.java
+++ b/java/java.editor/src/org/netbeans/modules/editor/java/TypingCompletion.java
@@ -76,7 +76,7 @@
         int caretOffset = context.isBackwardDelete() ? context.getOffset() - 1 : context.getOffset();
         if (removedChar == '\"') {
             if ((ts.token().id() == JavaTokenId.STRING_LITERAL && ts.offset() == caretOffset) ||
-                (ts.token().id() == JavaTokenId.MULTILINE_STRING_LITERAL && ts.offset() <= caretOffset - 2)) {
+                (ts.token().id() == JavaTokenId.MULTILINE_STRING_LITERAL && ts.offset() == caretOffset - 2)) {
                 context.getDocument().remove(caretOffset, 1);
             }
         } else if (removedChar == '\'') {
diff --git a/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/TypingCompletionUnitTest.java b/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/TypingCompletionUnitTest.java
index 3518d95..fa87409d 100644
--- a/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/TypingCompletionUnitTest.java
+++ b/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/TypingCompletionUnitTest.java
@@ -1375,6 +1375,18 @@
         ctx.assertDocumentTextEquals("t(\"\"\"\n  |\"\"\")");
     } 
     
+    public void testTextBlock6() throws Exception {
+        try {
+            SourceVersion.valueOf("RELEASE_13");
+        } catch (IllegalArgumentException ex) {
+            //OK, skip test:
+            return ;
+        }
+        Context ctx = new Context(new JavaKit(), "t(\"\"\"\n|\"\n\"\"\")");
+        ctx.typeChar('\f');
+        ctx.assertDocumentTextEquals("t(\"\"\"\n\n\"\"\")");
+    } 
+    
     public void testCorrectHandlingOfStringEscapes184059() throws Exception {
         assertTrue(isInsideString("foo\n\"bar|\""));
         assertTrue(isInsideString("foo\n\"bar\\\"|\""));
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/AddParameterOrLocalFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/AddParameterOrLocalFix.java
index a6b59f0..62ce234 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/AddParameterOrLocalFix.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/AddParameterOrLocalFix.java
@@ -31,7 +31,6 @@
 import com.sun.source.tree.VariableTree;
 import com.sun.source.util.TreePath;
 import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner;
-import java.io.IOException;
 import java.util.Arrays;
 import java.util.EnumSet;
 import java.util.Set;
@@ -44,42 +43,35 @@
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
-import org.netbeans.api.java.source.Task;
 import org.netbeans.api.java.source.CompilationInfo;
-import org.netbeans.api.java.source.JavaSource;
-import org.netbeans.api.java.source.JavaSource.Phase;
 import org.netbeans.api.java.source.TreeMaker;
-import org.netbeans.api.java.source.TreePathHandle;
 import org.netbeans.api.java.source.TreeUtilities;
 import org.netbeans.api.java.source.TypeMirrorHandle;
 import org.netbeans.api.java.source.WorkingCopy;
 import org.netbeans.modules.java.hints.errors.ErrorFixesFakeHint.FixKind;
-import org.netbeans.spi.editor.hints.ChangeInfo;
-import org.netbeans.spi.editor.hints.Fix;
 import org.openide.filesystems.FileObject;
 import org.netbeans.modules.java.hints.infrastructure.ErrorHintsProvider;
 import org.openide.util.NbBundle;
 import static org.netbeans.modules.java.hints.errors.Utilities.isEnhancedForLoopIdentifier;
-import org.netbeans.spi.editor.hints.EnhancedFix;
+import org.netbeans.spi.java.hints.JavaFix;
 
 
 /**
  *
  * @author Jan Lahoda
  */
-public class AddParameterOrLocalFix implements EnhancedFix {
+public class AddParameterOrLocalFix extends JavaFix {
     
     private FileObject file;
     private TypeMirrorHandle type;
     private String name;
     private ElementKind kind;
     
-    private TreePathHandle[] tpHandle;
-    
     public AddParameterOrLocalFix(CompilationInfo info,
                                   TypeMirror type, String name,
                                   ElementKind kind,
                                   int /*!!!Position*/ unresolvedVariable) {
+        super(info, info.getTreeUtilities().pathFor(unresolvedVariable + 1), getSortText(kind, name));
         this.file = info.getFileObject();
         if (type.getKind() == TypeKind.NULL || type.getKind() == TypeKind.NONE) {
             TypeElement te = info.getElements().getTypeElement("java.lang.Object"); // NOI18N
@@ -94,10 +86,6 @@
         }
         this.name = name;
         this.kind = kind;
-
-        TreePath treePath = info.getTreeUtilities().pathFor(unresolvedVariable + 1);
-        tpHandle = new TreePathHandle[1];
-        tpHandle[0] = TreePathHandle.create(treePath, info);
     }
 
     public String getText() {
@@ -110,75 +98,66 @@
         }
     }
 
-    public ChangeInfo implement() throws IOException {
-        //use the original cp-info so it is "sure" that the proposedType can be resolved:
-        JavaSource js = JavaSource.forFileObject(file);
+    @Override
+    protected void performRewrite(TransformationContext ctx) throws Exception {
+        WorkingCopy working = ctx.getWorkingCopy();
+        TypeMirror proposedType = type.resolve(working);
 
-        js.runModificationTask(new Task<WorkingCopy>() {
-            public void run(final WorkingCopy working) throws IOException {
-                working.toPhase(Phase.RESOLVED);
+        if (proposedType == null) {
+            ErrorHintsProvider.LOG.log(Level.INFO, "Cannot resolve proposed type."); // NOI18N
+            return;
+        }
 
-                TypeMirror proposedType = type.resolve(working);
+        TreeMaker make = working.getTreeMaker();
 
-                if (proposedType == null) {
-                    ErrorHintsProvider.LOG.log(Level.INFO, "Cannot resolve proposed type."); // NOI18N
+        //TreePath tp = working.getTreeUtilities().pathFor(unresolvedVariable + 1);
+        //Use TreePathHandle instead of position supplied as field (#143318)
+        TreePath tp = ctx.getPath();
+        if (tp == null || tp.getLeaf().getKind() != Kind.IDENTIFIER)
+            return;
+
+        switch (kind) {
+            case PARAMETER:
+                TreePath targetPath = findMethod(tp);
+
+                if (targetPath == null) {
+                    Logger.getLogger("global").log(Level.WARNING, "Add parameter - cannot find the method."); // NOI18N
                     return;
                 }
 
-                TreeMaker make = working.getTreeMaker();
+                MethodTree targetTree = (MethodTree) targetPath.getLeaf();
 
-                //TreePath tp = working.getTreeUtilities().pathFor(unresolvedVariable + 1);
-                //Use TreePathHandle instead of position supplied as field (#143318)
-                TreePath tp = tpHandle[0].resolve(working);
-                if (tp == null || tp.getLeaf().getKind() != Kind.IDENTIFIER)
+                Element el = working.getTrees().getElement(targetPath);
+                if (el == null) {
                     return;
-
-                switch (kind) {
-                    case PARAMETER:
-                        TreePath targetPath = findMethod(tp);
-
-                        if (targetPath == null) {
-                            Logger.getLogger("global").log(Level.WARNING, "Add parameter - cannot find the method."); // NOI18N
-                            return;
-                        }
-
-                        MethodTree targetTree = (MethodTree) targetPath.getLeaf();
-
-                        Element el = working.getTrees().getElement(targetPath);
-                        if (el == null) {
-                            return;
-                        }
-                        int index = targetTree.getParameters().size();
-
-                        if (el != null && (el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR)) {
-                            ExecutableElement ee = (ExecutableElement) el;
-
-                            if (ee.isVarArgs()) {
-                                index = ee.getParameters().size() - 1;
-                            }
-                        }
-
-                        MethodTree result = make.insertMethodParameter(targetTree, index, make.Variable(make.Modifiers(EnumSet.noneOf(Modifier.class)), name, make.Type(proposedType), null));
-
-                        working.rewrite(targetTree, result);
-                        break;
-                    case LOCAL_VARIABLE:
-                        if (ErrorFixesFakeHint.isCreateLocalVariableInPlace(ErrorFixesFakeHint.getPreferences(working.getFileObject(), FixKind.CREATE_LOCAL_VARIABLE)) || isEnhancedForLoopIdentifier(tp)) {
-                            resolveLocalVariable(working, tp, make, proposedType);
-                        } else {
-                            resolveLocalVariable55(working, tp, make, proposedType);
-                        }
-                        break;
-                    case RESOURCE_VARIABLE:
-                        resolveResourceVariable(working, tp, make, proposedType);
-                        break;
-                    default:
-                        throw new IllegalStateException(kind.name());
                 }
-            }
-        }).commit();
-        
-        return null;
+                int index = targetTree.getParameters().size();
+
+                if (el != null && (el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR)) {
+                    ExecutableElement ee = (ExecutableElement) el;
+
+                    if (ee.isVarArgs()) {
+                        index = ee.getParameters().size() - 1;
+                    }
+                }
+
+                MethodTree result = make.insertMethodParameter(targetTree, index, make.Variable(make.Modifiers(EnumSet.noneOf(Modifier.class)), name, make.Type(proposedType), null));
+
+                working.rewrite(targetTree, result);
+                break;
+            case LOCAL_VARIABLE:
+                if (ErrorFixesFakeHint.isCreateLocalVariableInPlace(ErrorFixesFakeHint.getPreferences(working.getFileObject(), FixKind.CREATE_LOCAL_VARIABLE)) || isEnhancedForLoopIdentifier(tp)) {
+                    resolveLocalVariable(working, tp, make, proposedType);
+                } else {
+                    resolveLocalVariable55(working, tp, make, proposedType);
+                }
+                break;
+            case RESOURCE_VARIABLE:
+                resolveResourceVariable(working, tp, make, proposedType);
+                break;
+            default:
+                throw new IllegalStateException(kind.name());
+        }
     }
 
     /** In case statement is an Assignment, replace it with variable declaration */
@@ -504,16 +483,15 @@
         return hash;
     }
     
-    @Override
-    public CharSequence getSortText() {
+    private static String getSortText(ElementKind kind, String name) {
         //see usage at org.netbeans.modules.editor.hints.FixData.getSortText(org.netbeans.spi.editor.hints.Fix):java.lang.CharSequence
     
         //creates ordering top to bottom: create resource>local variable>create field>create parameter
         //see org.netbeans.modules.java.hints.errors.CreateFieldFix.getSortText():java.lang.CharSequence
         switch (kind) {
-            case PARAMETER: return "Create 7000 " + getText();
-            case LOCAL_VARIABLE: return "Create 5000 " + getText();
-            case RESOURCE_VARIABLE: return "Create 3000 " + getText();
+            case PARAMETER: return "Create 7000 " + name;
+            case LOCAL_VARIABLE: return "Create 5000 " + name;
+            case RESOURCE_VARIABLE: return "Create 3000 " + name;
             default:
                 throw new IllegalStateException();
         }
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateElement.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateElement.java
index 0f02b90..a9e0895 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateElement.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateElement.java
@@ -494,11 +494,11 @@
 
             int identifierPos = (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), errorPath.getLeaf());
             if (ee != null && fixTypes.contains(ElementKind.PARAMETER) && !Utilities.isMethodHeaderInsideGuardedBlock(info, (MethodTree) firstMethod.getLeaf()))
-                result.add(new AddParameterOrLocalFix(info, type, simpleName, ElementKind.PARAMETER, identifierPos));
+                result.add(new AddParameterOrLocalFix(info, type, simpleName, ElementKind.PARAMETER, identifierPos).toEditorFix());
             if ((firstMethod != null || firstInitializer != null) && fixTypes.contains(ElementKind.LOCAL_VARIABLE) && ErrorFixesFakeHint.enabled(ErrorFixesFakeHint.FixKind.CREATE_LOCAL_VARIABLE))
-                result.add(new AddParameterOrLocalFix(info, type, simpleName, ElementKind.LOCAL_VARIABLE, identifierPos));
+                result.add(new AddParameterOrLocalFix(info, type, simpleName, ElementKind.LOCAL_VARIABLE, identifierPos).toEditorFix());
             if (fixTypes.contains(ElementKind.RESOURCE_VARIABLE))
-                result.add(new AddParameterOrLocalFix(info, type, simpleName, ElementKind.RESOURCE_VARIABLE, identifierPos));
+                result.add(new AddParameterOrLocalFix(info, type, simpleName, ElementKind.RESOURCE_VARIABLE, identifierPos).toEditorFix());
         }
 
         return result;
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/ImportClass.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/ImportClass.java
index 11ae366..15018a2 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/ImportClass.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/ImportClass.java
@@ -532,7 +532,7 @@
         
     }
     
-    static final class FixImport extends FixBase {
+    public static final class FixImport extends FixBase {
         private final @NullAllowed TreePathHandle replacePathHandle;
         private final @NullAllowed String suffix;
         private final boolean statik;
@@ -741,5 +741,10 @@
                 s.save();
             }
         }
+
+        public ElementHandle<Element> getToImport() {
+            return toImport;
+        }
+
     }
 }
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/suggestions/ExpectedTypeResolver.java b/java/java.hints/src/org/netbeans/modules/java/hints/suggestions/ExpectedTypeResolver.java
index 8bf7951..a2424e5 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/suggestions/ExpectedTypeResolver.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/suggestions/ExpectedTypeResolver.java
@@ -25,6 +25,7 @@
 import com.sun.source.tree.AssertTree;
 import com.sun.source.tree.AssignmentTree;
 import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.BindingPatternTree;
 import com.sun.source.tree.BlockTree;
 import com.sun.source.tree.BreakTree;
 import com.sun.source.tree.CaseTree;
@@ -67,6 +68,7 @@
 import com.sun.source.tree.RequiresTree;
 import com.sun.source.tree.ReturnTree;
 import com.sun.source.tree.Scope;
+import com.sun.source.tree.SwitchExpressionTree;
 import com.sun.source.tree.SwitchTree;
 import com.sun.source.tree.SynchronizedTree;
 import com.sun.source.tree.ThrowTree;
@@ -81,6 +83,7 @@
 import com.sun.source.tree.VariableTree;
 import com.sun.source.tree.WhileLoopTree;
 import com.sun.source.tree.WildcardTree;
+import com.sun.source.tree.YieldTree;
 import com.sun.source.util.SourcePositions;
 import com.sun.source.util.TreePath;
 import com.sun.tools.javac.code.Type.CapturedType;
@@ -1388,4 +1391,16 @@
     public List<? extends TypeMirror> visitUses(UsesTree node, Object p) {
         return null;
     }
+   
+    public List<? extends TypeMirror> visitBindingPattern(BindingPatternTree bpt, Object p) {
+        return null;
+    }
+    
+    public List<? extends TypeMirror> visitSwitchExpression(SwitchExpressionTree set, Object p) {
+        return null;
+    }
+
+    public List<? extends TypeMirror> visitYield(YieldTree yt, Object p) {
+        return null;
+    }
 }
diff --git a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/AddParameterOrLocalFixTest.java b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/AddParameterOrLocalFixTest.java
index 1e5d163..8c8b876 100644
--- a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/AddParameterOrLocalFixTest.java
+++ b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/AddParameterOrLocalFixTest.java
@@ -25,6 +25,7 @@
 import org.netbeans.api.java.source.CompilationInfo;
 import org.netbeans.modules.java.hints.errors.ErrorFixesFakeHint.FixKind;
 import org.netbeans.modules.java.hints.infrastructure.ErrorHintsTestBase;
+import org.netbeans.modules.java.hints.spiimpl.JavaFixImpl;
 import org.netbeans.spi.editor.hints.Fix;
 
 /**
@@ -473,7 +474,7 @@
         List<Fix> result=  new LinkedList<Fix>();
 
         for (Fix f : fixes) {
-            if (f instanceof AddParameterOrLocalFix) {
+            if (f instanceof JavaFixImpl && ((JavaFixImpl) f).jf instanceof AddParameterOrLocalFix) {
                 result.add(f);
             }
         }
@@ -483,6 +484,6 @@
 
     @Override
     protected String toDebugString(CompilationInfo info, Fix f) {
-        return ((AddParameterOrLocalFix) f).toDebugString(info);
+        return ((AddParameterOrLocalFix) ((JavaFixImpl) f).jf).toDebugString(info);
     }
 }
diff --git a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/CreateElementTest.java b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/CreateElementTest.java
index 981399d..5b54e6b 100644
--- a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/CreateElementTest.java
+++ b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/CreateElementTest.java
@@ -25,6 +25,7 @@
 import java.util.Set;
 import javax.swing.text.Document;
 import org.netbeans.modules.java.hints.infrastructure.HintsTestBase;
+import org.netbeans.modules.java.hints.spiimpl.JavaFixImpl;
 import org.netbeans.spi.editor.hints.Fix;
 import org.openide.cookies.EditorCookie;
 import org.openide.loaders.DataObject;
@@ -457,8 +458,8 @@
                 real.add(((CreateFieldFix) f).toDebugString(info));
                 continue;
             }
-            if (f instanceof AddParameterOrLocalFix) {
-                real.add(((AddParameterOrLocalFix) f).toDebugString(info));
+            if (f instanceof JavaFixImpl && ((JavaFixImpl) f).jf instanceof AddParameterOrLocalFix) {
+                real.add(((AddParameterOrLocalFix) ((JavaFixImpl) f).jf).toDebugString(info));
                 continue;
             }
 	    if (f instanceof CreateMethodFix) {
diff --git a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/infrastructure/ErrorHintsTestBase.java b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/infrastructure/ErrorHintsTestBase.java
index 4921a3c..8f58303 100644
--- a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/infrastructure/ErrorHintsTestBase.java
+++ b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/infrastructure/ErrorHintsTestBase.java
@@ -45,6 +45,7 @@
 import org.netbeans.modules.editor.java.JavaKit;
 import org.netbeans.modules.java.JavaDataLoader;
 import org.netbeans.modules.java.hints.spi.ErrorRule;
+import org.netbeans.modules.java.hints.spiimpl.JavaFixImpl;
 import org.netbeans.modules.java.source.indexing.JavaCustomIndexer;
 import org.netbeans.modules.java.source.indexing.TransactionContext;
 import org.netbeans.modules.java.source.parsing.JavacParserFactory;
diff --git a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToPatternInstanceOfTest.java b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToPatternInstanceOfTest.java
index 14ea3d4..1982bbd 100644
--- a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToPatternInstanceOfTest.java
+++ b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToPatternInstanceOfTest.java
@@ -18,9 +18,9 @@
  */
 package org.netbeans.modules.java.hints.jdk;
 
-import javax.lang.model.SourceVersion;
 import org.netbeans.junit.NbTestCase;
 import org.netbeans.modules.java.hints.test.api.HintTest;
+import javax.lang.model.SourceVersion;
 
 /**
  *
@@ -44,6 +44,8 @@
                        "        return -1;\n" +
                        "    }\n" +
                        "}\n")
+                .sourceLevel(SourceVersion.latest().name())
+                .options("--enable-preview")
                 .run(ConvertToPatternInstanceOf.class)
                 .findWarning("3:8-3:10:verifier:" + Bundle.ERR_ConvertToPatternInstanceOf())
                 .applyFix()
@@ -72,6 +74,8 @@
                        "        }\n" +
                        "    }\n" +
                        "}\n")
+                .sourceLevel(SourceVersion.latest().name())
+                .options("--enable-preview")
                 .run(ConvertToPatternInstanceOf.class)
                 .findWarning("3:8-3:10:verifier:" + Bundle.ERR_ConvertToPatternInstanceOf())
                 .applyFix()
@@ -99,6 +103,8 @@
                        "        return -1;\n" +
                        "    }\n" +
                        "}\n")
+                .sourceLevel(SourceVersion.latest().name())
+                .options("--enable-preview")
                 .run(ConvertToPatternInstanceOf.class)
                 .findWarning("3:8-3:10:verifier:" + Bundle.ERR_ConvertToPatternInstanceOf())
                 .applyFix()
@@ -125,6 +131,8 @@
                        "        return -1;\n" +
                        "    }\n" +
                        "}\n")
+                .sourceLevel(SourceVersion.latest().name())
+                .options("--enable-preview")
                 .run(ConvertToPatternInstanceOf.class)
                 .findWarning("3:8-3:10:verifier:" + Bundle.ERR_ConvertToPatternInstanceOf())
                 .applyFix()
@@ -140,15 +148,4 @@
                               "}\n");
     }
 
-    @Override
-    protected void runTest() throws Throwable {
-        try {
-            SourceVersion.valueOf("RELEASE_14");
-        } catch (IllegalArgumentException ex) {
-            //OK, skip test
-            return ;
-        }
-        super.runTest();
-    }
-    
 }
diff --git a/java/java.lsp.server/build.xml b/java/java.lsp.server/build.xml
index 56cfe67..e70d4d1 100644
--- a/java/java.lsp.server/build.xml
+++ b/java/java.lsp.server/build.xml
@@ -80,7 +80,7 @@
         <exec executable="${build.dir}/vsce/node_modules/vsce/out/vsce" failonerror="true" dir="${basedir}/vscode">
             <arg value="package" />
             <arg value="--out" />
-            <arg value="${build.dir}/apache-netbeans-java-0.0.1.vsix" />
+            <arg value="${build.dir}/apache-netbeans-java-0.1.0.vsix" />
         </exec>
 
         <delete dir="${build.dir}/vscode" />
@@ -88,7 +88,7 @@
         <delete dir="${build.dir}/vscode-mandatory" />
         <mkdir dir="${build.dir}/vscode-mandatory/licenses" />
 
-        <unzip dest="${build.dir}/vscode" src="${build.dir}/apache-netbeans-java-0.0.1.vsix" />
+        <unzip dest="${build.dir}/vscode" src="${build.dir}/apache-netbeans-java-0.1.0.vsix" />
 
         <taskdef name="createlicensesummary" classname="org.netbeans.nbbuild.extlibs.CreateLicenseSummary" classpath="${nbantext.jar}"/>
 
@@ -110,6 +110,6 @@
                               includeAllFiles="true"
         />
 
-        <zip destfile="${build.dir}/apache-netbeans-java-0.0.1.vsix" basedir="${build.dir}/vscode-mandatory/" update="true" />
+        <zip destfile="${build.dir}/apache-netbeans-java-0.1.0.vsix" basedir="${build.dir}/vscode-mandatory/" update="true" />
     </target>
 </project>
diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml
index c5277b7..af6a370 100644
--- a/java/java.lsp.server/nbproject/project.xml
+++ b/java/java.lsp.server/nbproject/project.xml
@@ -323,6 +323,11 @@
                         <compile-dependency/>
                     </test-dependency>
                     <test-dependency>
+                        <code-name-base>org.netbeans.modules.java.source.base</code-name-base>
+                        <compile-dependency/>
+                        <test/>
+                    </test-dependency>
+                    <test-dependency>
                         <code-name-base>org.netbeans.modules.lexer.nbbridge</code-name-base>
                     </test-dependency>
                     <test-dependency>
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Server.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Server.java
index 218ccf8..de4455a 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Server.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Server.java
@@ -18,6 +18,7 @@
  */
 package org.netbeans.modules.java.lsp.server;
 
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -114,15 +115,7 @@
         LanguageServerImpl server = new LanguageServerImpl();
         Launcher<LanguageClient> serverLauncher = LSPLauncher.createServerLauncher(server, in, out);
         ((LanguageClientAware) server).connect(serverLauncher.getRemoteProxy());
-        serverLauncher.startListening();
-
-        while (true) {
-            try {
-                Thread.sleep(100000);
-            } catch (InterruptedException ex) {
-                //ignore
-            }
-        }
+        serverLauncher.startListening().get();
     }
 
     private static class LanguageServerImpl implements LanguageServer, LanguageClientAware {
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/text/TextDocumentServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/text/TextDocumentServiceImpl.java
index 583fd2e..b63c19b 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/text/TextDocumentServiceImpl.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/text/TextDocumentServiceImpl.java
@@ -36,11 +36,15 @@
 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.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -97,6 +101,7 @@
 import org.eclipse.lsp4j.DocumentSymbol;
 import org.eclipse.lsp4j.DocumentSymbolParams;
 import org.eclipse.lsp4j.Hover;
+import org.eclipse.lsp4j.InsertTextFormat;
 import org.eclipse.lsp4j.Location;
 import org.eclipse.lsp4j.MarkupContent;
 import org.eclipse.lsp4j.MessageParams;
@@ -123,10 +128,15 @@
 import org.netbeans.api.editor.document.LineDocumentUtils;
 import org.netbeans.api.java.source.CompilationController;
 import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.CompilationInfo.CacheClearPolicy;
 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.TreePathHandle;
+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.java.source.ui.ElementOpen;
@@ -141,6 +151,7 @@
 import org.netbeans.modules.java.completion.JavaDocumentationTask;
 import org.netbeans.modules.java.editor.base.semantic.MarkOccurrencesHighlighterBase;
 import org.netbeans.modules.java.editor.options.MarkOccurencesSettings;
+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.spiimpl.JavaFixImpl;
@@ -154,6 +165,7 @@
 import org.netbeans.modules.parsing.api.UserTask;
 import org.netbeans.modules.parsing.spi.ParseException;
 import org.netbeans.modules.parsing.spi.SchedulerEvent;
+import org.netbeans.spi.editor.hints.EnhancedFix;
 import org.netbeans.spi.editor.hints.ErrorDescription;
 import org.netbeans.spi.editor.hints.Fix;
 import org.netbeans.spi.editor.hints.LazyFixList;
@@ -190,7 +202,7 @@
             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(uri), EnumSet.noneOf(Options.class), () -> false);
+            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();) {
@@ -202,8 +214,8 @@
             return CompletableFuture.completedFuture(Either.<List<CompletionItem>, CompletionList>forRight(new CompletionList(result)));
         } catch (IOException | ParseException ex) {
             throw new IllegalStateException(ex);
+            }
         }
-    }
 
     public static final class CompletionData {
         public String uri;
@@ -233,9 +245,11 @@
 
     private static class ItemFactoryImpl implements JavaCompletionTask.ItemFactory<CompletionItem> {
 
+        private final LanguageClient client;
         private final String uri;
 
-        public ItemFactoryImpl(String uri) {
+        public ItemFactoryImpl(LanguageClient client, String uri) {
+            this.client = client;
             this.uri = uri;
         }
 
@@ -255,7 +269,9 @@
 
         @Override
         public CompletionItem createPackageItem(String pkgFQN, int substitutionOffset, boolean inPackageStatement) {
-            return null; //TODO: fill
+            CompletionItem item = new CompletionItem(pkgFQN.substring(pkgFQN.lastIndexOf('.') + 1));
+            item.setKind(CompletionItemKind.Folder);
+            return item;
         }
 
         @Override
@@ -322,7 +338,14 @@
             label.append(Utilities.getTypeName(info, retType, false).toString());
             CompletionItem item = new CompletionItem(label.toString());
             item.setKind(elementKind2CompletionItemKind(elem.getKind()));
-            item.setInsertText(elem.getSimpleName().toString());
+            StringBuilder insertText = new StringBuilder();
+            insertText.append(elem.getSimpleName());
+            insertText.append("(");
+            if (elem.getParameters().isEmpty()) {
+                insertText.append(")");
+            }
+            item.setInsertText(insertText.toString());
+            item.setInsertTextFormat(InsertTextFormat.PlainText);
             setCompletionData(item, elem);
             return item;
         }
@@ -373,9 +396,36 @@
             return null; //TODO: fill
         }
 
+        private static final Object KEY_IMPORT_TEXT_EDITS = new Object();
+
         @Override
         public CompletionItem createStaticMemberItem(CompilationInfo info, DeclaredType type, Element memberElem, TypeMirror memberType, boolean multipleVersions, int substitutionOffset, boolean isDeprecated, boolean addSemicolon) {
-            return null; //TODO: fill
+            //TODO: prefer static imports (but would be much slower?)
+            //TODO: should be resolveImport instead of addImports:
+            Map<Element, List<TextEdit>> imports = (Map<Element, List<TextEdit>>) info.getCachedValue(KEY_IMPORT_TEXT_EDITS);
+            if (imports == null) {
+                info.putCachedValue(KEY_IMPORT_TEXT_EDITS, imports = new HashMap<>(), CacheClearPolicy.ON_TASK_END);
+            }
+            List<TextEdit> currentClassImport = imports.computeIfAbsent(type.asElement(), toImport -> {
+                try {
+                    return modify2TextEdits(JavaSource.forFileObject(info.getFileObject()), wc -> {
+                        wc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
+                        wc.rewrite(info.getCompilationUnit(), GeneratorUtilities.get(wc).addImports(wc.getCompilationUnit(), new HashSet<>(Arrays.asList(toImport))));
+                    });
+                } catch (IOException ex) {
+                    //TODO: include stack trace:
+                    client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
+                    return Collections.emptyList();
+                }
+            });
+            String label = type.asElement().getSimpleName() + "." + memberElem.getSimpleName();
+            CompletionItem item = new CompletionItem(label);
+            item.setKind(elementKind2CompletionItemKind(memberElem.getKind()));
+            item.setInsertText(label);
+            item.setInsertTextFormat(InsertTextFormat.PlainText);
+            item.setAdditionalTextEdits(currentClassImport);
+            setCompletionData(item, memberElem);
+            return item;
         }
 
         @Override
@@ -501,7 +551,7 @@
     public CompletableFuture<List<? extends Location>> definition(TextDocumentPositionParams params) {
         JavaSource js = getSource(params.getTextDocument().getUri());
         GoToTarget[] target = new GoToTarget[1];
-        LineMap[] lm = new LineMap[1];
+        LineMap[] thisFileLineMap = new LineMap[1];
         try {
             js.runUserActionTask(cc -> {
                 cc.toPhase(JavaSource.Phase.RESOLVED);
@@ -512,7 +562,7 @@
                     return ;
                 }
                 target[0] = GoToSupport.computeGoToTarget(cc, context, offset);
-                lm[0] = cc.getCompilationUnit().getLineMap();
+                thisFileLineMap[0] = cc.getCompilationUnit().getLineMap();
             }, true);
         } catch (IOException ex) {
             //TODO: include stack trace:
@@ -524,18 +574,21 @@
         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) {
+                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[0], start),
-                                                      createPosition(lm[0], end))));
+                                            new Range(createPosition(lm, start),
+                                                      createPosition(lm, end))));
                 }
             } else {
-                Position pos = createPosition(js.getFileObjects().iterator().next(), target[0].offsetToOpen);
+                int start = target[0].offsetToOpen;
+                int end = target[0].endPos;
                 result.add(new Location(params.getTextDocument().getUri(),
-                                        new Range(pos, pos)));
+                                        new Range(createPosition(thisFileLineMap[0], start),
+                                                  createPosition(thisFileLineMap[0], end))));
             }
         }
         return CompletableFuture.completedFuture(result);
@@ -684,6 +737,7 @@
                 continue;
             }
 
+            TreePathHandle[] topLevelHandle = new TreePathHandle[1];
             LazyFixList lfl = err.getFixes();
 
             if (lfl instanceof CreatorBasedLazyFixList) {
@@ -691,36 +745,51 @@
                     js.runUserActionTask(cc -> {
                         cc.toPhase(JavaSource.Phase.RESOLVED);
                         ((CreatorBasedLazyFixList) lfl).compute(cc, new AtomicBoolean());
+                        topLevelHandle[0] = TreePathHandle.create(new TreePath(cc.getCompilationUnit()), cc);
                     }, true);
                 } catch (IOException ex) {
                     //TODO: include stack trace:
                     client.logMessage(new MessageParams(MessageType.Error, ex.getMessage()));
                 }
             }
-            List<Fix> fixes = lfl.getFixes();
+            List<Fix> fixes = sortFixes(lfl.getFixes());
 
             //TODO: ordering
 
             for (Fix f : fixes) {
+                if (f instanceof ImportClass.FixImport) {
+                    //TODO: FixImport is not a JavaFix, create one. Is there a better solution?
+                    String text = f.getText();
+                    CharSequence sortText = ((ImportClass.FixImport) f).getSortText();
+                    ElementHandle<Element> toImport = ((ImportClass.FixImport) f).getToImport();
+                    f = new JavaFix(topLevelHandle[0], sortText != null ? sortText.toString() : null) {
+                        @Override
+                        protected String getText() {
+                            return text;
+                        }
+                        @Override
+                        protected void performRewrite(JavaFix.TransformationContext ctx) throws Exception {
+                            Element resolved = toImport.resolve(ctx.getWorkingCopy());
+                            if (resolved == null) {
+                                return ;
+                            }
+                            WorkingCopy copy = ctx.getWorkingCopy();
+                            CompilationUnitTree cut = GeneratorUtilities.get(copy).addImports(
+                                copy.getCompilationUnit(),
+                                Collections.singleton(resolved)
+                            );
+                            copy.rewrite(copy.getCompilationUnit(), cut);
+                        }
+                    }.toEditorFix();
+                }
                 if (f instanceof JavaFixImpl) {
                     try {
-                        LineMap[] lm = new LineMap[1];
-                        ModificationResult changes = js.runModificationTask(wc -> {
+                        JavaFix jf = ((JavaFixImpl) f).jf;
+                        List<TextEdit> edits = modify2TextEdits(js, wc -> {
                             wc.toPhase(JavaSource.Phase.RESOLVED);
                             Map<FileObject, byte[]> resourceContentChanges = new HashMap<FileObject, byte[]>();
-                            JavaFix jf = ((JavaFixImpl) f).jf;
                             JavaFixImpl.Accessor.INSTANCE.process(jf, wc, true, resourceContentChanges, /*Ignored in editor:*/new ArrayList<>());
-                            lm[0] = wc.getCompilationUnit().getLineMap();
                         });
-                        //TODO: full, correct and safe edit production:
-                        List<? extends ModificationResult.Difference> diffs = changes.getDifferences(changes.getModifiedFileObjects().iterator().next());
-                        List<TextEdit> edits = new ArrayList<>();
-                        for (ModificationResult.Difference diff : diffs) {
-                            String newText = diff.getNewText();
-                            edits.add(new TextEdit(new Range(createPosition(lm[0], diff.getStartPosition().getOffset()),
-                                                             createPosition(lm[0], diff.getEndPosition().getOffset())),
-                                                   newText != null ? newText : ""));
-                        }
                         TextDocumentEdit te = new TextDocumentEdit(new VersionedTextDocumentIdentifier(params.getTextDocument().getUri(),
                                                                                                        -1),
                                                                    edits);
@@ -740,6 +809,43 @@
         return CompletableFuture.completedFuture(result);
     }
 
+    //TODO: copied from spi.editor.hints/.../FixData:
+    private List<Fix> sortFixes(Collection<Fix> fixes) {
+        List<Fix> result = new ArrayList<Fix>(fixes);
+
+        Collections.sort(result, new FixComparator());
+
+        return result;
+    }
+
+    private static final String DEFAULT_SORT_TEXT = "\uFFFF";
+
+    private static CharSequence getSortText(Fix f) {
+        if (f instanceof EnhancedFix) {
+            return ((EnhancedFix) f).getSortText();
+        } else {
+            return DEFAULT_SORT_TEXT;
+        }
+    }
+    private static final class FixComparator implements Comparator<Fix> {
+        public int compare(Fix o1, Fix o2) {
+            return compareText(getSortText(o1), getSortText(o2));
+        }
+    }
+
+    private static int compareText(CharSequence text1, CharSequence text2) {
+        int len = Math.min(text1.length(), text2.length());
+        for (int i = 0; i < len; i++) {
+            char ch1 = text1.charAt(i);
+            char ch2 = text2.charAt(i);
+            if (ch1 != ch2) {
+                return ch1 - ch2;
+            }
+        }
+        return text1.length() - text2.length();
+    }
+    //end copied
+
     @Override
     public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams arg0) {
         throw new UnsupportedOperationException("Not supported yet.");
@@ -944,7 +1050,15 @@
     private static String toUri(FileObject file) {
         if (FileUtil.isArchiveArtifact(file)) {
             //VS code cannot open jar:file: URLs, workaround:
-            File cacheDir = Places.getCacheSubfile("java-server");
+            //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("nb-java-lsp-server").toFile();
+            } catch (IOException ex) {
+                throw new UncheckedIOException(ex);
+            }
             File segments = new File(cacheDir, "segments");
             Properties props = new Properties();
 
@@ -1015,4 +1129,26 @@
         return URLMapper.findFileObject(URI.create(uri).toURL());
     }
 
+    private 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 -> {
+            task.run(wc);
+            file[0] = wc.getFileObject();
+            lm[0] = wc.getCompilationUnit().getLineMap();
+        });
+        //TODO: full, correct and safe edit production:
+        List<? extends ModificationResult.Difference> diffs = changes.getDifferences(file[0]);
+        if (diffs == null) {
+            return Collections.emptyList();
+        }
+        List<TextEdit> edits = new ArrayList<>();
+        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())),
+                                   newText != null ? newText : ""));
+        }
+        return edits;
+    }
 }
diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/ServerTest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/ServerTest.java
index d785465..d3a3731 100644
--- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/ServerTest.java
+++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/ServerTest.java
@@ -20,6 +20,7 @@
 
 import java.io.File;
 import java.io.FileWriter;
+import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.net.InetAddress;
@@ -28,21 +29,21 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
 import java.util.stream.Collectors;
-import junit.framework.Test;
-import junit.framework.TestCase;
 import org.eclipse.lsp4j.CodeAction;
 import org.eclipse.lsp4j.CodeActionContext;
 import org.eclipse.lsp4j.CodeActionParams;
 import org.eclipse.lsp4j.Command;
 import org.eclipse.lsp4j.CompletionItem;
+import org.eclipse.lsp4j.CompletionItemKind;
 import org.eclipse.lsp4j.CompletionList;
 import org.eclipse.lsp4j.CompletionParams;
 import org.eclipse.lsp4j.Diagnostic;
@@ -54,6 +55,7 @@
 import org.eclipse.lsp4j.DocumentSymbolParams;
 import org.eclipse.lsp4j.InitializeParams;
 import org.eclipse.lsp4j.InitializeResult;
+import org.eclipse.lsp4j.InsertTextFormat;
 import org.eclipse.lsp4j.Location;
 import org.eclipse.lsp4j.MessageActionItem;
 import org.eclipse.lsp4j.MessageParams;
@@ -74,19 +76,27 @@
 import org.eclipse.lsp4j.launch.LSPLauncher;
 import org.eclipse.lsp4j.services.LanguageClient;
 import org.eclipse.lsp4j.services.LanguageServer;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.classpath.GlobalPathRegistry;
 import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.project.Project;
 import org.netbeans.api.sendopts.CommandLine;
-import org.netbeans.junit.NbModuleSuite;
 import org.netbeans.junit.NbTestCase;
-import org.netbeans.modules.java.source.parsing.ParameterNameProviderImpl;
+import org.netbeans.modules.java.source.BootClassPathUtil;
 import org.netbeans.modules.parsing.impl.indexing.implspi.CacheFolderProvider;
+import org.netbeans.spi.java.classpath.ClassPathProvider;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.project.ProjectFactory;
+import org.netbeans.spi.project.ProjectState;
+import org.netbeans.spi.project.ui.ProjectOpenedHook;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileUtil;
 import org.openide.modules.ModuleInfo;
 import org.openide.modules.Places;
-import org.openide.util.Exceptions;
 import org.openide.util.Lookup;
 import org.openide.util.Utilities;
+import org.openide.util.lookup.Lookups;
+import org.openide.util.lookup.ServiceProvider;
 
 /**
  *
@@ -219,7 +229,7 @@
         assertTrue(actualItems.contains("Keyword:interface"));
         server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, hashCodeStart), new Position(0, hashCodeStart + "equ".length())), "equ".length(), "hashCode"))));
         int closingBrace = code.lastIndexOf("}");
-        server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, closingBrace), new Position(0, closingBrace)), 0, "private String c(Object o) {\nreturn o;\n}"))));
+        server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, closingBrace), new Position(0, closingBrace)), 0, "public String c(Object o) {\nreturn o;\n}"))));
         List<Diagnostic> diagnostics = assertDiags(diags, "Error:1:0-1:9"); //errors
         assertDiags(diags, "Error:1:0-1:9");//hints
         List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(1, 0), new Position(1, 9)), new CodeActionContext(Arrays.asList(diagnostics.get(0))))).get();
@@ -236,7 +246,7 @@
         assertEquals(1, edit.getRange().getEnd().getLine());
         assertEquals(7, edit.getRange().getEnd().getCharacter());
         assertEquals("(String) ", edit.getNewText());
-        server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, closingBrace), new Position(0, closingBrace)), 0, "private void assignToSelf(Object o) { o = o; }"))));
+        server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, closingBrace), new Position(0, closingBrace)), 0, "public  void assignToSelf(Object o) { o = o; }"))));
         assertDiags(diags, "Error:1:0-1:9");//errors
         assertDiags(diags, "Error:1:0-1:9", "Warning:0:148-0:153", "Warning:0:152-0:153");//hints
     }
@@ -244,7 +254,7 @@
     public void testCodeActionWithRemoval() throws Exception {
         File src = new File(getWorkDir(), "Test.java");
         src.getParentFile().mkdirs();
-        String code = "public class Test { private String c(String s) {\nreturn s.toString();\n} }";
+        String code = "public class Test { public String c(String s) {\nreturn s.toString();\n} }";
         try (Writer w = new FileWriter(src)) {
             w.write(code);
         }
@@ -452,11 +462,19 @@
                       "    public void method(int ppp) {\n" +
                       "        System.err.println(field);\n" +
                       "        System.err.println(ppp);\n" +
+                      "        new Other().test();\n" +
                       "    }\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" +
+                    "}");
+        }
         FileUtil.refreshFor(getWorkDir());
         Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
             @Override
@@ -493,13 +511,24 @@
         assertEquals(src.toURI().toString(), 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 TextDocumentPositionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get();
         assertEquals(1, definition.size());
         assertEquals(src.toURI().toString(), definition.get(0).getUri());
         assertEquals(2, definition.get(0).getRange().getStart().getLine());
         assertEquals(23, definition.get(0).getRange().getStart().getCharacter());
-        //XXX: test jump to another file!
+        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 TextDocumentPositionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get();
+        assertEquals(1, definition.size());
+        assertEquals(otherSrc.toURI().toString(), 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());
+        assertEquals(26, definition.get(0).getRange().getEnd().getCharacter());
     }
 
     public void testOpenProjectOpenJDK() throws Exception {
@@ -623,6 +652,202 @@
                          "<none>:1:26-1:29", "<none>:2:12-2:15", "<none>:3:17-3:20");
     }
 
+    public void testAdvancedCompletion1() 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(getWorkDir().toURI().toString());
+        InitializeResult result = server.initialize(initParams).get();
+        indexingComplete.await();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code)));
+
+        {
+            VersionedTextDocumentIdentifier id1 = new VersionedTextDocumentIdentifier(src.toURI().toString(), 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();
+            assertTrue(completion.isRight());
+            Optional<CompletionItem> lengthItem = completion.getRight().getItems().stream().filter(ci -> "length() : int".equals(ci.getLabel())).findAny();
+            assertTrue(lengthItem.isPresent());
+            assertEquals(InsertTextFormat.PlainText, lengthItem.get().getInsertTextFormat());
+            assertEquals("length()", lengthItem.get().getInsertText());
+            Optional<CompletionItem> substringItem = completion.getRight().getItems().stream().filter(ci -> ci.getLabel().startsWith("substring(") && ci.getLabel().contains(",")).findAny();
+            assertTrue(substringItem.isPresent());
+            assertEquals(InsertTextFormat.PlainText, substringItem.get().getInsertTextFormat());
+            assertEquals("substring(", substringItem.get().getInsertText());
+        }
+
+        {
+            VersionedTextDocumentIdentifier id2 = new VersionedTextDocumentIdentifier(src.toURI().toString(), 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();
+                assertTrue(completion.isRight());
+                Optional<CompletionItem> annotationItem = completion.getRight().getItems().stream().filter(ci -> "annotation".equals(ci.getLabel())).findAny();
+                assertTrue(annotationItem.isPresent());
+                assertEquals("annotation", annotationItem.get().getLabel());
+                assertEquals(CompletionItemKind.Folder, annotationItem.get().getKind());
+            }
+
+            server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(afterJavaLang, afterJavaLang), 0, "annotation."))));
+
+            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();
+                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();
+                assertTrue(targetItem.isPresent());
+                assertEquals("Target", targetItem.get().getLabel()); //TODO: insert text '('!
+                assertEquals(CompletionItemKind.Interface, targetItem.get().getKind());
+            }
+
+            server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(afterJavaLangAnnotation, afterJavaLangAnnotation), 0, "Target("))));
+
+            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();
+                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();
+                assertTrue(methodItem.isPresent());
+                assertEquals(InsertTextFormat.PlainText, methodItem.get().getInsertTextFormat());
+                assertEquals("ElementType.METHOD", methodItem.get().getInsertText());
+                assertEquals(1, methodItem.get().getAdditionalTextEdits().size());
+                assertEquals(0, methodItem.get().getAdditionalTextEdits().get(0).getRange().getStart().getLine());
+                assertEquals(0, methodItem.get().getAdditionalTextEdits().get(0).getRange().getStart().getCharacter());
+                assertEquals(0, methodItem.get().getAdditionalTextEdits().get(0).getRange().getEnd().getLine());
+                assertEquals(0, methodItem.get().getAdditionalTextEdits().get(0).getRange().getEnd().getCharacter());
+                assertEquals("\nimport java.lang.annotation.ElementType;\n\n", methodItem.get().getAdditionalTextEdits().get(0).getNewText());
+            }
+
+            server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, 0), new Position(0, 0)), 0, "import java.lang.annotation.ElementType;"))));
+
+            {
+                //import already exists:
+                Either<List<CompletionItem>, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), 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();
+                assertTrue(methodItem.isPresent());
+                assertEquals(InsertTextFormat.PlainText, methodItem.get().getInsertTextFormat());
+                assertEquals("ElementType.METHOD", methodItem.get().getInsertText());
+                assertEquals(0, methodItem.get().getAdditionalTextEdits().size());
+            }
+        }
+    }
+
+    public void testFixImports() 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() {\n" +
+                      "        List l;\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(getWorkDir().toURI().toString());
+        InitializeResult result = server.initialize(initParams).get();
+        indexingComplete.await();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "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();
+        assertEquals(2, codeActions.size());
+    }
+
     private void assertHighlights(List<? extends DocumentHighlight> highlights, String... expected) {
         Set<String> stringHighlights = new HashSet<>();
         for (DocumentHighlight h : highlights) {
@@ -634,4 +859,79 @@
         assertEquals(new HashSet<>(Arrays.asList(expected)),
                      stringHighlights);
     }
+
+    //make sure files can access other files in the same directory:
+    @ServiceProvider(service=ClassPathProvider.class, position=100)
+    public static final class ClassPathProviderImpl implements ClassPathProvider {
+
+        @Override
+        public ClassPath findClassPath(FileObject file, String type) {
+            if (ClassPath.SOURCE.equals(type) && file.isData()) {
+                return ClassPathSupport.createClassPath(file.getParent());
+            }
+            if (ClassPath.BOOT.equals(type)) {
+                return BootClassPathUtil.getBootClassPath();
+            }
+            return null;
+        }
+
+    }
+
+    //tests may run as a project, so that indexing works properly:
+    @ServiceProvider(service=ProjectFactory.class)
+    public static class TestProjectFactory implements ProjectFactory {
+
+        @Override
+        public boolean isProject(FileObject projectDirectory) {
+            return projectDirectory.getFileObject(".test-project") != null;
+        }
+
+        @Override
+        public Project loadProject(FileObject projectDirectory, ProjectState state) throws IOException {
+            if (isProject(projectDirectory)) {
+                ClassPath source = ClassPathSupport.createClassPath(projectDirectory);
+                Lookup lookup = Lookups.fixed(new ProjectOpenedHook() {
+                        @Override
+                        protected void projectOpened() {
+                            GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {source});
+                        }
+
+                        @Override
+                        protected void projectClosed() {
+                            GlobalPathRegistry.getDefault().unregister(ClassPath.SOURCE, new ClassPath[] {source});
+                        }
+                    }, new ClassPathProvider() {
+                        @Override
+                        public ClassPath findClassPath(FileObject file, String type) {
+                            switch (type) {
+                                case ClassPath.SOURCE: return source;
+                                case ClassPath.BOOT: return BootClassPathUtil.getBootClassPath();
+                            }
+                            return null;
+                        }
+                    }
+                );
+                return new Project() {
+                    @Override
+                    public FileObject getProjectDirectory() {
+                        return projectDirectory;
+                    }
+
+                    @Override
+                    public Lookup getLookup() {
+                        return lookup;
+                    }
+                };
+            }
+            return null;
+        }
+
+        @Override
+        public void saveProject(Project project) throws IOException, ClassCastException {
+        }
+    }
+
+    static {
+        System.setProperty("SourcePath.no.source.filter", "true");
+    }
 }
diff --git a/java/java.lsp.server/vscode/package-lock.json b/java/java.lsp.server/vscode/package-lock.json
index a124690..a06353d 100644
--- a/java/java.lsp.server/vscode/package-lock.json
+++ b/java/java.lsp.server/vscode/package-lock.json
@@ -1,6 +1,6 @@
 {
 	"name": "apache-netbeans-java",
-	"version": "0.0.1",
+	"version": "0.1.0",
 	"lockfileVersion": 1,
 	"requires": true,
 	"dependencies": {
diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json
index acb5788..4e4b7de 100644
--- a/java/java.lsp.server/vscode/package.json
+++ b/java/java.lsp.server/vscode/package.json
@@ -3,7 +3,7 @@
 	"description": "An Apache NetBeans Java plugin for Visual Studio Code",
 	"author": "Apache NetBeans",
 	"license": "Apache 2.0",
-	"version": "0.0.1",
+	"version": "0.1.0",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/apache/netbeans"
diff --git a/java/java.lsp.server/vscode/src/VerifyJDK11.java b/java/java.lsp.server/vscode/src/VerifyJDK14.java
similarity index 89%
rename from java/java.lsp.server/vscode/src/VerifyJDK11.java
rename to java/java.lsp.server/vscode/src/VerifyJDK14.java
index 80e5928..b05b308 100644
--- a/java/java.lsp.server/vscode/src/VerifyJDK11.java
+++ b/java/java.lsp.server/vscode/src/VerifyJDK14.java
@@ -16,9 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-public class VerifyJDK11 {
+public class VerifyJDK14 {
     public static void main(String[] args) {
-        if (Runtime.version().compareTo(Runtime.Version.parse("11-build")) < 0)
-            throw new IllegalStateException("Not JDK 11+.");
+        if (Runtime.version().compareTo(Runtime.Version.parse("14-build")) < 0)
+            throw new IllegalStateException("Not JDK 14+.");
     }
 }
diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts
index e1f39b8..4817bc5 100644
--- a/java/java.lsp.server/vscode/src/extension.ts
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -27,9 +27,9 @@
 
     try {
         let targetJava = specifiedJDK != null ? specifiedJDK + '/bin/java' : 'java';
-        execSync(targetJava + ' ' + context.extensionPath + '/src/VerifyJDK11.java');
+        execSync(targetJava + ' ' + context.extensionPath + '/src/VerifyJDK14.java');
     } catch (e) {
-        window.showErrorMessage('The Java language server needs a JDK 11 to run, but none found. Please configure it under File/Preferences/Settings/Extensions/Java and restart VS Code.');
+        window.showErrorMessage('The Java language server needs a JDK 14 to run, but none found. Please configure it under File/Preferences/Settings/Extensions/Java and restart VS Code.');
         return ;
     }
     let serverPath = path.resolve(context.extensionPath, "nb-java-lsp-server", "bin", "nb-java-lsp-server");
diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/TreeShims.java b/java/java.source.base/src/org/netbeans/modules/java/source/TreeShims.java
index 5e7b042..88504aa 100644
--- a/java/java.source.base/src/org/netbeans/modules/java/source/TreeShims.java
+++ b/java/java.source.base/src/org/netbeans/modules/java/source/TreeShims.java
@@ -228,4 +228,13 @@
     public static boolean isRecordComponent(ElementKind kind) {
         return "RECORD_COMPONENT".equals(kind.name());
     }
+
+    public static ElementKind getRecordKind() {
+        try {
+            return ElementKind.valueOf(RECORD); //NOI18N
+        } catch (IllegalArgumentException ex) {
+            return null;
+        }
+    }
+
 }
diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ElementHandleTest.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ElementHandleTest.java
index d38785b..e113741 100644
--- a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ElementHandleTest.java
+++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ElementHandleTest.java
@@ -53,6 +53,7 @@
 import org.netbeans.modules.java.source.BootClassPathUtil;
 import org.netbeans.modules.java.source.ElementUtils;
 import org.netbeans.modules.java.source.TestUtil;
+import org.netbeans.modules.java.source.TreeShims;
 import org.netbeans.modules.java.source.usages.IndexUtil;
 import org.netbeans.spi.java.classpath.ClassPathProvider;
 import org.netbeans.spi.java.classpath.support.ClassPathSupport;
@@ -73,6 +74,7 @@
     
     private FileObject src;
     private FileObject data;
+    private String orignalNetbeansUsr;
     
     
     static {
@@ -129,9 +131,15 @@
         ClassPathProviderImpl.getDefault().setClassPaths(TestUtil.getBootClassPath(),
                                                          ClassPathSupport.createClassPath(new URL[0]),
                                                          ClassPathSupport.createClassPath(new FileObject[]{this.src}));
+        orignalNetbeansUsr = System.getProperty("netbeans.user");
+        System.setProperty("netbeans.user", getWorkDirPath());
     }
 
     protected void tearDown() throws Exception {
+        if (orignalNetbeansUsr != null)
+            System.setProperty("netbeans.user", orignalNetbeansUsr);
+        else
+            System.clearProperty("netbeans.user");
     }
 
 
@@ -426,6 +434,9 @@
             ElementKind.ENUM,
             ElementKind.ANNOTATION_TYPE
         }));
+        ElementKind recordKind = TreeShims.getRecordKind();
+        if (recordKind != null)
+            allowed.add(recordKind);
         for (ElementKind aek : allowed) {
             ElementHandle<TypeElement> eh = ElementHandle.createTypeElementHandle(aek, "org.me.Foo");    //NOI18N
             assertEquals(aek, eh.getKind());
diff --git a/java/java.sourceui/manifest.mf b/java/java.sourceui/manifest.mf
index e122b88..89f5f11 100644
--- a/java/java.sourceui/manifest.mf
+++ b/java/java.sourceui/manifest.mf
@@ -1,6 +1,6 @@
 Manifest-Version: 1.0
 OpenIDE-Module: org.netbeans.modules.java.sourceui/1
-OpenIDE-Module-Implementation-Version: 1
+OpenIDE-Module-Implementation-Version: 2
 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/java/source/ui/resources/Bundle.properties
 OpenIDE-Module-Install: org/netbeans/modules/java/source/ui/JavaSourceUIModule.class
 AutoUpdate-Show-In-Client: false
diff --git a/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementOpen.java b/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementOpen.java
index f16649f..7eb1057 100644
--- a/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementOpen.java
+++ b/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementOpen.java
@@ -294,8 +294,10 @@
         assert fo != null;
         
         try {
-            int[] offset = getOffset(fo, handle, cancel);
-            return new Object[] {fo, offset[0], offset[1]};
+            Object[] result = new Object[4];
+            result[0] = fo;
+            getOffset(fo, handle, result, cancel);
+            return result;
         } catch (IOException e) {
             Exceptions.printStackTrace(e);
             return null;
@@ -343,8 +345,9 @@
     private static final int AWT_TIMEOUT = 1000;
     private static final int NON_AWT_TIMEOUT = 2000;
 
-    private static int[] getOffset(final FileObject fo, final ElementHandle<? extends Element> handle, final AtomicBoolean cancel) throws IOException {
-        final int[]  result = new int[] {-1, -1};
+    private static void getOffset(final FileObject fo, final ElementHandle<? extends Element> handle, final Object[] result, final AtomicBoolean cancel) throws IOException {
+        result[1] = -1;
+        result[2] = -1;
         
         final JavaSource js = JavaSource.forFileObject(fo);
         if (js != null) {
@@ -358,6 +361,7 @@
                     } catch (IOException ioe) {
                         Exceptions.printStackTrace(ioe);
                     }
+                    result[3] = info.getCompilationUnit().getLineMap();
                     Element el = handle.resolve(info);
                     if (el == null) {
                         if (!SourceUtils.isScanInProgress()) {
@@ -375,7 +379,7 @@
                         // Imprecise but should usually work:
                         Matcher m = Pattern.compile("(?m)^package (.+);$").matcher(fo.asText(/*FileEncodingQuery.getEncoding(fo).name()*/)); // NOI18N
                         if (m.find()) {
-                            result[0] = m.start();
+                            result[1] = m.start();
                         }
                         return;
                     }
@@ -388,15 +392,14 @@
                     Tree elTree = v.declTree;
 
                     if (elTree != null) {
-                        result[0] = (int)info.getTrees().getSourcePositions().getStartPosition(cu, elTree);
-                        result[1] = (int)info.getTrees().getSourcePositions().getEndPosition(cu, elTree);
+                        result[1] = (int)info.getTrees().getSourcePositions().getStartPosition(cu, elTree);
+                        result[2] = (int)info.getTrees().getSourcePositions().getEndPosition(cu, elTree);
                     }
                 }
             };
 
             js.runUserActionTask(t, true);
         }
-        return result;
     }
     
     // Private innerclasses ----------------------------------------------------
diff --git a/java/libs.javacapi/external/binaries-list b/java/libs.javacapi/external/binaries-list
index d9def00..97525d3 100644
--- a/java/libs.javacapi/external/binaries-list
+++ b/java/libs.javacapi/external/binaries-list
@@ -14,4 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-FC7D04B1F1AE92A87F113096862112BE7E6970D4 nb-javac-13-api.jar
+988279FD0A258197BF24190BE44A60BA7EA1FC3C nb-javac-14-api.jar
\ No newline at end of file
diff --git a/java/libs.javacapi/external/nb-javac-13-api-license.txt b/java/libs.javacapi/external/nb-javac-14-api-license.txt
similarity index 99%
rename from java/libs.javacapi/external/nb-javac-13-api-license.txt
rename to java/libs.javacapi/external/nb-javac-14-api-license.txt
index 5f55457..d2aec9e 100644
--- a/java/libs.javacapi/external/nb-javac-13-api-license.txt
+++ b/java/libs.javacapi/external/nb-javac-14-api-license.txt
@@ -1,6 +1,6 @@
 Name: Javac Compiler API
 Description: Javac Compiler API
-Version: 13
+Version: 14
 License: GPL-2-CP
 Origin: OpenJDK (http://hg.openjdk.java.net/)
 Source: http://hg.netbeans.org/main/nb-java-x/
diff --git a/java/libs.javacapi/manifest.mf b/java/libs.javacapi/manifest.mf
index 5177edf..d1a19dd 100644
--- a/java/libs.javacapi/manifest.mf
+++ b/java/libs.javacapi/manifest.mf
@@ -2,5 +2,4 @@
 OpenIDE-Module: org.netbeans.libs.javacapi
 OpenIDE-Module-Implementation-Version: 3
 OpenIDE-Module-Localizing-Bundle: org/netbeans/libs/javacapi/Bundle.properties
-OpenIDE-Module-Layer: org/netbeans/libs/javacapi/layer.xml
 OpenIDE-Module-Requires: org.openide.modules.ModuleFormat2
diff --git a/java/libs.javacapi/nbproject/project.xml b/java/libs.javacapi/nbproject/project.xml
index 0dc3ad9..2bd65c9 100644
--- a/java/libs.javacapi/nbproject/project.xml
+++ b/java/libs.javacapi/nbproject/project.xml
@@ -40,7 +40,7 @@
             </public-packages>
             <class-path-extension>
                 <runtime-relative-path />
-                <binary-origin>external/nb-javac-13-api.jar</binary-origin>
+                <binary-origin>external/nb-javac-14-api.jar</binary-origin>
             </class-path-extension>
         </data>
     </configuration>
diff --git a/java/libs.javacapi/src/org/netbeans/libs/javacapi/Bundle.properties b/java/libs.javacapi/src/org/netbeans/libs/javacapi/Bundle.properties
index 6c99d32..2c578e0 100644
--- a/java/libs.javacapi/src/org/netbeans/libs/javacapi/Bundle.properties
+++ b/java/libs.javacapi/src/org/netbeans/libs/javacapi/Bundle.properties
@@ -19,7 +19,3 @@
     The javac public API, as defined by JSR 199, JSR 269, and the com.sun.source packages defined in JDK 6.
 OpenIDE-Module-Name=Javac API Wrapper
 OpenIDE-Module-Short-Description=The javac public API
-
-##library file from layer
-javac-api=Java Tree API
-
diff --git a/java/libs.javacapi/src/org/netbeans/libs/javacapi/javac-library.xml b/java/libs.javacapi/src/org/netbeans/libs/javacapi/javac-library.xml
deleted file mode 100644
index 26b90d6..0000000
--- a/java/libs.javacapi/src/org/netbeans/libs/javacapi/javac-library.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0"?>
-<!--
-
-    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.
-
--->
-<!DOCTYPE library PUBLIC "-//NetBeans//DTD Library Declaration 1.0//EN" "http://www.netbeans.org/dtds/library-declaration-1_0.dtd">
-
-<library version="1.0">
-    <name>javac-api</name>
-    <type>j2se</type>
-    <localizing-bundle>org/netbeans/libs/javacapi/Bundle</localizing-bundle>
-    <volume>
-        <type>classpath</type>
-        <resource>jar:nbinst://org.netbeans.libs.javacapi/modules/ext/nb-javac-api.jar!/</resource>
-    </volume>
-</library>
diff --git a/java/libs.javacapi/src/org/netbeans/libs/javacapi/layer.xml b/java/libs.javacapi/src/org/netbeans/libs/javacapi/layer.xml
deleted file mode 100644
index dcffde7..0000000
--- a/java/libs.javacapi/src/org/netbeans/libs/javacapi/layer.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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.
-
--->
-<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "http://www.netbeans.org/dtds/filesystem-1_2.dtd">
-<filesystem>
-    <folder name="org-netbeans-api-project-libraries">
-        <folder name="Libraries">
-            <file name="javac-library.xml" url="javac-library.xml">
-                <attr name="displayName" bundlevalue="org/netbeans/libs/javacapi/Bundle#javac-api"/>
-            </file>
-        </folder>
-    </folder>
-</filesystem>
diff --git a/java/libs.javacimpl/external/binaries-list b/java/libs.javacimpl/external/binaries-list
index 8461b2c..d270d6c 100644
--- a/java/libs.javacimpl/external/binaries-list
+++ b/java/libs.javacimpl/external/binaries-list
@@ -14,4 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-34E9F9C1BDC61FE7EFCCF305D70960B862DE7815 nb-javac-13-impl.jar
+BABD4BF10C42EE321AED7393BED72C579A486CD3 nb-javac-14-impl.jar
diff --git a/java/libs.javacimpl/external/nb-javac-13-impl-license.txt b/java/libs.javacimpl/external/nb-javac-14-impl-license.txt
similarity index 99%
rename from java/libs.javacimpl/external/nb-javac-13-impl-license.txt
rename to java/libs.javacimpl/external/nb-javac-14-impl-license.txt
index 7f1b8ff..99a03a9 100644
--- a/java/libs.javacimpl/external/nb-javac-13-impl-license.txt
+++ b/java/libs.javacimpl/external/nb-javac-14-impl-license.txt
@@ -1,6 +1,6 @@
 Name: Javac Compiler Implementation
 Description: Javac Compiler Implementation
-Version: 13
+Version: 14
 License: GPL-2-CP
 Origin: OpenJDK (http://hg.openjdk.java.net/)
 Source: http://hg.netbeans.org/main/nb-java-x/
diff --git a/java/libs.javacimpl/nbproject/project.xml b/java/libs.javacimpl/nbproject/project.xml
index 6b0987f..c382b99 100644
--- a/java/libs.javacimpl/nbproject/project.xml
+++ b/java/libs.javacimpl/nbproject/project.xml
@@ -37,7 +37,7 @@
             <public-packages/>
             <class-path-extension>
                 <runtime-relative-path />
-                <binary-origin>external/nb-javac-13-impl.jar</binary-origin>
+                <binary-origin>external/nb-javac-14-impl.jar</binary-origin>
             </class-path-extension>
         </data>
     </configuration>
diff --git a/java/libs.springframework/nbproject/project.properties b/java/libs.springframework/nbproject/project.properties
index 63adc8c..845161d 100644
--- a/java/libs.springframework/nbproject/project.properties
+++ b/java/libs.springframework/nbproject/project.properties
@@ -92,12 +92,10 @@
     modules/ext/spring-3.0/spring-aop-3.2.18.RELEASE.jar,\
     modules/ext/spring-3.0/spring-aspects-3.2.18.RELEASE.jar,\
     modules/ext/spring-3.0/spring-beans-3.2.18.RELEASE.jar,\
-    modules/ext/spring-3.0/spring-build-src-3.2.18.RELEASE.jar,\
     modules/ext/spring-3.0/spring-context-3.2.18.RELEASE.jar,\
     modules/ext/spring-3.0/spring-context-support-3.2.18.RELEASE.jar,\
     modules/ext/spring-3.0/spring-core-3.2.18.RELEASE.jar,\
     modules/ext/spring-3.0/spring-expression-3.2.18.RELEASE.jar,\
-    modules/ext/spring-3.0/spring-framework-bom-3.2.18.RELEASE.jar,\
     modules/ext/spring-3.0/spring-instrument-3.2.18.RELEASE.jar,\
     modules/ext/spring-3.0/spring-instrument-tomcat-3.2.18.RELEASE.jar,\
     modules/ext/spring-3.0/spring-jdbc-3.2.18.RELEASE.jar,\
diff --git a/java/libs.springframework/src/org/netbeans/libs/springframework/spring-framework300.xml b/java/libs.springframework/src/org/netbeans/libs/springframework/spring-framework300.xml
index d69ff59..fd8f4b6 100644
--- a/java/libs.springframework/src/org/netbeans/libs/springframework/spring-framework300.xml
+++ b/java/libs.springframework/src/org/netbeans/libs/springframework/spring-framework300.xml
@@ -31,12 +31,10 @@
         <resource>jar:nbinst://org.netbeans.libs.springframework/modules/ext/spring-3.0/spring-aop-3.2.18.RELEASE.jar!/</resource>
         <resource>jar:nbinst://org.netbeans.libs.springframework/modules/ext/spring-3.0/spring-aspects-3.2.18.RELEASE.jar!/</resource>
         <resource>jar:nbinst://org.netbeans.libs.springframework/modules/ext/spring-3.0/spring-beans-3.2.18.RELEASE.jar!/</resource>
-        <resource>jar:nbinst://org.netbeans.libs.springframework/modules/ext/spring-3.0/spring-build-src-3.2.18.RELEASE.jar!/</resource>
         <resource>jar:nbinst://org.netbeans.libs.springframework/modules/ext/spring-3.0/spring-context-3.2.18.RELEASE.jar!/</resource>
         <resource>jar:nbinst://org.netbeans.libs.springframework/modules/ext/spring-3.0/spring-context-support-3.2.18.RELEASE.jar!/</resource>
         <resource>jar:nbinst://org.netbeans.libs.springframework/modules/ext/spring-3.0/spring-core-3.2.18.RELEASE.jar!/</resource>
         <resource>jar:nbinst://org.netbeans.libs.springframework/modules/ext/spring-3.0/spring-expression-3.2.18.RELEASE.jar!/</resource>
-        <resource>jar:nbinst://org.netbeans.libs.springframework/modules/ext/spring-3.0/spring-framework-bom-3.2.18.RELEASE.jar!/</resource>
         <resource>jar:nbinst://org.netbeans.libs.springframework/modules/ext/spring-3.0/spring-instrument-3.2.18.RELEASE.jar!/</resource>
         <resource>jar:nbinst://org.netbeans.libs.springframework/modules/ext/spring-3.0/spring-instrument-tomcat-3.2.18.RELEASE.jar!/</resource>
         <resource>jar:nbinst://org.netbeans.libs.springframework/modules/ext/spring-3.0/spring-jdbc-3.2.18.RELEASE.jar!/</resource>
diff --git a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/ui/WhereUsedPanel.java b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/ui/WhereUsedPanel.java
index 2540b8e..583ac8d 100644
--- a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/ui/WhereUsedPanel.java
+++ b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/ui/WhereUsedPanel.java
@@ -86,7 +86,10 @@
             case FIELD:
             case ENUM_CONSTANT:
             default: {
-                panel = new WhereUsedPanelVariable(parent);
+                if (kind.name().equals("RECORD"))   // NOI18N
+                     panel = new WhereUsedPanelClass(parent);
+                else
+                    panel = new WhereUsedPanelVariable(parent);
                 break;
             }
         }
diff --git a/java/spi.java.hints/src/org/netbeans/modules/java/hints/spiimpl/Utilities.java b/java/spi.java.hints/src/org/netbeans/modules/java/hints/spiimpl/Utilities.java
index 03deb95..85c12ca 100644
--- a/java/spi.java.hints/src/org/netbeans/modules/java/hints/spiimpl/Utilities.java
+++ b/java/spi.java.hints/src/org/netbeans/modules/java/hints/spiimpl/Utilities.java
@@ -111,6 +111,8 @@
 import java.util.Map.Entry;
 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.AnnotationMirror;
 import javax.lang.model.element.AnnotationValue;
 import javax.lang.model.element.AnnotationValueVisitor;
@@ -1352,6 +1354,33 @@
             return super.modifiersOpt(partial);
         }
 
+
+        public JCVariableDecl formalParameter(boolean lambdaParam, boolean recordComponents) {
+            if (token.kind == TokenKind.IDENTIFIER) {
+                if (token.name().startsWith(dollar)) {
+                    com.sun.tools.javac.util.Name name = token.name();
+
+                    Token peeked = S.token(1);
+
+                    if (peeked.kind == TokenKind.COMMA || peeked.kind == TokenKind.RPAREN) {
+                        nextToken();
+                        return JackpotTrees.createVariableWildcard(ctx, name);
+                    }
+                }
+            }
+            JCTree.JCVariableDecl result = null;
+            try {
+                Class[] paramTypes = {boolean.class, boolean.class};
+                result = (JCTree.JCVariableDecl) MethodHandles.lookup()
+                        .findSpecial(JavacParser.class, "formalParameter", MethodType.methodType(JCTree.JCVariableDecl.class, paramTypes), JackpotJavacParser.class) // NOI18N
+                        .invoke(this, lambdaParam, recordComponents);
+            } catch (Throwable ex) {
+                throw new IllegalStateException(ex);
+            }
+            return result;
+
+        }
+        
         @Override
         public JCVariableDecl formalParameter(boolean lambdaParam) {
             if (token.kind == TokenKind.IDENTIFIER) {
@@ -1408,7 +1437,35 @@
             }
             return super.catchClause();
         }
+        
+        public com.sun.tools.javac.util.List<JCTree> classOrInterfaceOrRecordBodyDeclaration(com.sun.tools.javac.util.Name className, boolean isInterface, boolean isRecord) {
 
+            if (token.kind == TokenKind.IDENTIFIER) {
+                if (token.name().startsWith(dollar)) {
+                    com.sun.tools.javac.util.Name name = token.name();
+
+                    Token peeked = S.token(1);
+
+                    if (peeked.kind == TokenKind.SEMI) {
+                        nextToken();
+                        nextToken();
+
+                        return com.sun.tools.javac.util.List.<JCTree>of(F.Ident(name));
+                    }
+                }
+            }
+
+            com.sun.tools.javac.util.List<JCTree> result = null;
+            Class[] argsType = {com.sun.tools.javac.util.Name.class, boolean.class, boolean.class};
+            try {
+                result = (com.sun.tools.javac.util.List<JCTree>) MethodHandles.lookup().findSpecial(JavacParser.class, "classOrInterfaceOrRecordBodyDeclaration", MethodType.methodType(com.sun.tools.javac.util.List.class, argsType), JackpotJavacParser.class) // NOI18N
+                        .invoke(this, className, false, false);
+            } catch (Throwable ex) {
+                throw new IllegalStateException(ex);
+            }
+            return result;
+        }
+        
         @Override
         public com.sun.tools.javac.util.List<JCTree> classOrInterfaceBodyDeclaration(com.sun.tools.javac.util.Name className, boolean isInterface) {
             if (token.kind == TokenKind.IDENTIFIER) {
diff --git a/nb/updatecenters/extras/nbjavac.api/manifest.mf b/nb/updatecenters/extras/nbjavac.api/manifest.mf
index af2837e..00bd544 100644
--- a/nb/updatecenters/extras/nbjavac.api/manifest.mf
+++ b/nb/updatecenters/extras/nbjavac.api/manifest.mf
@@ -2,6 +2,6 @@
 AutoUpdate-Show-In-Client: false
 OpenIDE-Module: org.netbeans.modules.nbjavac.api
 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/nbjavac/api/Bundle.properties
-OpenIDE-Module-Specification-Version: 2.1
+OpenIDE-Module-Specification-Version: 2.2
 OpenIDE-Module-Hide-Classpath-Packages: com.sun.javadoc.**, com.sun.source.**, javax.annotation.processing.**, javax.lang.model.**, javax.tools.**, com.sun.tools.javac.**
 OpenIDE-Module-Fragment-Host: org.netbeans.libs.javacapi
diff --git a/nb/updatecenters/extras/nbjavac.api/nbproject/project.xml b/nb/updatecenters/extras/nbjavac.api/nbproject/project.xml
index 8ebde33..a2bfb3d 100644
--- a/nb/updatecenters/extras/nbjavac.api/nbproject/project.xml
+++ b/nb/updatecenters/extras/nbjavac.api/nbproject/project.xml
@@ -28,8 +28,8 @@
             <module-dependencies/>
             <public-packages/>
             <class-path-extension>
-                <runtime-relative-path>ext/nb-javac-13-api.jar</runtime-relative-path>
-                <binary-origin>release/modules/ext/nb-javac-13-api.jar</binary-origin>
+                <runtime-relative-path>ext/nb-javac-14-api.jar</runtime-relative-path>
+                <binary-origin>release/modules/ext/nb-javac-14-api.jar</binary-origin>
             </class-path-extension>
         </data>
     </configuration>
diff --git a/nb/updatecenters/extras/nbjavac.api/release/modules/ext/nb-javac-13-api.jar.external b/nb/updatecenters/extras/nbjavac.api/release/modules/ext/nb-javac-13-api.jar.external
deleted file mode 100644
index 09a444a..0000000
--- a/nb/updatecenters/extras/nbjavac.api/release/modules/ext/nb-javac-13-api.jar.external
+++ /dev/null
@@ -1,6 +0,0 @@
-CRC:520567768
-SIZE:230395
-URL:https://netbeans.osuosl.org/binaries/FC7D04B1F1AE92A87F113096862112BE7E6970D4-nb-javac-13-api.jar
-URL:https://hg.netbeans.org/binaries/FC7D04B1F1AE92A87F113096862112BE7E6970D4-nb-javac-13-api.jar
-MessageDigest: SHA-256 eb36f3a1714010028d0da5c6be22cdb7b7cc8cc04d618351403a57c9b10adbfc
-MessageDigest: SHA-512 bdf48bef2578cd66df8c3a95e6ad1dc022ee136806674dcf791f43829d050f20b565b367b47a5524fb8d62c0f58187a1e0317429526fd219f324033830ac3e57
\ No newline at end of file
diff --git a/nb/updatecenters/extras/nbjavac.api/release/modules/ext/nb-javac-14-api.jar.external b/nb/updatecenters/extras/nbjavac.api/release/modules/ext/nb-javac-14-api.jar.external
new file mode 100644
index 0000000..e30d396
--- /dev/null
+++ b/nb/updatecenters/extras/nbjavac.api/release/modules/ext/nb-javac-14-api.jar.external
@@ -0,0 +1,6 @@
+CRC:1160927925
+SIZE:250879
+URL:https://netbeans.osuosl.org/binaries/988279FD0A258197BF24190BE44A60BA7EA1FC3C-nb-javac-14-api.jar
+URL:https://hg.netbeans.org/binaries/988279FD0A258197BF24190BE44A60BA7EA1FC3C-nb-javac-14-api.jar
+MessageDigest: SHA-256 76c02474a83a61e92952548c3b59af093d20fb9de82b2aa47a458cdfab538da7
+MessageDigest: SHA-512 9a79f44cf335045a101b43ba8481070c65a5c2f8b274aa77069a751fa2c0da40c60c0b7830373f5a100205bb7d14a54f32ca3ce8c50442568e5de1474a6d5a4a
\ No newline at end of file
diff --git a/nb/updatecenters/extras/nbjavac.impl/manifest.mf b/nb/updatecenters/extras/nbjavac.impl/manifest.mf
index a9caa64..ac72632 100644
--- a/nb/updatecenters/extras/nbjavac.impl/manifest.mf
+++ b/nb/updatecenters/extras/nbjavac.impl/manifest.mf
@@ -2,7 +2,7 @@
 AutoUpdate-Show-In-Client: false
 OpenIDE-Module: org.netbeans.modules.nbjavac.impl
 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/nbjavac/impl/Bundle.properties
-OpenIDE-Module-Specification-Version: 2.1
+OpenIDE-Module-Specification-Version: 2.2
 OpenIDE-Module-Hide-Classpath-Packages: com.sun.tools.javac.**, com.sun.tools.javadoc.**, com.sun.tools.javap.**, com.sun.tools.classfile.**, com.sun.tools.doclint.**
 OpenIDE-Module-Fragment-Host: org.netbeans.libs.javacimpl
 OpenIDE-Module-Provides: org.netbeans.modules.nbjavac
diff --git a/nb/updatecenters/extras/nbjavac.impl/nbproject/project.xml b/nb/updatecenters/extras/nbjavac.impl/nbproject/project.xml
index 8436f37..93ff9e6 100644
--- a/nb/updatecenters/extras/nbjavac.impl/nbproject/project.xml
+++ b/nb/updatecenters/extras/nbjavac.impl/nbproject/project.xml
@@ -35,8 +35,8 @@
             </module-dependencies>
             <public-packages/>
             <class-path-extension>
-                <runtime-relative-path>ext/nb-javac-13-impl.jar</runtime-relative-path>
-                <binary-origin>release/modules/ext/nb-javac-13-impl.jar</binary-origin>
+                <runtime-relative-path>ext/nb-javac-14-impl.jar</runtime-relative-path>
+                <binary-origin>release/modules/ext/nb-javac-14-impl.jar</binary-origin>
             </class-path-extension>
         </data>
     </configuration>
diff --git a/nb/updatecenters/extras/nbjavac.impl/release/modules/ext/nb-javac-13-impl.jar.external b/nb/updatecenters/extras/nbjavac.impl/release/modules/ext/nb-javac-13-impl.jar.external
deleted file mode 100644
index e0f65a5..0000000
--- a/nb/updatecenters/extras/nbjavac.impl/release/modules/ext/nb-javac-13-impl.jar.external
+++ /dev/null
@@ -1,6 +0,0 @@
-CRC:3482904810
-SIZE:3595235
-URL:https://netbeans.osuosl.org/binaries/34E9F9C1BDC61FE7EFCCF305D70960B862DE7815-nb-javac-13-impl.jar
-URL:https://hg.netbeans.org/binaries/34E9F9C1BDC61FE7EFCCF305D70960B862DE7815-nb-javac-13-impl.jar
-MessageDigest: SHA-256 cabea2a079534191d09cba7bfe55c0de45079a04c8ac88926b09d76ed7067879
-MessageDigest: SHA-512 55feeefd61fda43e4a9b461f405e806a751ea35c713b7fc762f149565fd8780828fcce9d73d38edfa89176f00087220bd8b99ee5135f9f551fe40e2a8768ee83
\ No newline at end of file
diff --git a/nb/updatecenters/extras/nbjavac.impl/release/modules/ext/nb-javac-14-impl.jar.external b/nb/updatecenters/extras/nbjavac.impl/release/modules/ext/nb-javac-14-impl.jar.external
new file mode 100644
index 0000000..dc51f2f
--- /dev/null
+++ b/nb/updatecenters/extras/nbjavac.impl/release/modules/ext/nb-javac-14-impl.jar.external
@@ -0,0 +1,6 @@
+CRC:1143111181
+SIZE:3683563
+URL:https://netbeans.osuosl.org/binaries/BABD4BF10C42EE321AED7393BED72C579A486CD3-nb-javac-14-impl.jar
+URL:https://hg.netbeans.org/binaries/ BABD4BF10C42EE321AED7393BED72C579A486CD3-nb-javac-14-impl.jar
+MessageDigest: SHA-256 b6381f69707fb12f2edbbf47d59189bbfbd3390f64d9e7ec9b5d90abc3ba5db4
+MessageDigest: SHA-512 8fee92cc0df7ae2ec034c027cc761d6ca87e609db68a269c993148bc0c611b0a4dd088ac740f7c81bfe8c7e32c0367965cf8187378c02348dd6b2532e46a005c
\ No newline at end of file
diff --git a/nb/updatecenters/extras/nbjavac/src/org/netbeans/modules/nbjavac/Bundle.properties b/nb/updatecenters/extras/nbjavac/src/org/netbeans/modules/nbjavac/Bundle.properties
index 9da78f6..de7fd4e 100644
--- a/nb/updatecenters/extras/nbjavac/src/org/netbeans/modules/nbjavac/Bundle.properties
+++ b/nb/updatecenters/extras/nbjavac/src/org/netbeans/modules/nbjavac/Bundle.properties
@@ -18,6 +18,6 @@
 OpenIDE-Module-Display-Category=Java
 OpenIDE-Module-Long-Description=\
     This library improves the Java editor behavior and enables the Compile on Save features. \
-    Note this plugin does not support JDK 14 language features, like records.
+    Supports JDK-14 features.
 OpenIDE-Module-Name=The nb-javac Java editing support library
 OpenIDE-Module-Short-Description=The nb-javac Java editing support library
diff --git a/nb/updatecenters/licenseinfo.xml b/nb/updatecenters/licenseinfo.xml
index 06e3d8e..b40fdd4 100644
--- a/nb/updatecenters/licenseinfo.xml
+++ b/nb/updatecenters/licenseinfo.xml
@@ -26,8 +26,8 @@
         <comment type="COMMENT_UNSUPPORTED" />
     </fileset>
     <fileset>
-        <file>extras/nbjavac.api/release/modules/ext/nb-javac-13-api.jar.external</file>
-        <file>extras/nbjavac.impl/release/modules/ext/nb-javac-13-impl.jar.external</file>
+        <file>extras/nbjavac.api/release/modules/ext/nb-javac-14-api.jar.external</file>
+        <file>extras/nbjavac.impl/release/modules/ext/nb-javac-14-impl.jar.external</file>
         <license ref="Apache-2.0-ASF" />
         <comment type="COMMENT_UNSUPPORTED" />
     </fileset>
diff --git a/nbbuild/templates/projectized.xml b/nbbuild/templates/projectized.xml
index 20a06c6..d315d2d 100644
--- a/nbbuild/templates/projectized.xml
+++ b/nbbuild/templates/projectized.xml
@@ -177,7 +177,7 @@
         <property name="locmakenbm.brands" value="${brandings}"/>
         <!-- When requires.nb.javac property is true, prepend javac-api and javac-impl on bootclasspath to allow override the default annotation
              processing API located in rt.jar. -->
-        <property name="bootclasspath.prepend.nb" value="${nb_all}/java/libs.javacapi/external/nb-javac-13-api.jar${path.separator}${nb_all}/java/libs.javacimpl/external/nb-javac-13-impl.jar" />
+        <property name="bootclasspath.prepend.nb" value="${nb_all}/java/libs.javacapi/external/nb-javac-14-api.jar${path.separator}${nb_all}/java/libs.javacimpl/external/nb-javac-14-impl.jar" />
         <property name="bootclasspath.prepend.vanilla" value="${nb_all}/nbbuild/external/vanilla-javac-api.jar${path.separator}${nb_all}/nbbuild/external/vanilla-javac-impl.jar" />
         <condition property="bootclasspath.prepend" value="${bootclasspath.prepend.nb}">
             <istrue value="${requires.nb.javac.impl}"/>
diff --git a/php/php.editor/src/org/netbeans/modules/php/editor/csl/InstantRenamerImpl.java b/php/php.editor/src/org/netbeans/modules/php/editor/csl/InstantRenamerImpl.java
index c118b8e..f0ee97a 100644
--- a/php/php.editor/src/org/netbeans/modules/php/editor/csl/InstantRenamerImpl.java
+++ b/php/php.editor/src/org/netbeans/modules/php/editor/csl/InstantRenamerImpl.java
@@ -31,6 +31,7 @@
 import org.netbeans.modules.php.editor.api.elements.FieldElement;
 import org.netbeans.modules.php.editor.api.elements.MethodElement;
 import org.netbeans.modules.php.editor.api.elements.PhpElement;
+import org.netbeans.modules.php.editor.api.elements.TypeConstantElement;
 import org.netbeans.modules.php.editor.model.Model;
 import org.netbeans.modules.php.editor.model.Occurence;
 import org.netbeans.modules.php.editor.model.Occurence.Accuracy;
@@ -83,6 +84,12 @@
                         if (phpModifiers.isPrivate()) {
                             return checkAll(caretOccurence);
                         }
+                    } else if (decl instanceof TypeConstantElement) {
+                        TypeConstantElement cnst = (TypeConstantElement) decl;
+                        PhpModifiers phpModifiers = cnst.getPhpModifiers();
+                        if (phpModifiers.isPrivate()) {
+                            return checkAll(caretOccurence);
+                        }
                     }
                 } else {
                     return checkAll(caretOccurence);
diff --git a/platform/api.htmlui/src/org/netbeans/modules/htmlui/jfx/NbBrowsers.java b/platform/api.htmlui/src/org/netbeans/modules/htmlui/jfx/NbBrowsers.java
index 6d65983..a6b561a 100644
--- a/platform/api.htmlui/src/org/netbeans/modules/htmlui/jfx/NbBrowsers.java
+++ b/platform/api.htmlui/src/org/netbeans/modules/htmlui/jfx/NbBrowsers.java
@@ -81,6 +81,7 @@
             case "Windows":
                 return "win";
             case "Darcula":
+            case "Flat Dark":
                 return "darcula";
         }
         return null;
diff --git a/platform/openide.util.lookup/apichanges.xml b/platform/openide.util.lookup/apichanges.xml
index 99c701f..f3d71c3 100644
--- a/platform/openide.util.lookup/apichanges.xml
+++ b/platform/openide.util.lookup/apichanges.xml
@@ -25,6 +25,33 @@
     <apidef name="lookup">Lookup API</apidef>
 </apidefs>
 <changes>
+    <change id="ProxyLookupController">
+        <api name="lookup"/>
+        <summary>Add ProxyLookup.Controller to set lookups dynamically without 
+            subclassing</summary>
+        <version major="8" minor="43"/>
+        <date year="2020" month="7" day="4"/>
+        <author login="tboudreau"/>
+        <compatibility addition="yes" source="compatible" semantic="compatible" binary="compatible"/>
+        <description>
+            <p>
+                One of the most common usages of  <a href="@TOP@/org/openide/util/lookup/ProxyLookup.html">ProxyLookup</a>
+                is to dynamically change the set of lookups being delegated to. However the 
+                <a href="@TOP@/org/openide/util/lookup/ProxyLookup.html#setLookups-org.openide.util.Lookup...-">setLookups(...)</a>
+                method is <code>protected</code>. To avoid the need to subclass 
+                <a href="@TOP@/org/openide/util/lookup/ProxyLookup.html">ProxyLookup</a>
+                this change introduces
+                <a href="@TOP@/org/openide/util/lookup/ProxyLookup.Controller.html">ProxyLookup.Controller</a>
+                that gives the creator of <a href="@TOP@/org/openide/util/lookup/ProxyLookup.html">ProxyLookup</a>
+                a way to call 
+                <a href="@TOP@/org/openide/util/lookup/ProxyLookup.Controller.html#setLookups-org.openide.util.Lookup...-">setLookups(...)</a>
+                without exposing the method to others having just a reference to 
+                the <a href="@TOP@/org/openide/util/lookup/ProxyLookup.html">ProxyLookup</a>.
+            </p>
+        </description>
+        <class name="ProxyLookup" package="org.openide.util.lookup"/>
+        <issue number="NETBEANS-4699"/>
+    </change>    
     <change id="AbstractProcessorSupportedSource">
         <api name="lookup"/>
         <summary>Declare support for all source levels.</summary>
diff --git a/platform/openide.util.lookup/manifest.mf b/platform/openide.util.lookup/manifest.mf
index 7b0558c..372496f 100644
--- a/platform/openide.util.lookup/manifest.mf
+++ b/platform/openide.util.lookup/manifest.mf
@@ -1,5 +1,5 @@
 Manifest-Version: 1.0
 OpenIDE-Module: org.openide.util.lookup
 OpenIDE-Module-Localizing-Bundle: org/openide/util/lookup/Bundle.properties
-OpenIDE-Module-Specification-Version: 8.42
+OpenIDE-Module-Specification-Version: 8.43
 
diff --git a/platform/openide.util.lookup/src/org/openide/util/lookup/ProxyLookup.java b/platform/openide.util.lookup/src/org/openide/util/lookup/ProxyLookup.java
index e18d04d..17cb46e 100644
--- a/platform/openide.util.lookup/src/org/openide/util/lookup/ProxyLookup.java
+++ b/platform/openide.util.lookup/src/org/openide/util/lookup/ProxyLookup.java
@@ -57,6 +57,25 @@
     public ProxyLookup(Lookup... lookups) {
         data = ImmutableInternalData.EMPTY.setLookupsNoFire(lookups, true);
     }
+    /**
+     * Create a {@code ProxyLookup} whose contents can be set dynamically 
+     * subclassing. The passed
+     * {@link Controller} can be later be used to call
+     * {@link Controller#setLookups} which then 
+     * {@link ProxyLookup#setLookups changes} the lookups this {@code ProxyLookup} 
+     * delegates to. The passed controller may
+     * only be used for <i>one</i> ProxyLookup.
+     *
+     * @param controller A {@link Controller} which can be used to set the lookups
+     * @throws IllegalStateException if the passed controller has already
+     * been attached to another ProxyLookup 
+     * @since 8.43
+     */
+    @SuppressWarnings("LeakingThisInConstructor")
+    public ProxyLookup(Controller controller) {
+        this();
+        controller.setProxyLookup(this);
+    }
 
     /**
      * Create a lookup initially proxying to no others.
@@ -89,7 +108,72 @@
         }
         return map.keySet();
     }
-    
+
+    /**
+     * A controller which allows the set of lookups being proxied to be
+     * set dynamically for those who create the instance of
+     * {@link ProxyLookup}.
+     *
+     * @since 8.43
+     */
+    public static final class Controller {
+
+        private ProxyLookup consumer;
+
+        /**
+         * Creates a new controller to be attached to a {@link ProxyLookup}.
+         * @since 8.43
+         */
+        public Controller() {
+        }
+
+        /**
+         * Set the lookups on the {@link ProxyLookup} this controller controls.
+         * If called before a {@link ProxyLookup} has been attached to this
+         * controller, an IllegalStateException will be thrown.
+         *
+         * @param notifyIn an executor to notify changes in
+         * @param lookups an array of Lookups to be proxied
+         * @throws IllegalStateException if called before this instance
+         * has been passed to the constructor of (exactly one) {@link ProxyLookup}
+         * @since 8.43
+         */
+        public void setLookups(Executor notifyIn, Lookup... lookups) {
+            if (consumer == null) {
+                throw new IllegalStateException("Cannot use Controller until "
+                        + "a ProxyLookup has been created with it.");
+            }
+            consumer.setLookups(notifyIn, lookups);
+        }
+
+        /**
+         * Set the lookups on the {@link ProxyLookup} this controller controls.
+         * If called before a {@link ProxyLookup} has been attached to this
+         * controller, an IllegalStateException will be thrown.
+         *
+         * @param exe An executor to notify in
+         * @param lookups An array of Lookups to be proxied
+         * @throws IllegalStateException if called before this instance
+         * has been passed to the constructor of (exactly one) {@link ProxyLookup}
+         * @since 8.43
+         */
+        public void setLookups(Lookup... lookups) {
+            if (consumer == null) {
+                throw new IllegalStateException("Cannot use Controller until "
+                        + "a ProxyLookup has been created with it.");
+            }
+            setLookups(null, lookups);
+        }
+
+        void setProxyLookup(ProxyLookup lkp) {
+            if (consumer != null) {
+                throw new IllegalStateException("Controller cannot be used "
+                        + "with more than one ProxyLookup.");
+            }
+            consumer = lkp;
+        }
+    }
+
     /**
      * Changes the delegates.
      *
@@ -99,7 +183,7 @@
     protected final void setLookups(Lookup... lookups) {
         setLookups(null, lookups);
     }
-    
+
     /**
      * Changes the delegates immediatelly, notifies the listeners in provided
      * executor, potentially later.
@@ -113,10 +197,10 @@
         Set<Lookup> newL;
         Set<Lookup> current;
         Lookup[] old;
-        
+
         Map<Result,LookupListener> toRemove = new IdentityHashMap<Lookup.Result, LookupListener>();
         Map<Result,LookupListener> toAdd = new IdentityHashMap<Lookup.Result, LookupListener>();
-        
+
         ImmutableInternalData orig;
         synchronized (ProxyLookup.this) {
             orig = getData();
@@ -126,7 +210,7 @@
             }
             arr = setData(newData, lookups, toAdd, toRemove);
         }
-        
+
         // better to do this later than in synchronized block
         for (Map.Entry<Result, LookupListener> e : toRemove.entrySet()) {
             e.getKey().removeLookupListener(e.getValue());
@@ -144,7 +228,7 @@
                 r.collectFires(evAndListeners);
             }
         }
-        
+
         class Notify implements Runnable {
             public void run() {
                 Iterator it = evAndListeners.iterator();
@@ -208,7 +292,7 @@
     public final <T> Item<T> lookupItem(Template<T> template) {
         beforeLookup(template);
 
-        Lookup[] tmpLkps; 
+        Lookup[] tmpLkps;
         synchronized (ProxyLookup.this) {
             tmpLkps = getData().getLookups(false);
         }
@@ -256,14 +340,14 @@
     }
 
     private Collection<Reference<R>> setData(
-        ImmutableInternalData newData, Lookup[] current, 
+        ImmutableInternalData newData, Lookup[] current,
         Map<Result,LookupListener> toAdd, Map<Result,LookupListener> toRemove
     ) {
         assert Thread.holdsLock(ProxyLookup.this);
         assert newData != null;
-        
+
         ImmutableInternalData previous = this.getData();
-        
+
         if (previous == newData) {
             return Collections.emptyList();
         }
@@ -314,14 +398,14 @@
     private static final class R<T> extends WaitableResult<T> {
         /** weak listener & result */
         private final WeakResult<T> weakL;
-        
+
         /** list of listeners added */
         private LookupListenerList listeners;
 
         /** collection of Objects */
         private Collection[] cache;
 
-        
+
         /** associated lookup */
         private ImmutableInternalData data;
 
@@ -330,7 +414,7 @@
         public R(ProxyLookup proxy, Lookup.Template<T> t) {
             this.weakL = new WeakResult<T>(proxy, this, t);
         }
-        
+
         private ProxyLookup proxy() {
             return weakL.result.proxy;
         }
@@ -339,7 +423,7 @@
         private Result<T>[] newResults(int len) {
             return new Result[len];
         }
-        
+
         @Override
         protected void finalize() {
             weakL.result.run();
@@ -369,7 +453,7 @@
                     if (current != data) {
                         continue;
                     }
-                    
+
                     Lookup[] currentLkps = data.getLookups(false);
                     if (currentLkps.length != myLkps.length) {
                         continue BIG_LOOP;
@@ -379,8 +463,8 @@
                             continue BIG_LOOP;
                         }
                     }
-                    
-                    // some other thread might compute the result mean while. 
+
+                    // some other thread might compute the result mean while.
                     // if not finish the computation yourself
                     if (weakL.getResults() != null) {
                         return weakL.getResults();
@@ -548,7 +632,7 @@
         public void resultChanged(LookupEvent ev) {
             collectFires(null);
         }
-        
+
         private static ThreadLocal<R<?>> IN = new ThreadLocal<>();
         protected void collectFires(Collection<Object> evAndListeners) {
             R<?> prev = IN.get();
@@ -563,7 +647,7 @@
                 IN.set(prev);
             }
         }
-        
+
         private void collImpl(Collection<Object> evAndListeners) {
             boolean modified = true;
 
@@ -589,7 +673,7 @@
                     }
                     ll = listeners.getListenerList();
                     assert ll != null;
-                    
+
 
                     // ignore events if they arrive as a result of call to allItems
                     // or allInstances, bellow...
@@ -632,7 +716,7 @@
                     }
                 }
             }
-            
+
             if (modified) {
                 LookupEvent ev = new LookupEvent(this);
                 AbstractLookup.notifyListeners(ll, ev, evAndListeners);
@@ -646,7 +730,7 @@
             boolean callBeforeLookup, boolean callBeforeOnWait
         ) {
             Template<T> template = template();
-            
+
             proxy().beforeLookup(callBeforeLookup, template);
 
             Lookup.Result<T>[] arr = initResults();
@@ -691,11 +775,11 @@
             synchronized (proxy()) {
                 Collection[] cc = getCache();
                 if (cc != oldCC) {
-                    // don't change the cache when it is based on 
+                    // don't change the cache when it is based on
                     // outdated results
                     return;
                 }
-                
+
                 if (cc == null || cc == R.NO_CACHE) {
                     // initialize the cache to indicate this result is in use
                     setCache(cc = new Collection[3]);
@@ -707,14 +791,14 @@
                     cc[indexToCache] = ret;
                 }
             }
-            
+
         }
     }
     private static final class WeakRef<T> extends WeakReference<R> implements Runnable {
         final WeakResult<T> result;
         final ProxyLookup proxy;
         final Template<T> template;
-        
+
         public WeakRef(R r, WeakResult<T> result, ProxyLookup proxy, Template<T> template) {
             super(r);
             this.result = result;
@@ -727,17 +811,17 @@
             proxy.unregisterTemplate(template);
         }
     }
-    
-    
+
+
     private static final class WeakResult<T> extends WaitableResult<T> implements LookupListener, Runnable {
         /** all results */
         private Lookup.Result<T>[] results;
         private final WeakRef<T> result;
-        
+
         public WeakResult(ProxyLookup proxy, R r, Template<T> t) {
             this.result = new WeakRef<T>(r, this, proxy, t);
         }
-        
+
         final void removeListeners() {
             Lookup.Result<T>[] arr = this.getResults();
             if (arr == null) {
@@ -822,15 +906,15 @@
             return allItems();
         }
     } // end of WeakResult
-    
+
     static abstract class ImmutableInternalData extends Object {
         static final ImmutableInternalData EMPTY = new EmptyInternalData();
         static final Lookup[] EMPTY_ARR = new Lookup[0];
 
-        
+
         protected ImmutableInternalData() {
         }
-        
+
         public static ImmutableInternalData create(Object lkp, Map<Template, Reference<R>> results) {
             if (results.size() == 0 && lkp == EMPTY_ARR) {
                 return EMPTY;
@@ -839,7 +923,7 @@
                 Entry<Template,Reference<R>> e = results.entrySet().iterator().next();
                 return new SingleInternalData(lkp, e.getKey(), e.getValue());
             }
-            
+
             return new RealInternalData(lkp, results);
         }
 
@@ -850,7 +934,7 @@
         final Collection<Reference<R>> references() {
             return getResults().values();
         }
-        
+
         final <T> ImmutableInternalData removeTemplate(ProxyLookup proxy, Template<T> template) {
             if (getResults().containsKey(template)) {
                 HashMap<Template,Reference<R>> c = new HashMap<Template, Reference<ProxyLookup.R>>(getResults());
@@ -865,12 +949,12 @@
                 return this;
             }
         }
-        
+
         <T> R<T> findResult(ProxyLookup proxy, ImmutableInternalData[] newData, Template<T> template) {
             assert Thread.holdsLock(proxy);
-            
+
             Map<Template,Reference<R>> map = getResults();
-            
+
             Reference<R> ref = map.get(template);
             R r = (ref == null) ? null : ref.get();
 
@@ -878,7 +962,7 @@
                 newData[0] = this;
                 return convertResult(r);
             }
-            
+
             HashMap<Template, Reference<R>> res = new HashMap<Template, Reference<R>>(map);
             R<T> newR = new R<T>(proxy, template);
             res.put(template, new java.lang.ref.SoftReference<R>(newR));
@@ -887,13 +971,13 @@
         }
         final ImmutableInternalData setLookupsNoFire(Lookup[] lookups, boolean skipCheck) {
             Object l;
-            
+
             if (!skipCheck) {
                 Lookup[] previous = getLookups(false);
                 if (previous == lookups) {
                     return this;
                 }
-            
+
                 if (previous.length == lookups.length) {
                     int same = 0;
                     for (int i = 0; i < previous.length; i++) {
@@ -907,7 +991,7 @@
                     }
                 }
             }
-            
+
             if (lookups.length == 1) {
                 l = lookups[0];
                 assert l != null : "Cannot assign null delegate";
@@ -918,11 +1002,11 @@
                     l = lookups.clone();
                 }
             }
-            
+
             if (isEmpty() && l == EMPTY_ARR) {
                 return this;
             }
-            
+
             return create(l, getResults());
         }
         final Lookup[] getLookups(boolean clone) {
@@ -938,17 +1022,17 @@
             }
         }
         final List<Lookup> getLookupsList() {
-            return Arrays.asList(getLookups(false));            
+            return Arrays.asList(getLookups(false));
         }
 
     } // end of ImmutableInternalData
-    
+
     private static final class SingleInternalData extends ImmutableInternalData {
         /** lookups to delegate to (either Lookup or array of Lookups) */
         private final Object lookups;
         private final Template template;
         private final Reference<ProxyLookup.R> result;
-                
+
         public SingleInternalData(Object lookups, Template<?> template, Reference<ProxyLookup.R> result) {
             this.lookups = lookups;
             this.template = template;
@@ -962,7 +1046,7 @@
         protected Map<Template, Reference<R>> getResults() {
             return Collections.singletonMap(template, result);
         }
-        
+
         protected Object getRawLookups() {
             return lookups;
         }
@@ -990,7 +1074,7 @@
             assert needsStrict = true;
             return needsStrict && !isUnmodifiable(results) ? unmodifiableMap(results) : results;
         }
-        
+
         @Override
         protected Object getRawLookups() {
             return lookups;
@@ -1008,7 +1092,7 @@
             return res;
         }
     }
-    
+
     private static final class EmptyInternalData extends ImmutableInternalData {
         EmptyInternalData() {
         }
diff --git a/platform/openide.util.lookup/test/unit/src/org/openide/util/lookup/ProxyLookupFactoryMethodsTest.java b/platform/openide.util.lookup/test/unit/src/org/openide/util/lookup/ProxyLookupFactoryMethodsTest.java
new file mode 100644
index 0000000..715e9f1
--- /dev/null
+++ b/platform/openide.util.lookup/test/unit/src/org/openide/util/lookup/ProxyLookupFactoryMethodsTest.java
@@ -0,0 +1,196 @@
+/*
+ * 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.openide.util.lookup;
+
+import java.util.Arrays;
+import static java.util.Arrays.asList;
+import java.util.HashSet;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.openide.util.Lookup;
+import org.openide.util.LookupEvent;
+import org.openide.util.LookupListener;
+import org.openide.util.lookup.ProxyLookupFactoryMethodsTest.TThreadFactory.TThread;
+
+/**
+ *
+ * @author Tim Boudreau
+ */
+public class ProxyLookupFactoryMethodsTest {
+
+    private ProxyLookup.Controller controller1;
+    private ProxyLookup.Controller controller2;
+
+    private Lookup createWithSingleConsumer(Lookup... lookups) {
+        ProxyLookup.Controller controller = new ProxyLookup.Controller();
+        controller1 = controller;
+        ProxyLookup result = new ProxyLookup(controller);
+        result.setLookups(lookups);
+        return result;
+    }
+
+    private Lookup createWithBiConsumer(Lookup... lookups) {
+        ProxyLookup.Controller controller = new ProxyLookup.Controller();
+        controller2 = controller;
+        ProxyLookup result = new ProxyLookup(controller);
+        result.setLookups(lookups);
+        return result;
+    }
+
+    @Test
+    public void testCannotUseControllerOnMultipleLookups() {
+        ProxyLookup.Controller ctrllr = new ProxyLookup.Controller();
+        ProxyLookup first = new ProxyLookup(ctrllr);
+        assertTrue(first.lookupAll(String.class).isEmpty());
+        try {
+            ProxyLookup second = new ProxyLookup(ctrllr);
+            fail("Exception should have been thrown using controller more than "
+                    + "once but was able to create " + second);
+        } catch (IllegalStateException ex) {
+            // ok
+        }
+    }
+
+    @Test
+    public void testStartWithEmptyController() {
+        ProxyLookup.Controller ctrllr = new ProxyLookup.Controller();
+        ProxyLookup lkp = new ProxyLookup(ctrllr);
+        assertTrue(lkp.lookupAll(String.class).isEmpty());
+        ctrllr.setLookups(Lookups.fixed("a"), Lookups.fixed("b"));
+        assertEquals(new HashSet<>(Arrays.asList("a", "b")),
+                new HashSet<>(lkp.lookupAll(String.class)));
+    }
+
+    @Test
+    public void testSimpleFactory() {
+        Lookup a = Lookups.fixed("a");
+        Lookup b = Lookups.fixed("b");
+        Lookup c = Lookups.fixed("c");
+
+        Lookup target = createWithSingleConsumer(a, b);
+        assertNotNull(controller1);
+        assertTrue(target.lookupAll(String.class).containsAll(asList("a", "b")));
+        assertFalse(target.lookupAll(String.class).contains("c"));
+
+        controller1.setLookups(new Lookup[]{a, b, c});
+        assertTrue(target.lookupAll(String.class).containsAll(asList("a", "b", "c")));
+
+        controller1.setLookups(new Lookup[0]);
+        assertTrue(target.lookupAll(String.class).isEmpty());
+    }
+
+    @Test
+    public void testThreadedFactory() throws Throwable {
+        ExecutorService svc = Executors.newSingleThreadExecutor(new TThreadFactory());
+        Lookup a = Lookups.fixed("a");
+        Lookup b = Lookups.fixed("b");
+        Lookup c = Lookups.fixed("c");
+
+        Lookup target = createWithBiConsumer(a, b);
+        Lookup.Result<String> result = target.lookupResult(String.class);
+
+        LL lis = new LL();
+        result.addLookupListener(lis);
+        // Ugh, ProxyLookup.LazyList does not implement the contract
+        // of Collection.equals().
+        assertEquals(new HashSet<>(result.allInstances()), new HashSet<>(Arrays.asList("a", "b")));
+
+        assertNotNull(controller2);
+        assertTrue(target.lookupAll(String.class).containsAll(asList("a", "b")));
+        assertFalse(target.lookupAll(String.class).contains("c"));
+
+        controller2.setLookups(svc, new Lookup[]{a, b, c});
+
+        lis.assertNotifiedInExecutor();
+
+        assertTrue(target.lookupAll(String.class).containsAll(asList("a", "b", "c")));
+        assertEquals(new HashSet<>(result.allInstances()), new HashSet<>(Arrays.asList("a", "b", "c")));
+
+        controller2.setLookups(svc, new Lookup[0]);
+        lis.assertNotifiedInExecutor();
+        assertTrue(target.lookupAll(String.class).isEmpty());
+        assertTrue(result.allInstances().isEmpty());
+
+        controller2.setLookups(null, new Lookup[]{b, c});
+        assertTrue(target.lookupAll(String.class).containsAll(asList("b", "c")));
+        assertEquals(new HashSet<>(result.allInstances()), new HashSet<>(Arrays.asList("b", "c")));
+        lis.assertNotifiedSynchronously();
+    }
+
+    static final class TThreadFactory implements ThreadFactory {
+
+        @Override
+        public Thread newThread(Runnable r) {
+            return new TThread(r);
+        }
+
+        static class TThread extends Thread {
+
+            TThread(Runnable r) {
+                super(r);
+                setName("test-thread");
+                setDaemon(true);
+            }
+        }
+    }
+
+    static class LL implements LookupListener {
+
+        private Thread notifyThread;
+        private CountDownLatch latch = new CountDownLatch(1);
+
+        void assertNotifiedInExecutor() throws InterruptedException {
+            CountDownLatch l;
+            synchronized (this) {
+                l = latch;
+            }
+            latch.await(10, TimeUnit.SECONDS);
+            Thread t;
+            synchronized (this) {
+                t = notifyThread;
+                notifyThread = null;
+            }
+            assertNotNull(t);
+            assertTrue(t instanceof TThread);
+        }
+
+        void assertNotifiedSynchronously() throws InterruptedException {
+            assertSame(Thread.currentThread(), notifyThread);
+        }
+
+        @Override
+        public void resultChanged(LookupEvent ev) {
+            CountDownLatch l;
+            synchronized (this) {
+                notifyThread = Thread.currentThread();
+                l = latch;
+                latch = new CountDownLatch(1);
+                assert l != null;
+            }
+            l.countDown();
+        }
+
+    }
+
+}