Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic autocompletion support for pde.bnd files buildpath #817

Merged
merged 1 commit into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -207,13 +207,18 @@ public ViewerComparator createDefaultOutlineComparator() {
}

protected ISortableContentOutlinePage createOutlinePage() {
SourceOutlinePage sourceOutlinePage = new SourceOutlinePage(fEditor, (IEditingModel) getInputContext().getModel(), createOutlineLabelProvider(), createOutlineContentProvider(), createDefaultOutlineComparator(), createOutlineComparator());
fOutlinePage = sourceOutlinePage;
fOutlineSelectionChangedListener = this::updateSelection;
fOutlinePage.addSelectionChangedListener(fOutlineSelectionChangedListener);
getSelectionProvider().addSelectionChangedListener(sourceOutlinePage);
fEditorSelectionChangedListener = new PDESourcePageChangedListener();
fEditorSelectionChangedListener.install(getSelectionProvider());
IBaseModel model = getInputContext().getModel();
if (model instanceof IEditingModel editModel) {
SourceOutlinePage sourceOutlinePage = new SourceOutlinePage(fEditor, editModel,
createOutlineLabelProvider(), createOutlineContentProvider(), createDefaultOutlineComparator(),
createOutlineComparator());
fOutlinePage = sourceOutlinePage;
fOutlineSelectionChangedListener = this::updateSelection;
fOutlinePage.addSelectionChangedListener(fOutlineSelectionChangedListener);
getSelectionProvider().addSelectionChangedListener(sourceOutlinePage);
fEditorSelectionChangedListener = new PDESourcePageChangedListener();
fEditorSelectionChangedListener.install(getSelectionProvider());
}
return fOutlinePage;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*******************************************************************************
* Copyright (c) 2023 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
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.internal.ui.editor.bnd;

import java.util.Arrays;
import java.util.Comparator;

import org.eclipse.core.runtime.Status;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.bnd.BndDocument;

import aQute.bnd.osgi.Constants;
import aQute.bnd.properties.LineType;
import aQute.bnd.properties.PropertiesLineReader;

public class BndAutoCompleteProcessor implements IContentAssistProcessor {

@Override
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
IDocument document = viewer.getDocument();
if (document != null) {
try {
PropertiesLineReader reader = new PropertiesLineReader(new BndDocument(document));
LineType type;
while ((type = reader.next()) != LineType.eof) {
if (type == LineType.entry) {
String key = reader.key();
aQute.bnd.properties.IRegion region = reader.region();
if (matches(region, offset)) {
Prefix prefix = getPrefix(document, offset);
if (Constants.BUILDPATH.equals(key)) {
Value value = getValue(key, reader, document);
BundleDescription[] bundles = PDECore.getDefault().getModelManager().getState()
.getState().getBundles();
String strippedPrefix = prefix.prefix().strip().toLowerCase();
Comparator<BundleDescription> prefixMatchFirst = Comparator.comparingInt(
bd -> bd.getSymbolicName().toLowerCase().startsWith(strippedPrefix) ? 0 : 1);
Comparator<BundleDescription> orderBySymbolicName = Comparator
.comparing(BundleDescription::getSymbolicName, String.CASE_INSENSITIVE_ORDER);
ICompletionProposal[] array = Arrays.stream(bundles)
.filter(bd -> bd.getSymbolicName() != null)
.filter(bd -> bd.getSymbolicName().toLowerCase().contains(strippedPrefix))
.sorted(prefixMatchFirst.thenComparing(orderBySymbolicName)).map(bd -> {
String replacement = buildReplacement(key, value, bd, prefix);
CompletionProposal proposal = new CompletionProposal(replacement,
region.getOffset(), region.getLength(), replacement.length(), null,
bd.getSymbolicName(), null, null);
// proposal.setName(getCompletionProposalAutoActivationCharacters())
return proposal;
}).toArray(ICompletionProposal[]::new);
return array;
} else if (Constants.RUNEE.equals(key)) {
// TODO suggest any known EE from JDT?
} else if (Constants.INCLUDERESOURCE.equals(key)) {
// TODO we might want to suggest resources from
// the current project?
}
}
}
}
} catch (Exception e) {
// can't do anything here then...
PDECore.log(Status.error("Internal error on autocompletion", e)); //$NON-NLS-1$
}

}
return new ICompletionProposal[0];
}

private String buildReplacement(String key, Value value, BundleDescription bundleDescription, Prefix prefix) {
StringBuilder sb = new StringBuilder();
sb.append(key);
sb.append(value.terminatingChar());
String rawValue = value.value();
String prefixString = prefix.prefix();
String substring = rawValue.substring(0, rawValue.length() - prefixString.length());
sb.append(substring);
int l = prefixString.length();
for (int i = 0; i < l; i++) {
char c = prefixString.charAt(i);
if (Character.isWhitespace(c)) {
sb.append(c);
}

}
sb.append(bundleDescription.getSymbolicName());
return sb.toString();
}

private Prefix getPrefix(IDocument document, int offset) {
try {
StringBuilder sb = new StringBuilder();
while (offset > 0) {
char c = document.getChar(offset - 1);
if (c == ':' || c == '=' || c == ' ' || c == ',' || c == '\\') {
return new Prefix(c, sb.toString());
}
sb.insert(0, c);
offset--;
}
} catch (BadLocationException e) {
}
return new Prefix(' ', ""); //$NON-NLS-1$
}

private boolean matches(aQute.bnd.properties.IRegion region, int offset) {
if (offset >= region.getOffset()) {
return offset <= region.getOffset() + region.getLength();
}
return false;
}

private Value getValue(String key, PropertiesLineReader reader, IDocument document) throws BadLocationException {
// due to bug
// https://github.com/bndtools/bnd/issues/5839 we can't
// fetch the value easily... by calling reader.value() ...
aQute.bnd.properties.IRegion region = reader.region();
String string = document.get(region.getOffset(), region.getLength()).substring(key.length());
while (string.length() > 0) {
char c = string.charAt(0);
string = string.substring(1);
if (c == ':' || c == '=') {
return new Value(c, string);
}
}
return new Value(' ', ""); //$NON-NLS-1$
}

@Override
public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
return new IContextInformation[0];
}

@Override
public char[] getCompletionProposalAutoActivationCharacters() {
return new char[0];
}

@Override
public char[] getContextInformationAutoActivationCharacters() {
return new char[0];
}

@Override
public String getErrorMessage() {
return null;
}

@Override
public IContextInformationValidator getContextInformationValidator() {
return null;
}

private static final record Prefix(char terminatingChar, String prefix) {
}

private static final record Value(char terminatingChar, String value) {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.eclipse.ui.IEditorInput;

public class BndInputContext extends InputContext implements IInputContextListener, IModelChangedListener {
public static final String BND_PARTITION = "___bnd_partition"; //$NON-NLS-1$
public static final String CONTEXT_ID = "bnd-context"; //$NON-NLS-1$

public BndInputContext(PDEFormEditor editor, IEditorInput input, boolean primary) {
Expand Down Expand Up @@ -71,7 +72,7 @@ public void doRevert() {

@Override
protected String getPartitionName() {
return "___bnd_partition"; //$NON-NLS-1$
return BND_PARTITION;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*******************************************************************************
* Copyright (c) 2023 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
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.internal.ui.editor.bnd;

import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.pde.internal.ui.PDEPluginImages;
import org.eclipse.pde.internal.ui.editor.GenericSourcePage;
import org.eclipse.pde.internal.ui.editor.PDEFormEditor;

public class BndSourcePage extends GenericSourcePage {

public BndSourcePage(PDEFormEditor editor, String id, String title) {
super(editor, id, title);
setSourceViewerConfiguration(new SourceViewerConfiguration() {
private ContentAssistant fContentAssistant;

@Override
public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
if (isEditable()) {
if (fContentAssistant == null) {
// Initialize in SWT thread before using in background
// thread:
PDEPluginImages.get(null);
fContentAssistant = new ContentAssistant(true);
fContentAssistant.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer));
fContentAssistant.setContentAssistProcessor(new BndAutoCompleteProcessor(),
IDocument.DEFAULT_CONTENT_TYPE);
fContentAssistant.enableAutoInsert(true);
fContentAssistant
.setInformationControlCreator(parent -> new DefaultInformationControl(parent, false));
fContentAssistant.setContextInformationPopupOrientation(IContentAssistant.CONTEXT_INFO_ABOVE);
fContentAssistant.enableAutoActivation(true);
}
return fContentAssistant;
}
return null;
}
});
}

@Override
public ILabelProvider createOutlineLabelProvider() {
return null;
}

@Override
public ITreeContentProvider createOutlineContentProvider() {
return null;
}

@Override
public void updateSelection(Object object) {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
import org.eclipse.pde.internal.ui.editor.PDELauncherFormEditor;
import org.eclipse.pde.internal.ui.editor.PDESourcePage;
import org.eclipse.pde.internal.ui.editor.bnd.BndInputContext;
import org.eclipse.pde.internal.ui.editor.bnd.BndSourcePage;
import org.eclipse.pde.internal.ui.editor.build.BuildInputContext;
import org.eclipse.pde.internal.ui.editor.build.BuildPage;
import org.eclipse.pde.internal.ui.editor.build.BuildSourcePage;
Expand Down Expand Up @@ -662,6 +663,9 @@ protected PDESourcePage createSourcePage(PDEFormEditor editor, String title, Str
return new BuildSourcePage(editor, title, name);
if (contextId.equals(BundleInputContext.CONTEXT_ID))
return new BundleSourcePage(editor, title, name);
if (contextId.equals(BndInputContext.CONTEXT_ID)) {
return new BndSourcePage(editor, contextId, title);
}
return super.createSourcePage(editor, title, name, contextId);
}

Expand Down