Skip to content

Commit 10faeb6

Browse files
committed
Provide auto completion for bnd instructions in maven xml documents
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]>
1 parent 4074fc1 commit 10faeb6

16 files changed

+369
-1
lines changed

RELEASE_NOTES.md

+13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Eclipse m2e - Release notes
22

3+
## 2.6.3
4+
5+
### Auto-Completion support for `bnd-maven-plugin` and `felix-bundle-plugin` with lemminx editor
6+
7+
The bnd-maven and felix-bundle plugin provide a way to use
8+
bnd-instructions to build OSGi bundles. As this is a complex (and extensible) syntax that
9+
can not be expressed as regular maven-mojo configuration lemminx-maven
10+
can not supply any useful completions.
11+
12+
m2e now contains a new lemminx-extension that provides such completions in a
13+
basic way to support people writing such bnd instructions in pom xml configurations.
14+
15+
316
## 2.6.2
417

518
* 📅 Release Date: 04th September 2024
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.4.0" name="org.eclipse.m2e.bnd.ui.BndPluginAdapter">
3+
<property name="adaptableClass" type="String" value="org.eclipse.core.resources.IProject"/>
4+
<property name="adapterNames" type="String" value="aQute.bnd.build.Project"/>
5+
<service>
6+
<provide interface="org.eclipse.core.runtime.IAdapterFactory"/>
7+
</service>
8+
<implementation class="org.eclipse.m2e.bnd.ui.BndPluginAdapter"/>
9+
</scr:component>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<classpath>
3+
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21"/>
4+
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
5+
<classpathentry kind="src" path="src"/>
6+
<classpathentry kind="output" path="bin"/>
7+
</classpath>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<projectDescription>
3+
<name>org.eclipse.m2e.editor.lemminx.bnd</name>
4+
<comment></comment>
5+
<projects>
6+
</projects>
7+
<buildSpec>
8+
<buildCommand>
9+
<name>org.eclipse.jdt.core.javabuilder</name>
10+
<arguments>
11+
</arguments>
12+
</buildCommand>
13+
<buildCommand>
14+
<name>org.eclipse.pde.ManifestBuilder</name>
15+
<arguments>
16+
</arguments>
17+
</buildCommand>
18+
<buildCommand>
19+
<name>org.eclipse.pde.SchemaBuilder</name>
20+
<arguments>
21+
</arguments>
22+
</buildCommand>
23+
</buildSpec>
24+
<natures>
25+
<nature>org.eclipse.pde.PluginNature</nature>
26+
<nature>org.eclipse.jdt.core.javanature</nature>
27+
</natures>
28+
</projectDescription>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
eclipse.preferences.version=1
2+
encoding/<project>=UTF-8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
eclipse.preferences.version=1
2+
org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
3+
org.eclipse.jdt.core.compiler.compliance=21
4+
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
5+
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
6+
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
7+
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
8+
org.eclipse.jdt.core.compiler.release=enabled
9+
org.eclipse.jdt.core.compiler.source=21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Manifest-Version: 1.0
2+
Bundle-ManifestVersion: 2
3+
Bundle-SymbolicName: org.eclipse.m2e.editor.lemminx.bnd;singleton:=true
4+
Bundle-Version: 1.0.0.qualifier
5+
Require-Bundle: org.eclipse.wildwebdeveloper.xml
6+
Bundle-Name: %Bundle-Name
7+
Bundle-Vendor: %Bundle-Vendor
8+
Automatic-Module-Name: org.eclipse.m2e.editor.lemminx.bnd
9+
Bundle-RequiredExecutionEnvironment: JavaSE-21
10+
Import-Package: aQute.bnd.help;version="[2.0.0,3.0.0)",
11+
org.eclipse.core.runtime;version="[3.7.0,4.0.0)",
12+
org.osgi.framework;version="[1.10.0,2.0.0)",
13+
org.osgi.framework.wiring;version="[1.2.0,2.0.0)",
14+
org.osgi.resource;version="[1.0.0,2.0.0)"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.eclipse.m2e.editor.lemminx.bnd.BndLemminxPlugin
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3+
<html xmlns="http://www.w3.org/1999/xhtml">
4+
<head>
5+
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
6+
<title>About</title>
7+
</head>
8+
<body lang="EN-US">
9+
<h2>About This Content</h2>
10+
11+
<p>November 30, 2017</p>
12+
<h3>License</h3>
13+
14+
<p>
15+
The Eclipse Foundation makes available all content in this plug-in
16+
(&quot;Content&quot;). Unless otherwise indicated below, the Content
17+
is provided to you under the terms and conditions of the Eclipse
18+
Public License Version 2.0 (&quot;EPL&quot;). A copy of the EPL is
19+
available at <a href="http://www.eclipse.org/legal/epl-2.0">http://www.eclipse.org/legal/epl-2.0</a>.
20+
For purposes of the EPL, &quot;Program&quot; will mean the Content.
21+
</p>
22+
23+
<p>
24+
If you did not receive this Content directly from the Eclipse
25+
Foundation, the Content is being redistributed by another party
26+
(&quot;Redistributor&quot;) and different terms and conditions may
27+
apply to your use of any object code in the Content. Check the
28+
Redistributor's license that was provided with the Content. If no such
29+
license exists, contact the Redistributor. Unless otherwise indicated
30+
below, the terms and conditions of the EPL still apply to any source
31+
code in the Content and such source code may be obtained at <a
32+
href="http://www.eclipse.org/">http://www.eclipse.org</a>.
33+
</p>
34+
35+
</body>
36+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
source.. = src/
2+
output.. = bin/
3+
bin.includes = META-INF/,\
4+
.,\
5+
plugin.xml,\
6+
about.html,\
7+
plugin.properties
8+
jars.extra.classpath = platform:/plugin/org.eclipse.wildwebdeveloper.xml/language-servers/server/org.eclipse.lemminx-uber.jar
9+
src.includes = about.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#
2+
# Copyright (c) 2007, 2021 Sonatype, Inc.
3+
# All rights reserved. This program and the accompanying materials
4+
# are made available under the terms of the Eclipse Public License 2.0
5+
# which accompanies this distribution, and is available at
6+
# https://www.eclipse.org/legal/epl-2.0/
7+
#
8+
# SPDX-License-Identifier: EPL-2.0#
9+
# Contributors:
10+
# Sonatype, Inc. - initial API and implementation
11+
# Rob Newton - added warning preferences page for disabling warnings
12+
13+
Bundle-Vendor = Eclipse.org - m2e
14+
Bundle-Name = M2E Lemminx Bnd Extension
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<?eclipse version="3.4"?>
3+
<plugin>
4+
<extension
5+
point="org.eclipse.wildwebdeveloper.xml.lemminxExtension">
6+
<classpathExtensionProvider
7+
provider="org.eclipse.m2e.editor.lemminx.bnd.BndClasspathExtensionProvider">
8+
</classpathExtensionProvider>
9+
</extension>
10+
11+
</plugin>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Christoph Läubrich and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
package org.eclipse.m2e.editor.lemminx.bnd;
12+
13+
import java.io.File;
14+
import java.util.ArrayList;
15+
import java.util.LinkedHashSet;
16+
import java.util.List;
17+
import java.util.Set;
18+
19+
import org.eclipse.core.runtime.FileLocator;
20+
import org.osgi.framework.Bundle;
21+
import org.osgi.framework.FrameworkUtil;
22+
import org.osgi.framework.wiring.BundleWire;
23+
import org.osgi.framework.wiring.BundleWiring;
24+
25+
import aQute.bnd.help.Syntax;
26+
27+
/**
28+
* register additional jars and the extension bundle
29+
*/
30+
@SuppressWarnings("restriction")
31+
public class BndClasspathExtensionProvider
32+
implements org.eclipse.wildwebdeveloper.xml.LemminxClasspathExtensionProvider {
33+
34+
@Override
35+
public List<File> get() {
36+
List<File> list = new ArrayList<>();
37+
Set<Bundle> bundleRequirements = new LinkedHashSet<>();
38+
bundleRequirements.add((FrameworkUtil.getBundle(getClass())));
39+
collectBundles(FrameworkUtil.getBundle(Syntax.class), bundleRequirements);
40+
for (Bundle bundle : bundleRequirements) {
41+
FileLocator.getBundleFileLocation(bundle).ifPresent(file -> {
42+
if (file.isDirectory()) {
43+
// For bundles from the workspace launch include the bin folder for classes
44+
File outputFolder = new File(file, "bin");
45+
if (outputFolder.exists()) {
46+
list.add(outputFolder);
47+
}
48+
}
49+
list.add(file);
50+
});
51+
}
52+
return list;
53+
}
54+
55+
private void collectBundles(Bundle bundle, Set<Bundle> bundleRequirements) {
56+
if (isValid(bundle) && bundleRequirements.add(bundle)) {
57+
BundleWiring wiring = bundle.adapt(BundleWiring.class);
58+
List<BundleWire> wires = wiring.getRequiredWires("osgi.wiring.package");
59+
for (BundleWire bundleWire : wires) {
60+
collectBundles(bundleWire.getProvider().getBundle(), bundleRequirements);
61+
}
62+
}
63+
64+
}
65+
66+
private boolean isValid(Bundle bundle) {
67+
if (bundle == null) {
68+
return false;
69+
}
70+
String bsn = bundle.getSymbolicName();
71+
if ("slf4j.api".equals(bsn)) {
72+
// slf4j is already provided
73+
return false;
74+
}
75+
return true;
76+
}
77+
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Christoph Läubrich and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
package org.eclipse.m2e.editor.lemminx.bnd;
12+
13+
import java.util.function.Function;
14+
import java.util.logging.Level;
15+
import java.util.logging.Logger;
16+
17+
import org.eclipse.lemminx.dom.DOMDocument;
18+
import org.eclipse.lemminx.dom.DOMNode;
19+
import org.eclipse.lemminx.services.extensions.IXMLExtension;
20+
import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
21+
import org.eclipse.lemminx.services.extensions.completion.ICompletionParticipant;
22+
import org.eclipse.lemminx.services.extensions.completion.ICompletionRequest;
23+
import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse;
24+
import org.eclipse.lemminx.services.extensions.save.ISaveContext;
25+
import org.eclipse.lsp4j.CompletionItem;
26+
import org.eclipse.lsp4j.CompletionItemKind;
27+
import org.eclipse.lsp4j.InitializeParams;
28+
import org.eclipse.lsp4j.InsertTextFormat;
29+
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
30+
31+
import aQute.bnd.help.Syntax;
32+
33+
/**
34+
* Extension to provide bnd instruction autocompletion to maven
35+
*/
36+
public class BndLemminxPlugin implements IXMLExtension {
37+
38+
@Override
39+
public void start(InitializeParams params, XMLExtensionsRegistry registry) {
40+
Logger logger = Logger.getLogger("bnd");
41+
logger.log(Level.INFO, "Loading bnd-lemminx extension");
42+
registry.registerCompletionParticipant(new ICompletionParticipant() {
43+
44+
@Override
45+
public void onAttributeName(boolean generateValue, ICompletionRequest completionRequest,
46+
ICompletionResponse response, CancelChecker checker) throws Exception {
47+
}
48+
49+
@Override
50+
public void onAttributeValue(String valuePrefix, ICompletionRequest completionRequest,
51+
ICompletionResponse response, CancelChecker checker) throws Exception {
52+
}
53+
54+
@Override
55+
public void onDTDSystemId(String valuePrefix, ICompletionRequest completionRequest,
56+
ICompletionResponse response, CancelChecker checker) throws Exception {
57+
}
58+
59+
@Override
60+
public void onTagOpen(ICompletionRequest completionRequest, ICompletionResponse response,
61+
CancelChecker checker) throws Exception {
62+
}
63+
64+
@Override
65+
public void onXMLContent(ICompletionRequest completionRequest, ICompletionResponse response,
66+
CancelChecker checker) throws Exception {
67+
try {
68+
DOMDocument xmlDocument = completionRequest.getXMLDocument();
69+
DOMNode node = xmlDocument.findNodeBefore(completionRequest.getOffset());
70+
logger.log(Level.INFO, "onXMLContent: " + node);
71+
if (isBndInstructionNode(node)) {
72+
addCompletion(response, syntax -> syntax.getHeader() + ": ");
73+
} else if (isFelixInstructionNode(node)) {
74+
addCompletion(response, syntax -> {
75+
String header = syntax.getHeader();
76+
if (header.startsWith("-")) {
77+
header = "_" + header.substring(1);
78+
}
79+
return String.format("<%s>${0}</%s>", header, header);
80+
});
81+
}
82+
} catch (Exception e) {
83+
logger.log(Level.WARNING, "err=" + e);
84+
}
85+
}
86+
87+
private void addCompletion(ICompletionResponse response, Function<Syntax, String> insert) {
88+
Syntax.HELP.values().stream().forEach(syntax -> {
89+
CompletionItem item = new CompletionItem();
90+
item.setLabel(syntax.getHeader());
91+
item.setDocumentation(syntax.getLead());
92+
item.setDetail(syntax.getExample());
93+
item.setInsertText(insert.apply(syntax));
94+
item.setKind(CompletionItemKind.Property);
95+
item.setInsertTextFormat(InsertTextFormat.Snippet);
96+
response.addCompletionItem(item);
97+
});
98+
}
99+
});
100+
}
101+
102+
private static boolean isBndInstructionNode(DOMNode node) {
103+
if (node != null) {
104+
if (node.getNodeName().equals("bnd")) {
105+
return true;
106+
}
107+
return isBndInstructionNode(node.getParentNode());
108+
}
109+
return false;
110+
}
111+
112+
private static boolean isFelixInstructionNode(DOMNode node) {
113+
if (node != null) {
114+
if (node.getNodeName().equals("instructions")) {
115+
return true;
116+
}
117+
return isFelixInstructionNode(node.getParentNode());
118+
}
119+
return false;
120+
}
121+
122+
@Override
123+
public void stop(XMLExtensionsRegistry registry) {
124+
// nothing special to do...
125+
}
126+
127+
@Override
128+
public void doSave(ISaveContext context) {
129+
IXMLExtension.super.doSave(context);
130+
}
131+
132+
}

0 commit comments

Comments
 (0)