From 47ff9ce4fae912e726dd37293bd05c179606bccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Fri, 15 Nov 2024 17:14:09 +0100 Subject: [PATCH] Provide auto completion for bnd instructions in maven xml documents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bnd-maven and felix-bundle plugin provide a way to use bnd-instructions to build OSGi bundles. As this is a complex syntax that can not be expressed as regular maven-mojo configuration lemminx-maven can not supply any useful completions. This adds a new lemminx-extension that provides such completions in a very basic way to support people writing such custom configuration. Signed-off-by: Christoph Läubrich --- RELEASE_NOTES.md | 13 ++ ...rg.eclipse.m2e.bnd.ui.BndPluginAdapter.xml | 9 ++ org.eclipse.m2e.editor.lemminx.bnd/.classpath | 7 + org.eclipse.m2e.editor.lemminx.bnd/.project | 28 ++++ .../org.eclipse.core.resources.prefs | 2 + .../.settings/org.eclipse.jdt.core.prefs | 9 ++ .../META-INF/MANIFEST.MF | 14 ++ ....lemminx.services.extensions.IXMLExtension | 1 + org.eclipse.m2e.editor.lemminx.bnd/about.html | 36 +++++ .../build.properties | 9 ++ .../plugin.properties | 14 ++ org.eclipse.m2e.editor.lemminx.bnd/plugin.xml | 11 ++ .../bnd/BndClasspathExtensionProvider.java | 78 +++++++++++ .../editor/lemminx/bnd/BndLemminxPlugin.java | 132 ++++++++++++++++++ org.eclipse.m2e.lemminx.feature/feature.xml | 4 + pom.xml | 1 + 16 files changed, 368 insertions(+) create mode 100644 org.eclipse.m2e.bnd.ui/OSGI-INF/org.eclipse.m2e.bnd.ui.BndPluginAdapter.xml create mode 100644 org.eclipse.m2e.editor.lemminx.bnd/.classpath create mode 100644 org.eclipse.m2e.editor.lemminx.bnd/.project create mode 100644 org.eclipse.m2e.editor.lemminx.bnd/.settings/org.eclipse.core.resources.prefs create mode 100644 org.eclipse.m2e.editor.lemminx.bnd/.settings/org.eclipse.jdt.core.prefs create mode 100644 org.eclipse.m2e.editor.lemminx.bnd/META-INF/MANIFEST.MF create mode 100644 org.eclipse.m2e.editor.lemminx.bnd/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension create mode 100644 org.eclipse.m2e.editor.lemminx.bnd/about.html create mode 100644 org.eclipse.m2e.editor.lemminx.bnd/build.properties create mode 100644 org.eclipse.m2e.editor.lemminx.bnd/plugin.properties create mode 100644 org.eclipse.m2e.editor.lemminx.bnd/plugin.xml create mode 100644 org.eclipse.m2e.editor.lemminx.bnd/src/org/eclipse/m2e/editor/lemminx/bnd/BndClasspathExtensionProvider.java create mode 100644 org.eclipse.m2e.editor.lemminx.bnd/src/org/eclipse/m2e/editor/lemminx/bnd/BndLemminxPlugin.java diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 58c5d2e24e..306da2f92d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,18 @@ # Eclipse m2e - Release notes +## 2.6.3 + +### Auto-Completion support for `bnd-maven-plugin` and `felix-bundle-plugin` with lemminx editor + +The bnd-maven and felix-bundle plugin provide a way to use +bnd-instructions to build OSGi bundles. As this is a complex (and extensible) syntax that +can not be expressed as regular maven-mojo configuration lemminx-maven +can not supply any useful completions. + +m2e now contains a new lemminx-extension that provides such completions in a +basic way to support people writing such bnd instructions in pom xml configurations. + + ## 2.6.2 * 📅 Release Date: 04th September 2024 diff --git a/org.eclipse.m2e.bnd.ui/OSGI-INF/org.eclipse.m2e.bnd.ui.BndPluginAdapter.xml b/org.eclipse.m2e.bnd.ui/OSGI-INF/org.eclipse.m2e.bnd.ui.BndPluginAdapter.xml new file mode 100644 index 0000000000..e1c0f82f2d --- /dev/null +++ b/org.eclipse.m2e.bnd.ui/OSGI-INF/org.eclipse.m2e.bnd.ui.BndPluginAdapter.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/org.eclipse.m2e.editor.lemminx.bnd/.classpath b/org.eclipse.m2e.editor.lemminx.bnd/.classpath new file mode 100644 index 0000000000..375961e4d6 --- /dev/null +++ b/org.eclipse.m2e.editor.lemminx.bnd/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.eclipse.m2e.editor.lemminx.bnd/.project b/org.eclipse.m2e.editor.lemminx.bnd/.project new file mode 100644 index 0000000000..d09c73d213 --- /dev/null +++ b/org.eclipse.m2e.editor.lemminx.bnd/.project @@ -0,0 +1,28 @@ + + + org.eclipse.m2e.editor.lemminx.bnd + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.eclipse.m2e.editor.lemminx.bnd/.settings/org.eclipse.core.resources.prefs b/org.eclipse.m2e.editor.lemminx.bnd/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000000..99f26c0203 --- /dev/null +++ b/org.eclipse.m2e.editor.lemminx.bnd/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/org.eclipse.m2e.editor.lemminx.bnd/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.m2e.editor.lemminx.bnd/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..23fa13b170 --- /dev/null +++ b/org.eclipse.m2e.editor.lemminx.bnd/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 +org.eclipse.jdt.core.compiler.compliance=21 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=21 diff --git a/org.eclipse.m2e.editor.lemminx.bnd/META-INF/MANIFEST.MF b/org.eclipse.m2e.editor.lemminx.bnd/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..f70da9b4e0 --- /dev/null +++ b/org.eclipse.m2e.editor.lemminx.bnd/META-INF/MANIFEST.MF @@ -0,0 +1,14 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-SymbolicName: org.eclipse.m2e.editor.lemminx.bnd;singleton:=true +Bundle-Version: 1.0.0.qualifier +Require-Bundle: org.eclipse.wildwebdeveloper.xml +Bundle-Name: %Bundle-Name +Bundle-Vendor: %Bundle-Vendor +Automatic-Module-Name: org.eclipse.m2e.editor.lemminx.bnd +Bundle-RequiredExecutionEnvironment: JavaSE-21 +Import-Package: aQute.bnd.help;version="[2.0.0,3.0.0)", + org.eclipse.core.runtime;version="[3.7.0,4.0.0)", + org.osgi.framework;version="[1.10.0,2.0.0)", + org.osgi.framework.wiring;version="[1.2.0,2.0.0)", + org.osgi.resource;version="[1.0.0,2.0.0)" diff --git a/org.eclipse.m2e.editor.lemminx.bnd/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension b/org.eclipse.m2e.editor.lemminx.bnd/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension new file mode 100644 index 0000000000..cac61af4e5 --- /dev/null +++ b/org.eclipse.m2e.editor.lemminx.bnd/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension @@ -0,0 +1 @@ +org.eclipse.m2e.editor.lemminx.bnd.BndLemminxPlugin \ No newline at end of file diff --git a/org.eclipse.m2e.editor.lemminx.bnd/about.html b/org.eclipse.m2e.editor.lemminx.bnd/about.html new file mode 100644 index 0000000000..8eee37d470 --- /dev/null +++ b/org.eclipse.m2e.editor.lemminx.bnd/about.html @@ -0,0 +1,36 @@ + + + + +About + + +

About This Content

+ +

November 30, 2017

+

License

+ +

+ The Eclipse Foundation makes available all content in this plug-in + ("Content"). Unless otherwise indicated below, the Content + is provided to you under the terms and conditions of the Eclipse + Public License Version 2.0 ("EPL"). A copy of the EPL is + available at http://www.eclipse.org/legal/epl-2.0. + For purposes of the EPL, "Program" will mean the Content. +

+ +

+ If you did not receive this Content directly from the Eclipse + Foundation, the Content is being redistributed by another party + ("Redistributor") and different terms and conditions may + apply to your use of any object code in the Content. Check the + Redistributor's license that was provided with the Content. If no such + license exists, contact the Redistributor. Unless otherwise indicated + below, the terms and conditions of the EPL still apply to any source + code in the Content and such source code may be obtained at http://www.eclipse.org. +

+ + + diff --git a/org.eclipse.m2e.editor.lemminx.bnd/build.properties b/org.eclipse.m2e.editor.lemminx.bnd/build.properties new file mode 100644 index 0000000000..2c7f5c7689 --- /dev/null +++ b/org.eclipse.m2e.editor.lemminx.bnd/build.properties @@ -0,0 +1,9 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.xml,\ + about.html,\ + plugin.properties +jars.extra.classpath = platform:/plugin/org.eclipse.wildwebdeveloper.xml/language-servers/server/org.eclipse.lemminx-uber.jar +src.includes = about.html diff --git a/org.eclipse.m2e.editor.lemminx.bnd/plugin.properties b/org.eclipse.m2e.editor.lemminx.bnd/plugin.properties new file mode 100644 index 0000000000..28c974808b --- /dev/null +++ b/org.eclipse.m2e.editor.lemminx.bnd/plugin.properties @@ -0,0 +1,14 @@ +# +# Copyright (c) 2007, 2021 Sonatype, Inc. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License 2.0 +# which accompanies this distribution, and is available at +# https://www.eclipse.org/legal/epl-2.0/ +# +# SPDX-License-Identifier: EPL-2.0# +# Contributors: +# Sonatype, Inc. - initial API and implementation +# Rob Newton - added warning preferences page for disabling warnings + +Bundle-Vendor = Eclipse.org - m2e +Bundle-Name = M2E Lemminx Bnd Extension diff --git a/org.eclipse.m2e.editor.lemminx.bnd/plugin.xml b/org.eclipse.m2e.editor.lemminx.bnd/plugin.xml new file mode 100644 index 0000000000..dd8bfc85f2 --- /dev/null +++ b/org.eclipse.m2e.editor.lemminx.bnd/plugin.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/org.eclipse.m2e.editor.lemminx.bnd/src/org/eclipse/m2e/editor/lemminx/bnd/BndClasspathExtensionProvider.java b/org.eclipse.m2e.editor.lemminx.bnd/src/org/eclipse/m2e/editor/lemminx/bnd/BndClasspathExtensionProvider.java new file mode 100644 index 0000000000..05e6f85118 --- /dev/null +++ b/org.eclipse.m2e.editor.lemminx.bnd/src/org/eclipse/m2e/editor/lemminx/bnd/BndClasspathExtensionProvider.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2024 Christoph Läubrich and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.m2e.editor.lemminx.bnd; + +import java.io.File; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.FileLocator; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.wiring.BundleWire; +import org.osgi.framework.wiring.BundleWiring; + +import aQute.bnd.help.Syntax; + +/** + * register additional jars and the extension bundle + */ +@SuppressWarnings("restriction") +public class BndClasspathExtensionProvider + implements org.eclipse.wildwebdeveloper.xml.LemminxClasspathExtensionProvider { + + @Override + public List get() { + List list = new ArrayList<>(); + Set bundleRequirements = new LinkedHashSet<>(); + bundleRequirements.add((FrameworkUtil.getBundle(getClass()))); + collectBundles(FrameworkUtil.getBundle(Syntax.class), bundleRequirements); + for (Bundle bundle : bundleRequirements) { + FileLocator.getBundleFileLocation(bundle).ifPresent(file -> { + if (file.isDirectory()) { + // For bundles from the workspace launch include the bin folder for classes + File outputFolder = new File(file, "bin"); + if (outputFolder.exists()) { + list.add(outputFolder); + } + } + list.add(file); + }); + } + return list; + } + + private void collectBundles(Bundle bundle, Set bundleRequirements) { + if (isValid(bundle) && bundleRequirements.add(bundle)) { + BundleWiring wiring = bundle.adapt(BundleWiring.class); + List wires = wiring.getRequiredWires("osgi.wiring.package"); + for (BundleWire bundleWire : wires) { + collectBundles(bundleWire.getProvider().getBundle(), bundleRequirements); + } + } + + } + + private boolean isValid(Bundle bundle) { + if (bundle == null) { + return false; + } + String bsn = bundle.getSymbolicName(); + if ("slf4j.api".equals(bsn)) { + // slf4j is already provided + return false; + } + return true; + } + +} diff --git a/org.eclipse.m2e.editor.lemminx.bnd/src/org/eclipse/m2e/editor/lemminx/bnd/BndLemminxPlugin.java b/org.eclipse.m2e.editor.lemminx.bnd/src/org/eclipse/m2e/editor/lemminx/bnd/BndLemminxPlugin.java new file mode 100644 index 0000000000..85fe44ddf2 --- /dev/null +++ b/org.eclipse.m2e.editor.lemminx.bnd/src/org/eclipse/m2e/editor/lemminx/bnd/BndLemminxPlugin.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2024 Christoph Läubrich and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.m2e.editor.lemminx.bnd; + +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMNode; +import org.eclipse.lemminx.services.extensions.IXMLExtension; +import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; +import org.eclipse.lemminx.services.extensions.completion.ICompletionParticipant; +import org.eclipse.lemminx.services.extensions.completion.ICompletionRequest; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; +import org.eclipse.lemminx.services.extensions.save.ISaveContext; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionItemKind; +import org.eclipse.lsp4j.InitializeParams; +import org.eclipse.lsp4j.InsertTextFormat; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +import aQute.bnd.help.Syntax; + +/** + * Extension to provide bnd instruction autocompletion to maven + */ +public class BndLemminxPlugin implements IXMLExtension { + + @Override + public void start(InitializeParams params, XMLExtensionsRegistry registry) { + Logger logger = Logger.getLogger("bnd"); + logger.log(Level.INFO, "Loading bnd-lemminx extension"); + registry.registerCompletionParticipant(new ICompletionParticipant() { + + @Override + public void onAttributeName(boolean generateValue, ICompletionRequest completionRequest, + ICompletionResponse response, CancelChecker checker) throws Exception { + } + + @Override + public void onAttributeValue(String valuePrefix, ICompletionRequest completionRequest, + ICompletionResponse response, CancelChecker checker) throws Exception { + } + + @Override + public void onDTDSystemId(String valuePrefix, ICompletionRequest completionRequest, + ICompletionResponse response, CancelChecker checker) throws Exception { + } + + @Override + public void onTagOpen(ICompletionRequest completionRequest, ICompletionResponse response, + CancelChecker checker) throws Exception { + } + + @Override + public void onXMLContent(ICompletionRequest completionRequest, ICompletionResponse response, + CancelChecker checker) throws Exception { + try { + DOMDocument xmlDocument = completionRequest.getXMLDocument(); + DOMNode node = xmlDocument.findNodeBefore(completionRequest.getOffset()); + logger.log(Level.INFO, "onXMLContent: " + node); + if (isBndInstructionNode(node)) { + addCompletion(response, syntax -> syntax.getHeader() + ": "); + } else if (isFelixInstructionNode(node)) { + addCompletion(response, syntax -> { + String header = syntax.getHeader(); + if (header.startsWith("-")) { + header = "_" + header.substring(1); + } + return String.format("<%s>${0}", header, header); + }); + } + } catch (Exception e) { + logger.log(Level.WARNING, "err=" + e); + } + } + + private void addCompletion(ICompletionResponse response, Function insert) { + Syntax.HELP.values().stream().forEach(syntax -> { + CompletionItem item = new CompletionItem(); + item.setLabel(syntax.getHeader()); + item.setDocumentation(syntax.getLead()); + item.setDetail(syntax.getExample()); + item.setInsertText(insert.apply(syntax)); + item.setKind(CompletionItemKind.Property); + item.setInsertTextFormat(InsertTextFormat.Snippet); + response.addCompletionItem(item); + }); + } + }); + } + + private static boolean isBndInstructionNode(DOMNode node) { + if (node != null) { + if (node.getNodeName().equals("bnd")) { + return true; + } + return isBndInstructionNode(node.getParentNode()); + } + return false; + } + + private static boolean isFelixInstructionNode(DOMNode node) { + if (node != null) { + if (node.getNodeName().equals("instructions")) { + return true; + } + return isFelixInstructionNode(node.getParentNode()); + } + return false; + } + + @Override + public void stop(XMLExtensionsRegistry registry) { + // nothing special to do... + } + + @Override + public void doSave(ISaveContext context) { + IXMLExtension.super.doSave(context); + } + +} \ No newline at end of file diff --git a/org.eclipse.m2e.lemminx.feature/feature.xml b/org.eclipse.m2e.lemminx.feature/feature.xml index e938a76951..a97bdc0901 100644 --- a/org.eclipse.m2e.lemminx.feature/feature.xml +++ b/org.eclipse.m2e.lemminx.feature/feature.xml @@ -20,4 +20,8 @@ id="org.eclipse.m2e.editor.lemminx" version="0.0.0"/> + + diff --git a/pom.xml b/pom.xml index a4930719f5..72678b3b88 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,7 @@ org.eclipse.m2e.sourcelookup org.eclipse.m2e.sourcelookup.ui org.eclipse.m2e.editor.lemminx + org.eclipse.m2e.editor.lemminx.bnd org.eclipse.m2e.pde.target org.eclipse.m2e.pde.ui org.eclipse.m2e.pde.connector