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 <laeubi@laeubi-soft.de>
  • Loading branch information
laeubi committed Nov 19, 2024
1 parent 4074fc1 commit 10faeb6
Show file tree
Hide file tree
Showing 16 changed files with 369 additions and 1 deletion.
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-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)"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.eclipse.m2e.editor.lemminx.bnd.BndLemminxPlugin
36 changes: 36 additions & 0 deletions org.eclipse.m2e.editor.lemminx.bnd/about.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>About</title>
</head>
<body lang="EN-US">
<h2>About This Content</h2>

<p>November 30, 2017</p>
<h3>License</h3>

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

<p>
If you did not receive this Content directly from the Eclipse
Foundation, the Content is being redistributed by another party
(&quot;Redistributor&quot;) 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 <a
href="http://www.eclipse.org/">http://www.eclipse.org</a>.
</p>

</body>
</html>
9 changes: 9 additions & 0 deletions org.eclipse.m2e.editor.lemminx.bnd/build.properties
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions org.eclipse.m2e.editor.lemminx.bnd/plugin.properties
Original file line number Diff line number Diff line change
@@ -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
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);
}

}
Loading

0 comments on commit 10faeb6

Please sign in to comment.