+ While NetBeans uses mimetypes as primary identifier for source types, + LSP uses languageId as a carrier to inform what language/type of file + is being edited. Even a single language server can provide support + for multiple languages. One such example is the typescript language + server, which defaults to typescript, but also supports the + typescript variant that embeds react templates (TSX). +
+
+ To support this an implementation can provide a resolver. The
+ resolver has to implement the interface
+ org.netbeans.modules.lsp.client.spi.LanguageIdResolver
+ and will be provided by a lookup provided to the
+ LanguageServerDescription. For each opened file
+ the resolver will be called with the file object and will return
+ the language id if it can resolve it for the given file.
+
Language servers can provide a language id mapper to allow customization + * of the resolving process.
+ * + * @since 1.33.0 + */ +public interface LanguageIdResolver { + /** + * Resolve the language id for the given file object. + * + * @param fileObject target to resolve the langeuge id for + * @return the determined language id or {@code null} if that can't be found. + */ + public String resolveLanguageId(FileObject fileObject); +} diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/spi/LanguageServerProvider.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/spi/LanguageServerProvider.java index eea786f3fe34..48fb06d3a0bf 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/spi/LanguageServerProvider.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/spi/LanguageServerProvider.java @@ -57,25 +57,47 @@ public static final class LanguageServerDescription { * @param process the process of the running language server, or null if none. * @return an instance of LanguageServerDescription */ - public static @NonNull LanguageServerDescription create(@NonNull InputStream in, @NonNull OutputStream out, @NullAllowed Process process) { - return new LanguageServerDescription(in, out, process, null); + public static @NonNull LanguageServerDescription create( + @NonNull InputStream in, + @NonNull OutputStream out, + @NullAllowed Process process) { + return create(in, out, process, null); + } + + /** + * Create the description of a running language server. + * + * @param in the InputStream that should be used to communicate with the server + * @param out the OutputStream that should be used to communicate with the server + * @param process the process of the running language server, or null if none. + * @param lookup lookup to be provided by the server + * @return an instance of LanguageServerDescription + */ + public static @NonNull LanguageServerDescription create( + @NonNull InputStream in, + @NonNull OutputStream out, + @NullAllowed Process process, + @NullAllowed Lookup lookup) { + return new LanguageServerDescription(in, out, process, null, lookup == null ? Lookup.EMPTY : lookup); } static @NonNull LanguageServerDescription create(@NonNull LanguageServer server) { - return new LanguageServerDescription(null, null, null, server); + return new LanguageServerDescription(null, null, null, server, Lookup.EMPTY); } private final InputStream in; private final OutputStream out; private final Process process; private final LanguageServer server; + private final Lookup lookup; private LSPBindings bindings; - private LanguageServerDescription(InputStream in, OutputStream out, Process process, LanguageServer server) { + private LanguageServerDescription(InputStream in, OutputStream out, Process process, LanguageServer server, Lookup lookup) { this.in = in; this.out = out; this.process = process; this.server = server; + this.lookup = lookup; } static { @@ -110,6 +132,11 @@ public void setBindings(LanguageServerDescription desc, LSPBindings bindings) { desc.bindings = bindings; } + @Override + public Lookup getLookup(LanguageServerDescription desc) { + return desc.lookup; + } + @Override public LanguageServerDescription createLanguageServerDescription(LanguageServer server) { return LanguageServerDescription.create(server); diff --git a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/options/LanguageStorageTest.java b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/options/LanguageStorageTest.java index 8cd5eea1b9c3..89b4a020c995 100644 --- a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/options/LanguageStorageTest.java +++ b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/options/LanguageStorageTest.java @@ -93,7 +93,7 @@ public FileObject findResource(String name) { DataObject testDO = DataObject.find(testFO); assertEquals("org.openide.loaders.DefaultDataObject", testDO.getClass().getName()); - LanguageStorage.store(Arrays.asList(new LanguageDescription("t", "txt", FileUtil.toFile(grammar).getAbsolutePath(), null, "txt", null, false))); + LanguageStorage.store(Arrays.asList(new LanguageDescription("t", "txt", FileUtil.toFile(grammar).getAbsolutePath(), null, "txt", null, false, null))); assertEquals("text/x-ext-t", FileUtil.getMIMEType(testFO)); DataObject recognized = DataObject.find(testFO); @@ -107,7 +107,7 @@ public FileObject findResource(String name) { Language> l = MimeLookup.getLookup("text/x-ext-t").lookup(Language.class); assertNotNull(l); - LanguageStorage.store(Arrays.asList(new LanguageDescription("t", "txt", FileUtil.toFile(grammar).getAbsolutePath(), null, "txt", null, false))); + LanguageStorage.store(Arrays.asList(new LanguageDescription("t", "txt", FileUtil.toFile(grammar).getAbsolutePath(), null, "txt", null, false, null))); LanguageStorage.store(Collections.emptyList()); diff --git a/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptDataObjectDataObject.java b/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptDataObjectDataObject.java index a3db16474ea2..7f2c633a5a1c 100644 --- a/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptDataObjectDataObject.java +++ b/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptDataObjectDataObject.java @@ -44,7 +44,7 @@ @MIMEResolver.ExtensionRegistration( displayName = "#LBL_TypeScriptDataObject_LOADER", mimeType = TYPESCRIPT_MIME_TYPE, - extension = {"ts"}, + extension = {"ts", "tsx"}, position = 193 // lower than 218 as CND also recognizes .ts file ) @DataObject.Registration( diff --git a/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptLSP.java b/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptLSP.java index c54c6f33ccbf..16d140dd5ea0 100644 --- a/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptLSP.java +++ b/webcommon/typescript.editor/src/org/netbeans/modules/typescript/editor/TypeScriptLSP.java @@ -26,12 +26,14 @@ import org.netbeans.api.editor.mimelookup.MimeRegistration; import org.netbeans.api.options.OptionsDisplayer; import org.netbeans.modules.javascript.nodejs.api.NodeJsSupport; +import org.netbeans.modules.lsp.client.spi.LanguageIdResolver; import org.netbeans.modules.lsp.client.spi.LanguageServerProvider; import org.openide.awt.NotificationDisplayer; import org.openide.modules.InstalledFileLocator; import org.openide.util.ImageUtilities; import org.openide.util.Lookup; import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.Lookups; import static org.netbeans.modules.typescript.editor.TypeScriptEditorKit.TYPESCRIPT_ICON; import static org.netbeans.modules.typescript.editor.TypeScriptEditorKit.TYPESCRIPT_MIME_TYPE; @@ -45,6 +47,14 @@ public class TypeScriptLSP implements LanguageServerProvider { private static final Logger LOG = Logger.getLogger(TypeScriptLSP.class.getName()); + private static final LanguageIdResolver LANGUAGE_ID_RESOLVER = fo -> { + if("tsx".equalsIgnoreCase(fo.getExt())) { + return "typescriptreact"; + } else { + return "typescript"; + } + }; + private final AtomicBoolean missingNodeWarningIssued = new AtomicBoolean(); @Override @@ -64,7 +74,12 @@ public LanguageServerDescription startServer(Lookup lookup) { File server = InstalledFileLocator.getDefault().locate("typescript-lsp/node_modules/typescript-language-server/lib/cli.mjs", "org.netbeans.modules.typescript.editor", false); try { Process p = new ProcessBuilder(new String[] {node, server.getAbsolutePath(), "--stdio"}).directory(server.getParentFile().getParentFile()).redirectError(ProcessBuilder.Redirect.INHERIT).start(); - return LanguageServerDescription.create(p.getInputStream(), p.getOutputStream(), p); + return LanguageServerDescription.create( + p.getInputStream(), + p.getOutputStream(), + p, + Lookups.fixed(LANGUAGE_ID_RESOLVER) + ); } catch (IOException ex) { LOG.log(Level.FINE, null, ex); return null;