Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

How to add support in NetBeans to a programming language using LSP

You will need Apache NetBeans source code. Please download or clone it from here and build it.

1. File Type Recognition

The first thing to do is for NetBeans to be able to recognize the file type. E.g. if you are adding support for the kotlin programming language you would like NetBeans editor to be able to recognize .kt source files.

...

See Adding New Language Support.

2. Custom project types

See Adding New Language Support.

3. Semantic Syntax highlighting and braces matching

See the File Type Integration Tutorial for more details on how to add File Type recognition support. A DataObject file will be created with a number of annotations.

Important Note! Make sure to create your module inside your cloned Apache NetBeans source code. Your module will need some files from nbbuild folder.

Take a look at java/kotlin.editor/src/org/netbeans/modules/kotlin/editor/KtDataObject.java and rust/rust.sources/src/org/netbeans/modules/rust/sources/rs/RustFileDataObject.java as examples of DataObjects.

Hint! To open a source file easily, click on Window → Favorites and navigate to the source file (e.g. a .kt file if you are adding support for kotlin, or a .rs source file for Rust)

2. Syntax highlighting

Without syntax highlighting, NetBeans opens the source file as a text file. To add syntax highlighting to your source file, follow the instructions provided in LSP Client demo - (ba)sh language server tutorial.

  1. Download the TextMate json file for your language. A list of Textmate syntaxes is provided here and here but in other places, too. Let's call it my-grammar.json for the purposes of this example. E.g. for Kotlin this should would be Kotlin.tmLanguage.json and for Rust rust.tmLanguage.json.
  2. Right-click on Libraries folder of your module project and select Add Module Dependency. Enter "TextMate Lexer" inside Filter and add the module found in the Module list as a dependency to your module.

  3. Into In the DataObject that you created in the previous chapter, add this annotation:

    @GrammarRegistration(grammar="my-grammar.json", mimeType="text/sh")

    and import it:

    import org.netbeans.modules.textmate.lexer.api.GrammarRegistration;

If you now open your source file in NetBeans again, as described in the previous chaptervia the Windows→Favourites tab, you should be able to see the various parts of the languages language (e.g. keywords) to be coloured as defined in the TextMate grammar file. Braces matching is also there. Take a look at java/kotlin.editor/src/org/netbeans/modules/kotlin/editor/KtDataObject.java as an example.

...

4. 5.

...

Code Completion, Formatting and Navigation

To add support for language-specific features like code completion, navigation, etc. you need the help of a Language Protocol Server.  Again, follow the instructions provided in LSP Client demo - (ba)sh language server tutorial.

...

One needs to tell NetBeans to download the LSP binary the first time the user opens a file of the language and to add support for it using the new LSP API provided by NetBeans.

How to add LSP support

Assuming you have download the LSP for your language (as it will be described in the next section), one needs to:

  1. add . See a list of Language Server implementations and another list of Language Server Implementations.For example, to add support for Kotlin programming language, download and build kotlin-language-server. Add dependencies to "LSP Client" and "MIME Lookup API" modules to your module, as described in the previous chapter:

  2. create Create a new class that implements LanguageServerProvider.

See for example, ShellClient or java/kotlin.editor/src/org/netbeans/modules/kotlin/editor/lsp/KotlinLSPServer. E.g. for Kotlin, you will need to provide the path to kotlin-language-server/server/build/install/server/bin/kotlin-language-server inside the overriden startServer() method of this class::

@MimeRegistration(mimeType="text/x-kotlin", service=LanguageServerProvider.class)
public class KotlinLSPServer implements LanguageServerProvider {
    private  static  finalServerRestarter Loggerrestarter LOG = Loggerlookup.getLoggerlookup(KotlinLSPServerServerRestarter.class.getName());
);
        Preferences settingsNode = Utils.settings().node(Utils.NODE_LSP);
        settingsNode.addPreferenceChangeListener(new PreferenceChangeListener() {
            @Override
            public LanguageServerDescription startServer(Lookup lookup) { void preferenceChange(PreferenceChangeEvent evt) {
                if (evt.getKey() == null || Utils.KEY_LOCATION.equals(evt.getKey())) {
                    restarter.restart();
                    settingsNode.removePreferenceChangeListener(this);
                }
            }
        });
        File server = Utils.getServer();
        if (server == null) {
            return null;
        }
        try {
            ProcessBuilder builder = new ProcessBuilder(new String[] {server.getAbsolutePath()}).directory(server.getParentFile().getParentFile());
            Map<String, String> env = builder.environment();
            File mvnCommand = InstalledFileLocator.getDefault("kotlin-language-server/server/build/install/server/bin/kotlin-language-server");
).locate("maven/bin/mvn", null, false);
            tryif (mvnCommand != null) {
                     return LanguageServerDescription.create(p.getInputStream(), p.getOutputStream(), env.put("PATH", env.get("PATH") + File.pathSeparator + mvnCommand.getParentFile().getAbsolutePath());
            }
     
            Process p = builder.start();
            return LanguageServerDescription.create(p);
        } catch (IOException ex) {
            LOG.log(Level.FINE, null, ex);
            return null;
        }
    }

}

Important note! It is yet to be decided where to install and how to access the (pre-)built language servers. 

Open the source file again and try code completion, navigation (e.g. jump to definition, peek definition), find all references, symbol search etc.

4. Custom project types

You may want to create a new "Project Type" for your specific language. For instance, "Rust" projects usually have a folder structure defined by the "cargo" tool.

The NetBeans Project Type Tutorial is a good starting point.

5. Refactoring

6.  Error Hints/Fixes/Suggestions

7. Debugging

---------

Other support you may need to provide:

  • jump to definition, peek definition, find all references, symbol search
  • types and documentation on hover
  • code formatting
  • refactoring (e.g. rename, move)
  • error squiggles and apply suggestions from errors
  • snippets
  • build tasks

...

Utils helper class will provide the location of the downloaded LSP file.

public final class Utils {

    public static final String NODE_LSP = "lsp";
    public static final String KEY_LOCATION = "location";

    private Utils() {
    }

    public static Preferences settings() {
        return NbPreferences.forModule(Utils.class);
    } 

    public static File getServer() {
        Preferences settingsNode = Utils.settings().node(Utils.NODE_LSP);
        String location = settingsNode.get(Utils.KEY_LOCATION, "");
        File serverExecutable = new File(new File(location, "bin"), "kotlin-language-server" + (Utilities.isWindows() ? ".bat" : ""));
        if (!serverExecutable.isFile()) {
            return null;
        }
        return serverExecutable;
    }
}

How to add a Hint to download the LSP binary

A list of Language Server implementations and another list of Language Server Implementations. For example, to add support for Kotlin programming language, you need to download kotlin-language-server.

A thread that is executed @OnStart

@OnStart
public class UnconfiguredHint implements Runnable {

    private static final Set<String> KOTLIN_MIME_TYPES = new HashSet<>(Arrays.asList("text/x-kotlin"));

    @Override
    public void run() {
        EditorRegistry.addPropertyChangeListener((PropertyChangeEvent evt) -> updateFocused());
        Utils.settings().addPreferenceChangeListener((PreferenceChangeEvent evt) -> updateFocused());
        updateFocused();
    }

    @Messages("ERR_KotlinSupportMissing=Enhanced Kotlin support is missing, click to install.")
    private synchronized void updateFocused() {
        JTextComponent selectedComponent = EditorRegistry.focusedComponent();
        if (selectedComponent == null) {
            return ;
        }
        Document doc = selectedComponent.getDocument();
        FileObject file = NbEditorUtilities.getFileObject(doc);
        if (file == null || !KOTLIN_MIME_TYPES.contains(FileUtil.getMIMEType(file))) {
            return ;
        }
        List<ErrorDescription> errors = new ArrayList<>();
        File server = Utils.getServer();
        if (server == null) {
            errors.add(ErrorDescriptionFactory.createErrorDescription(Severity.WARNING, Bundle.ERR_KotlinSupportMissing(), Collections.singletonList(new InstallKotlinLSP()), doc, 0));
        }
        HintsController.setErrors(doc, UnconfiguredHint.class.getName(), errors);
    }
    
    private static final class InstallKotlinLSP implements Fix {

        @Override
        public String getText() {
            return "Install Kotlin LSP";
        }

        @Override
        @Messages("DN_KotlinLSP=Kotlin LSP")
        public ChangeInfo implement() throws Exception {
            PluginInstaller.getDefault().install("org.netbeans.libs.kotlin.lsp", null, null, Bundle.DN_KotlinLSP()); // NOI18N
            return null;
        }
    
    }

will display the hint on the editor to download the LSP server. But, modifications are needed on nb/updatecenters.

public class Install extends ModuleInstall {

    @Override
    public void restored() {
        File serverCache = Places.getCacheSubdirectory("kotlin-lsp/server-1.3.3");
        File serverDir = new File(serverCache, "server");
        File executable = new File(new File(serverDir, "bin"), "kotlin-language-server");

        if (!executable.isFile()) {
            File serverZip = InstalledFileLocator.getDefault().locate("kotlin-lsp/server-1.3.3.zip", null, false);
            try (ZipFile zf = new ZipFile(serverZip)) {
                Enumeration<? extends ZipEntry> en = zf.entries();

                while (en.hasMoreElements()) {
                    ZipEntry ze = en.nextElement();
                    if (ze.isDirectory()) {
                        continue;
                    }
                    File target = new File(serverCache, ze.getName());
                    target.getParentFile().mkdirs();
                    try (OutputStream out = new FileOutputStream(target)) {
                        FileUtil.copy(zf.getInputStream(ze), out);
                    }
                }
                if (!Utilities.isWindows()) {
                    executable.setExecutable(true);
                }
                for (ModuleInfo mi : Lookup.getDefault().lookupAll(ModuleInfo.class)) {
                    if ("org.netbeans.modules.kotlin.editor".equals(mi.getCodeNameBase())) {
                        Class<?> utils = mi.getClassLoader().loadClass("org.netbeans.modules.kotlin.editor.Utils");
                        NbPreferences.forModule(utils).node("lsp").put("location", serverDir.getAbsolutePath());
                    }
                }
            } catch (IOException | ClassNotFoundException ex) {
                Exceptions.printStackTrace(ex);
            }
        }
    }

}

More changes are needed. The interested reader can study java/kotlin.editor and nb/updatecenters/extras/libs.kotlin.lsp modules for more information.

Open the source file again and try code completion, formatting, navigation (e.g. jump to definition, peek definition), find all references, symbol search etc.

6. Types and documentation on hover

8. Configuration

See Adding New Language Support.

9. Refactoring

See Adding New Language Support.

10. Error squiggles and apply suggestions from errors

11. Debugging

12. Snippets

13. Build tasks

Plugins supported by LSP

Kotlin plugin is an example of a language support using LSP.

...

NetBeans Specific Resources

...