Skip to content

Commit

Permalink
Provide auto completion for bnd instructions in maven xml documents
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
laeubi committed Nov 19, 2024
1 parent 4074fc1 commit 4faf726
Show file tree
Hide file tree
Showing 13 changed files with 314 additions and 0 deletions.
13 changes: 13 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.4.0" name="org.eclipse.m2e.bnd.ui.BndPluginAdapter">
<property name="adaptableClass" type="String" value="org.eclipse.core.resources.IProject"/>
<property name="adapterNames" type="String" value="aQute.bnd.build.Project"/>
<service>
<provide interface="org.eclipse.core.runtime.IAdapterFactory"/>
</service>
<implementation class="org.eclipse.m2e.bnd.ui.BndPluginAdapter"/>
</scr:component>
7 changes: 7 additions & 0 deletions org.eclipse.m2e.editor.lemminx.bnd/.classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
28 changes: 28 additions & 0 deletions org.eclipse.m2e.editor.lemminx.bnd/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.eclipse.m2e.editor.lemminx.bnd</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions org.eclipse.m2e.editor.lemminx.bnd/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Lemminx Bnd Extension
Bundle-SymbolicName: org.eclipse.m2e.editor.lemminx.bnd;singleton:=true
Bundle-Version: 1.0.0.qualifier
Require-Bundle: org.eclipse.wildwebdeveloper.xml
Bundle-Vendor: Eclipse
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)"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.eclipse.m2e.editor.lemminx.bnd.BndLemminxPlugin
6 changes: 6 additions & 0 deletions org.eclipse.m2e.editor.lemminx.bnd/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.,\
plugin.xml
jars.extra.classpath = platform:/plugin/org.eclipse.wildwebdeveloper.xml/language-servers/server/org.eclipse.lemminx-uber.jar
11 changes: 11 additions & 0 deletions org.eclipse.m2e.editor.lemminx.bnd/plugin.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.wildwebdeveloper.xml.lemminxExtension">
<classpathExtensionProvider
provider="org.eclipse.m2e.editor.lemminx.bnd.BndClasspathExtensionProvider">
</classpathExtensionProvider>
</extension>

</plugin>
Original file line number Diff line number Diff line change
@@ -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<File> get() {
List<File> list = new ArrayList<>();
Set<Bundle> 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<Bundle> bundleRequirements) {
if (isValid(bundle) && bundleRequirements.add(bundle)) {
BundleWiring wiring = bundle.adapt(BundleWiring.class);
List<BundleWire> 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;
}

}
Original file line number Diff line number Diff line change
@@ -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}</%s>", header, header);
});
}
} catch (Exception e) {
logger.log(Level.WARNING, "err=" + e);
}
}

private void addCompletion(ICompletionResponse response, Function<Syntax, String> 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);
}

}
4 changes: 4 additions & 0 deletions org.eclipse.m2e.lemminx.feature/feature.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@
id="org.eclipse.m2e.editor.lemminx"
version="0.0.0"/>

<plugin
id="org.eclipse.m2e.editor.lemminx.bnd"
version="0.0.0"/>

</feature>

0 comments on commit 4faf726

Please sign in to comment.