From beea5ce5ca5c83614f8875a2c9f89671af003a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Fri, 20 Dec 2024 20:56:39 +0100 Subject: [PATCH 1/2] LSP/Textmate: Implement multiview support for generic/textmate based syntax support Closes: #5204 --- ide/lsp.client/nbproject/project.xml | 9 ++++ .../lsp/client/options/GenericDataObject.java | 13 +++--- .../lsp/client/options/LanguageStorage.java | 42 +++++++++++++++++-- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/ide/lsp.client/nbproject/project.xml b/ide/lsp.client/nbproject/project.xml index b5bb7c5e5a46..f2ef766bd0dc 100644 --- a/ide/lsp.client/nbproject/project.xml +++ b/ide/lsp.client/nbproject/project.xml @@ -85,6 +85,15 @@ 1.50 + + org.netbeans.core.multiview + + + + 1 + 1.71 + + org.netbeans.libs.flexmark diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/options/GenericDataObject.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/options/GenericDataObject.java index c438bebfd16b..9f70924a531a 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/options/GenericDataObject.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/options/GenericDataObject.java @@ -20,9 +20,7 @@ import java.awt.Image; import java.beans.PropertyVetoException; -import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Method; @@ -30,7 +28,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import javax.imageio.ImageIO; +import org.netbeans.core.spi.multiview.text.MultiViewEditorElement; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.loaders.DataLoaderPool; @@ -41,7 +39,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.Exceptions; -import org.openide.util.ImageUtilities; +import org.openide.util.Lookup; /** * @@ -57,7 +55,7 @@ public class GenericDataObject extends MultiDataObject { public GenericDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException, IOException { super(pf, loader); this.mimeType = FileUtil.getMIMEType(pf); - registerEditor(mimeType, false); + registerEditor(mimeType, true); synchronized (REGISTRY) { REGISTRY.add(new WeakReference<>(this)); } @@ -144,5 +142,8 @@ public static Factory factory() { } }; } -} + public static MultiViewEditorElement createEditor(Lookup lkp) { + return new MultiViewEditorElement(lkp); + } +} \ No newline at end of file diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/options/LanguageStorage.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/options/LanguageStorage.java index a00dfde1909b..31d61b38fce0 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/options/LanguageStorage.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/options/LanguageStorage.java @@ -33,18 +33,19 @@ import java.util.Set; import java.util.stream.Collectors; import javax.swing.event.ChangeEvent; -import org.eclipse.tm4e.core.registry.IRegistryOptions; -import org.eclipse.tm4e.core.registry.Registry; import org.netbeans.modules.lsp.client.debugger.api.RegisterDAPBreakpoints; import org.eclipse.tm4e.core.internal.grammar.raw.RawGrammarReader; import org.eclipse.tm4e.core.registry.IGrammarSource; +import org.netbeans.core.spi.multiview.MultiViewFactory; import org.netbeans.modules.textmate.lexer.TextmateTokenId; import org.netbeans.spi.navigator.NavigatorPanel; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.loaders.DataLoaderPool; import org.openide.loaders.DataObject; +import org.openide.modules.OnStart; import org.openide.util.Exceptions; +import org.openide.util.NbBundle.Messages; import org.openide.util.NbPreferences; /** @@ -53,6 +54,23 @@ */ public class LanguageStorage { + /** + * Startup handler for language store. This handler reapplies the language + * descriptions at startup so that the runtime view matches the one expected + * by the running IDE. This is mostly relevant when the IDE requires new + * files (like the MultiView description when that feature was introduced). + */ + @OnStart + public static class StartupHandler implements Runnable { + + @Override + public void run() { + // load language definitions and reapply them + store(load()); + } + + } + private static final String KEY = "language.descriptions"; static List load() { @@ -60,6 +78,7 @@ static List load() { return Arrays.stream(new Gson().fromJson(descriptions, LanguageDescription[].class)).collect(Collectors.toList()); } + @Messages("Source=&Source") static void store(List languages) { Set originalMimeTypes = load().stream().map(ld -> ld.mimeType).collect(Collectors.toSet()); Set mimeTypesToClear = new HashSet<>(originalMimeTypes); @@ -74,7 +93,7 @@ static void store(List languages) { Exceptions.printStackTrace(ex); } } - + for (FileObject children : mimeResolver.getChildren()) { if ("synthetic".equals(children.getAttribute(LanguageServersPanel.class.getName()))) { try { @@ -133,6 +152,19 @@ static void store(List languages) { loader.setAttribute("dataObjectClass", GenericDataObject.class.getName()); loader.setAttribute("mimeType", description.mimeType); + deleteConfigFileIfExists("Editors/" + description.mimeType + "/MultiView/source.instance"); + FileObject multiViewRegistration = FileUtil.createData(FileUtil.getConfigRoot(), "Editors/" + description.mimeType + "/MultiView/source.instance"); + Method createMultiViewDescription = MultiViewFactory.class.getDeclaredMethod("createMultiViewDescription", Map.class); + multiViewRegistration.setAttribute("methodvalue:instanceCreate", createMultiViewDescription); + multiViewRegistration.setAttribute("instanceClass", "org.netbeans.core.multiview.ContextAwareDescription"); + multiViewRegistration.setAttribute("class", GenericDataObject.class.getName()); + multiViewRegistration.setAttribute("mimeType", description.mimeType); + multiViewRegistration.setAttribute("displayName", Bundle.Source()); + multiViewRegistration.setAttribute("preferredID", "lsp.source"); + multiViewRegistration.setAttribute("persistenceType", 1); + multiViewRegistration.setAttribute("position", 100); + multiViewRegistration.setAttribute("method", "createEditor"); + FileObject icon = FileUtil.getConfigFile("Loaders/" + description.mimeType + "/Factories/icon.png"); if (icon != null) { icon.delete(); @@ -146,8 +178,9 @@ static void store(List languages) { } loader.setAttribute("iconBase", icon.getNameExt()); + multiViewRegistration.setAttribute("iconBase", icon.getNameExt()); } - + if (description.languageServer != null && !description.languageServer.isEmpty()) { FileObject langServer = FileUtil.createData(FileUtil.getConfigRoot(), "Editors/" + description.mimeType + "/org-netbeans-modules-lsp-client-options-GenericLanguageServer.instance"); langServer.setAttribute("command", description.languageServer.split(" ")); @@ -187,6 +220,7 @@ static void store(List languages) { deleteConfigFileIfExists("Loaders/" + mimeType + "/Factories/data-object.instance"); deleteConfigFileIfExists("Editors/" + mimeType + "/generic-breakpoints.instance"); deleteConfigFileIfExists("Editors/" + mimeType + "/GlyphGutterActions/generic-toggle-breakpoint.shadow"); + deleteConfigFileIfExists("Editors/" + mimeType + "/MultiView/source.instance"); } catch (Exception ex) { Exceptions.printStackTrace(ex); } From 2c5b2e36be73ef7f13cd68d4f8e280ad502d4ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Thu, 13 Mar 2025 21:40:40 +0100 Subject: [PATCH 2/2] Don't raise user visible warning if declarative MIMERegistration can't be parsed After beea5ce (LSP/Textmate: Implement multiview support for generic/textmate based syntax support) it was observed, that occasionally an IllegalArgumentException was raised from the MimeResolverImpl (see full stacktrace below). The error message indicates that the description file is not valid anymore. This matches the renewal of the registration at startup. Log this on info level, so that is not raised to users attention, as for the LSP case, the updated file is available milliseconds later and should regenerate the resolvers in the next iteration. java.lang.IllegalArgumentException: MultiFileObject@d1d4bdb[Services/MIMEResolver/c.xml] parent: MultiFileObject@27ddc920[Services/MIMEResolver] validFlag: false validRoot: MultiFileObject@49354fe[root of SystemFileSystem[org.netbeans.core.startup.layers.SystemFileSystem@20cea7a4]] isValidRoot: true at org.netbeans.modules.openide.filesystems.declmime.MIMEResolverImpl.create(MIMEResolverImpl.java:344) at org.netbeans.modules.openide.filesystems.declmime.MIMEResolverImpl.forDescriptor(MIMEResolverImpl.java:67) at org.openide.filesystems.MIMESupport$CachedFileObject.declarativeResolvers(MIMESupport.java:256) at org.openide.filesystems.MIMESupport$CachedFileObject.getResolvers(MIMESupport.java:197) at org.openide.filesystems.MIMESupport$CachedFileObject.resolveMIME(MIMESupport.java:340) at org.openide.filesystems.MIMESupport$CachedFileObject.getMIMEType(MIMESupport.java:287) at org.openide.filesystems.MIMESupport.findMIMEType(MIMESupport.java:111) at org.openide.filesystems.FileUtil.getMIMEType(FileUtil.java:1388) at org.openide.filesystems.FileObject.getMIMEType(FileObject.java:642) at org.openide.filesystems.AbstractFileObject.getMIMEType(AbstractFileObject.java:169) at org.openide.filesystems.MultiFileObject.getMIMEType(MultiFileObject.java:623) at org.openide.filesystems.MultiFileObject.getMIMEType(MultiFileObject.java:623) at org.openide.loaders.DataLoaderPool.allLoaders(DataLoaderPool.java:307) at org.openide.loaders.DataLoaderPool.findDataObject(DataLoaderPool.java:505) [catch] at org.openide.loaders.FolderList.createBoth(FolderList.java:743) at org.openide.loaders.FolderList.getObjects(FolderList.java:539) at org.openide.loaders.FolderList.access$600(FolderList.java:52) at org.openide.loaders.FolderList$ListTask.computeResult(FolderList.java:938) at org.openide.loaders.FolderList$ListTask.run(FolderList.java:914) at org.openide.util.RequestProcessor$Task.run(RequestProcessor.java:1403) at org.netbeans.modules.openide.util.GlobalLookup.execute(GlobalLookup.java:45) at org.openide.util.lookup.Lookups.executeWith(Lookups.java:287) at org.openide.util.RequestProcessor$Processor.run(RequestProcessor.java:2018) --- .../src/org/openide/filesystems/MIMESupport.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/openide.filesystems/src/org/openide/filesystems/MIMESupport.java b/platform/openide.filesystems/src/org/openide/filesystems/MIMESupport.java index 347fe2c20fba..08c9b5caa805 100644 --- a/platform/openide.filesystems/src/org/openide/filesystems/MIMESupport.java +++ b/platform/openide.filesystems/src/org/openide/filesystems/MIMESupport.java @@ -254,8 +254,8 @@ private static synchronized List declarativeResolvers() { try { // For now, just assume it has the right DTD. Could check this if desired. declmimes.add(MIMEResolverImpl.forDescriptor(f)); // NOI18N - } catch (IOException ex) { - Exceptions.printStackTrace(ex); + } catch (IOException | IllegalArgumentException ex) { + ERR.log(Level.INFO, "Failed to parse declarative MIMEResolver: " + f, ex); } } }