diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ICoreConstants.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ICoreConstants.java index d37f982c0ab..585d907d677 100644 --- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ICoreConstants.java +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ICoreConstants.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2021 IBM Corporation and others. + * Copyright (c) 2000, 2021, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -312,6 +312,9 @@ public interface ICoreConstants { String OSGI_INF_FOLDER_NAME = "OSGI-INF/"; //$NON-NLS-1$ String FEATURE_FOLDER_NAME = "features"; //$NON-NLS-1$ + String P2_INF_FILENAME = "p2.inf"; //$NON-NLS-1$ + String P2_INF_BUNDLE_DESCRIPTOR = "META-INF/p2.inf"; //$NON-NLS-1$ + // Common paths IPath MANIFEST_PATH = IPath.fromOSString(BUNDLE_FILENAME_DESCRIPTOR); IPath PLUGIN_PATH = IPath.fromOSString(PLUGIN_FILENAME_DESCRIPTOR); @@ -319,6 +322,8 @@ public interface ICoreConstants { IPath FEATURE_PATH = IPath.fromOSString(FEATURE_FILENAME_DESCRIPTOR); IPath BUILD_PROPERTIES_PATH = IPath.fromOSString(BUILD_FILENAME_DESCRIPTOR); IPath OSGI_INF_PATH = IPath.fromOSString(OSGI_INF_FOLDER_NAME); + IPath P2_INF_BUNDLE_PATH = IPath.fromOSString(P2_INF_BUNDLE_DESCRIPTOR); + IPath P2_INF_FEATURE_PATH = IPath.fromOSString(P2_INF_FILENAME); // Extension point identifiers String EXTENSION_POINT_SOURCE = PDECore.PLUGIN_ID + ".source"; //$NON-NLS-1$ diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/PDEProject.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/PDEProject.java index 851d95dadcc..6e729ef19d6 100644 --- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/PDEProject.java +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/PDEProject.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010, 2024 IBM Corporation and others. + * Copyright (c) 2010, 2024, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -260,6 +260,30 @@ public static IFolder getMetaInf(IProject project) { return getBundleRelativeFolder(project, IPath.fromOSString(ICoreConstants.MANIFEST_FOLDER_NAME)); } + /** + * Returns the resource in the specified project corresponding to its + * META-INF/p2.inf file (for bundle/plugin projects). + * + * @param project project + * @return META-INF/p2.inf file that may or may not exist + */ + public static IFile getBundleP2Inf(IProject project) { + return getBundleRelativeFile(project, ICoreConstants.P2_INF_BUNDLE_PATH); + } + + /** + * Returns the resource in the specified project corresponding to its + * p2.inf file (for feature projects). Feature projects have + * the p2.inf file in the project root, not in META-INF. + * + * @param project + * project + * @return p2.inf file that may or may not exist + */ + public static IFile getFeatureP2Inf(IProject project) { + return getBundleRelativeFile(project, ICoreConstants.P2_INF_FEATURE_PATH); + } + /** * Returns a file relative to the bundle root of the specified project. * diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureEditor.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureEditor.java index ec22ea53b06..80c322e28d2 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureEditor.java +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureEditor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2021 IBM Corporation and others. + * Copyright (c) 2000, 2021, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -52,6 +52,8 @@ import org.eclipse.pde.internal.ui.editor.build.BuildSourcePage; import org.eclipse.pde.internal.ui.editor.context.InputContext; import org.eclipse.pde.internal.ui.editor.context.InputContextManager; +import org.eclipse.pde.internal.ui.editor.p2inf.P2InfInputContext; +import org.eclipse.pde.internal.ui.editor.p2inf.P2InfSourcePage; import org.eclipse.swt.SWTError; import org.eclipse.swt.dnd.RTFTransfer; import org.eclipse.swt.dnd.TextTransfer; @@ -172,8 +174,14 @@ protected void createResourceContexts(InputContextManager manager, IFileEditorIn FileEditorInput in = new FileEditorInput(buildFile); manager.putContext(in, new BuildInputContext(this, in, file == buildFile)); } + IFile p2InfFile = PDEProject.getFeatureP2Inf(project); + if (p2InfFile != null && p2InfFile.exists()) { + FileEditorInput in = new FileEditorInput(p2InfFile); + manager.putContext(in, new P2InfInputContext(this, in, false)); + } manager.monitorFile(featureFile); manager.monitorFile(buildFile); + manager.monitorFile(p2InfFile); } @Override @@ -200,6 +208,11 @@ public void monitoredFileAdded(IFile file) { IEditorInput in = new FileEditorInput(file); fInputContextManager.putContext(in, new BuildInputContext(this, in, false)); } + } else if (name.equalsIgnoreCase(ICoreConstants.P2_INF_FILENAME)) { + if (!fInputContextManager.hasContext(P2InfInputContext.CONTEXT_ID)) { + IEditorInput in = new FileEditorInput(file); + fInputContextManager.putContext(in, new P2InfInputContext(this, in, false)); + } } } @@ -254,6 +267,12 @@ protected void createSystemFileContexts(InputContextManager manager, FileStoreEd IEditorInput in = new FileStoreEditorInput(store); manager.putContext(in, new BuildInputContext(this, in, file == buildFile)); } + File p2InfFile = new File(file.getParentFile(), ICoreConstants.P2_INF_FILENAME); + if (p2InfFile.exists()) { + IFileStore store = EFS.getStore(p2InfFile.toURI()); + IEditorInput in = new FileStoreEditorInput(store); + manager.putContext(in, new P2InfInputContext(this, in, false)); + } } catch (CoreException e) { PDEPlugin.logException(e); } @@ -298,6 +317,7 @@ protected void addEditorPages() { } addSourcePage(FeatureInputContext.CONTEXT_ID); addSourcePage(BuildInputContext.CONTEXT_ID); + addSourcePage(P2InfInputContext.CONTEXT_ID); } @Override @@ -323,6 +343,9 @@ protected IEditorPart createSourcePage(PDEFormEditor editor, String title, Strin if (contextId.equals(BuildInputContext.CONTEXT_ID)) { return new BuildSourcePage(editor, title, name); } + if (contextId.equals(P2InfInputContext.CONTEXT_ID)) { + return new P2InfSourcePage(editor, contextId, title); + } return super.createSourcePage(editor, title, name, contextId); } @@ -426,9 +449,18 @@ protected boolean isPatchEditor() { public void showEditorInput(IEditorInput editorInput) { String name = editorInput.getName(); if (name.equals(ICoreConstants.FEATURE_FILENAME_DESCRIPTOR)) { - setActivePage(0); - } else { - setActivePage(getPageCount() - 3); + setActivePage(FeatureFormPage.PAGE_ID); + } else if (name.equalsIgnoreCase(ICoreConstants.BUILD_FILENAME_DESCRIPTOR)) { + IFormPage uiPage = findPage(BuildPage.PAGE_ID); + if (uiPage != null) { + setActivePage(uiPage.getId()); + } + + } else if (name.equalsIgnoreCase(ICoreConstants.P2_INF_FILENAME)) { + IFormPage page = findPage(P2InfInputContext.CONTEXT_ID); + if (page != null) { + setActivePage(page.getId()); + } } } diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureEditorMatchingStrategy.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureEditorMatchingStrategy.java index abddd0ad3ae..4fa2d5045f0 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureEditorMatchingStrategy.java +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureEditorMatchingStrategy.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2005, 2015 IBM Corporation and others. + * Copyright (c) 2005, 2015, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -45,11 +45,18 @@ public boolean matches(IEditorReference editorRef, IEditorInput input) { // build.properties matches with editors that have a feature.xml file // as their input and that feature.xml is at the root if (inputFile.getName().equals(ICoreConstants.FEATURE_FILENAME_DESCRIPTOR)) { - if (currInputFile.getName().equals(ICoreConstants.BUILD_FILENAME_DESCRIPTOR)) { + if (currInputFile.getName().equals(ICoreConstants.BUILD_FILENAME_DESCRIPTOR) + || currInputFile.getName().equals(ICoreConstants.P2_INF_FILENAME)) { return inputFile.getProjectRelativePath().toString().equals(ICoreConstants.FEATURE_FILENAME_DESCRIPTOR); } return inputFile.equals(currInputFile); } else if (inputFile.getName().equals(ICoreConstants.BUILD_FILENAME_DESCRIPTOR)) { + if (currInputFile.getName().equals(ICoreConstants.FEATURE_FILENAME_DESCRIPTOR)) + return currInputFile.getProjectRelativePath().toString().equals(ICoreConstants.FEATURE_FILENAME_DESCRIPTOR); + return inputFile.equals(currInputFile); + } else if (inputFile.getName().equals(ICoreConstants.P2_INF_FILENAME)) { + // p2.inf should match the feature editor when the current editor + // input is the feature.xml at the project root if (currInputFile.getName().equals(ICoreConstants.FEATURE_FILENAME_DESCRIPTOR)) { return currInputFile.getProjectRelativePath().toString().equals(ICoreConstants.FEATURE_FILENAME_DESCRIPTOR); } @@ -60,5 +67,4 @@ public boolean matches(IEditorReference editorRef, IEditorInput input) { return false; } } - -} +} \ No newline at end of file diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/Messages.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/Messages.java new file mode 100644 index 00000000000..e89718af663 --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/Messages.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.pde.internal.ui.editor.p2inf; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = Messages.class.getPackageName() + ".messages"; //$NON-NLS-1$ + public static String P2InfHeader_provides; + public static String P2InfHeader_requires; + public static String P2InfHeader_metaRequirements; + public static String P2InfHeader_properties; + public static String P2InfHeader_update; + public static String P2InfHeader_instructions; + public static String P2InfHeader_units; + + public static String P2InfHeader_filter; + public static String P2InfHeader_greedy; + public static String P2InfHeader_matchExp; + public static String P2InfHeader_multiple; + public static String P2InfHeader_name; + public static String P2InfHeader_namespace; + public static String P2InfHeader_optional; + public static String P2InfHeader_range; + public static String P2InfHeader_version; + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2InfContentAssistProcessor.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2InfContentAssistProcessor.java new file mode 100644 index 00000000000..b66fbf01380 --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2InfContentAssistProcessor.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.pde.internal.ui.editor.p2inf; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.pde.internal.ui.editor.PDESourcePage; +import org.eclipse.pde.internal.ui.editor.contentassist.TypeCompletionProposal; +import org.eclipse.pde.internal.ui.editor.contentassist.TypePackageCompletionProcessor; + +public class P2InfContentAssistProcessor extends TypePackageCompletionProcessor { + + protected PDESourcePage fSourcePage; + private SuggestionNode root; + private static final String[] COMMON_PARTS = { Messages.P2InfHeader_namespace, Messages.P2InfHeader_name, + Messages.P2InfHeader_version, Messages.P2InfHeader_range, Messages.P2InfHeader_matchExp, + Messages.P2InfHeader_greedy, Messages.P2InfHeader_optional, Messages.P2InfHeader_multiple, + Messages.P2InfHeader_filter }; + + public P2InfContentAssistProcessor(PDESourcePage sourcePage) { + fSourcePage = sourcePage; + buildSuggestionTree(); + } + + private static class SuggestionNode { + String key; + boolean index; + boolean terminal; + List children = new ArrayList<>(); + SuggestionNode(String key) { + this.key = key; + } + SuggestionNode index() { + this.index = true; + return this; + } + SuggestionNode terminal() { + this.terminal = true; + return this; + } + SuggestionNode addChild(SuggestionNode node) { + children.add(node); + return this; + } + } + + // Build Suggestion Tree + private void buildSuggestionTree() { + root = new SuggestionNode("root"); //$NON-NLS-1$ + + SuggestionNode provides = new SuggestionNode(Messages.P2InfHeader_provides); + SuggestionNode requires = new SuggestionNode(Messages.P2InfHeader_requires); + SuggestionNode metaReq = new SuggestionNode(Messages.P2InfHeader_metaRequirements); + SuggestionNode properties = new SuggestionNode(Messages.P2InfHeader_properties); + SuggestionNode update = new SuggestionNode(Messages.P2InfHeader_update); + SuggestionNode instructions = new SuggestionNode(Messages.P2InfHeader_instructions); + SuggestionNode units = new SuggestionNode(Messages.P2InfHeader_units); + + root.addChild(provides).addChild(requires).addChild(metaReq).addChild(properties).addChild(update) + .addChild(instructions).addChild(units); + + // Level-1 structures + addIndexedParts(provides); + addIndexedParts(requires); + addIndexedParts(metaReq); + addIndexedParts(properties); + addIndexedParts(update); + addIndexedParts(instructions); + + // units.{#} + SuggestionNode unitsIndex = new SuggestionNode("{#}").index(); //$NON-NLS-1$ + units.addChild(unitsIndex); + SuggestionNode unitRequires = new SuggestionNode(Messages.P2InfHeader_requires); + SuggestionNode unitProvides = new SuggestionNode(Messages.P2InfHeader_provides); + SuggestionNode unitProperties = new SuggestionNode(Messages.P2InfHeader_properties); + SuggestionNode unitMetaRed = new SuggestionNode(Messages.P2InfHeader_metaRequirements); + SuggestionNode unitUpdate = new SuggestionNode(Messages.P2InfHeader_update); + SuggestionNode unitInstructions = new SuggestionNode(Messages.P2InfHeader_instructions); + unitsIndex.addChild(unitRequires); + unitsIndex.addChild(unitProvides); + unitsIndex.addChild(unitProperties); + unitsIndex.addChild(unitMetaRed); + unitsIndex.addChild(unitUpdate); + unitsIndex.addChild(unitInstructions); + + addIndexedParts(unitRequires); + addIndexedParts(unitProvides); + addIndexedParts(unitProperties); + addIndexedParts(unitMetaRed); + addIndexedParts(unitUpdate); + addIndexedParts(unitInstructions); + } + + private void addIndexedParts(SuggestionNode parent) { + SuggestionNode indexNode = new SuggestionNode("{#}").index(); //$NON-NLS-1$ + parent.addChild(indexNode); + for (String p : COMMON_PARTS) { + indexNode.addChild(new SuggestionNode(p).terminal()); + } + } + + @Override + public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { + IDocument doc = fSourcePage.getDocumentProvider().getDocument(fSourcePage.getInputContext().getInput()); + try { + int lineNum = doc.getLineOfOffset(offset); + int lineStart = doc.getLineOffset(lineNum); + String value = doc.get(lineStart, offset - lineStart).trim(); + List completions = new ArrayList<>(); + List suggestions = getSuggestions(value); + for (SuggestionNode node : suggestions) { + String proposalText; + if (node.index) { + proposalText = value + "0."; //$NON-NLS-1$ + } else if (node.terminal) { + proposalText = value + node.key; + } else { + proposalText = value + node.key + "."; //$NON-NLS-1$ + } + completions + .add(new TypeCompletionProposal(proposalText, null, proposalText, lineStart, value.length())); + } + return completions.toArray(ICompletionProposal[]::new); + } catch (BadLocationException e) { + e.printStackTrace(); + } + return null; + } + + private List getSuggestions(String line) { + if (line.isEmpty()) { + return root.children; + } + String[] tokens = line.split("\\."); //$NON-NLS-1$ + SuggestionNode current = root; + for (String token : tokens) { + if (token.isEmpty()) + continue; + SuggestionNode next = null; + for (SuggestionNode child : current.children) { + if (child.index && token.matches("\\d+")) { //$NON-NLS-1$ + next = child; + break; + } + if (!child.index && child.key.equals(token)) { + next = child; + break; + } + } + if (next == null) + return new ArrayList<>(); + current = next; + } + return current.children; + } +} + diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2InfInputContext.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2InfInputContext.java new file mode 100644 index 00000000000..600a9942c5b --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2InfInputContext.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.pde.internal.ui.editor.p2inf; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.pde.core.IBaseModel; +import org.eclipse.pde.core.IModelChangedEvent; +import org.eclipse.pde.internal.ui.editor.PDEFormEditor; +import org.eclipse.pde.internal.ui.editor.context.InputContext; +import org.eclipse.text.edits.TextEdit; +import org.eclipse.ui.IEditorInput; + +public class P2InfInputContext extends InputContext { + public static final String P2_INF_PARTITION = "___p2_inf_partition"; //$NON-NLS-1$ + + public static final String CONTEXT_ID = "p2inf-context"; //$NON-NLS-1$ + + public P2InfInputContext(PDEFormEditor editor, IEditorInput input, boolean primary) { + super(editor, input, primary); + create(); + } + + @Override + protected Charset getDefaultCharset() { + return StandardCharsets.UTF_8; + } + + @Override + protected IBaseModel createModel(IEditorInput input) throws CoreException { + IDocument document = getDocumentProvider().getDocument(input); + P2InfModel model = new P2InfModel(document); + model.load(); + return model; + } + + @Override + public P2InfModel getModel() { + return (P2InfModel) super.getModel(); + } + + @Override + public String getId() { + return CONTEXT_ID; + } + + @Override + protected void addTextEditOperation(ArrayList ops, IModelChangedEvent event) { + + } + + @Override + public void doRevert() { + // Revert to the saved version by reloading the model from the document + P2InfModel model = getModel(); + if (model != null) { + model.load(); + } + } + + @Override + protected String getPartitionName() { + return P2_INF_PARTITION; + } +} \ No newline at end of file diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2InfModel.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2InfModel.java new file mode 100644 index 00000000000..1e00cc36eda --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2InfModel.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.pde.internal.ui.editor.p2inf; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.pde.core.IBaseModel; +import org.eclipse.pde.core.IModelChangeProvider; +import org.eclipse.pde.core.IModelChangedEvent; +import org.eclipse.pde.core.IModelChangedListener; + +public class P2InfModel implements IBaseModel, IModelChangeProvider { + + private final IDocument document; + private volatile boolean valid; + private volatile boolean disposed; + + public P2InfModel(IDocument document) { + this.document = document; + } + + public void load() { + valid = document != null; + } + + public IDocument getDocument() { + return document; + } + + @Override + public boolean isEditable() { + return true; + } + + @Override + public boolean isValid() { + return valid; + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public void dispose() { + disposed = true; + } + + @Override + public T getAdapter(Class adapter) { + if (adapter == IDocument.class) { + return adapter.cast(document); + } + return null; + } + + @Override + public void addModelChangedListener(IModelChangedListener listener) { + } + + @Override + public void fireModelChanged(IModelChangedEvent event) { + } + + @Override + public void fireModelObjectChanged(Object object, String property, Object oldValue, Object newValue) { + } + + @Override + public void removeModelChangedListener(IModelChangedListener listener) { + } +} \ No newline at end of file diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2InfSourcePage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2InfSourcePage.java new file mode 100644 index 00000000000..f3c53fcdabf --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2InfSourcePage.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.pde.internal.ui.editor.p2inf; + +import org.eclipse.jdt.ui.PreferenceConstants; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.pde.internal.ui.editor.KeyValueSourcePage; +import org.eclipse.pde.internal.ui.editor.PDEFormEditor; +import org.eclipse.pde.internal.ui.editor.text.ChangeAwareSourceViewerConfiguration; +import org.eclipse.pde.internal.ui.editor.text.IColorManager; +import org.eclipse.ui.editors.text.EditorsUI; +import org.eclipse.ui.texteditor.ChainedPreferenceStore; + +public class P2InfSourcePage extends KeyValueSourcePage { + + public P2InfSourcePage(PDEFormEditor editor, String id, String title) { + super(editor, id, title); + } + + @Override + public ILabelProvider createOutlineLabelProvider() { + return null; + } + + @Override + public ITreeContentProvider createOutlineContentProvider() { + return null; + } + + @Override + public void updateSelection(Object object) { + } + + @Override + protected ChangeAwareSourceViewerConfiguration createSourceViewerConfiguration(IColorManager colorManager) { + IPreferenceStore store = PreferenceConstants.getPreferenceStore(); + IPreferenceStore generalTextStore = EditorsUI.getPreferenceStore(); + IPreferenceStore combinedStore = new ChainedPreferenceStore(new IPreferenceStore[] { store, generalTextStore }); + this.setPreferenceStore(combinedStore); + return new P2infViewerConfiguration(colorManager, combinedStore, this); + } + +} diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2infViewerConfiguration.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2infViewerConfiguration.java new file mode 100644 index 00000000000..aea36c740e9 --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/P2infViewerConfiguration.java @@ -0,0 +1,291 @@ +/******************************************************************************* + * Copyright (c) 2000, 2015 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.pde.internal.ui.editor.p2inf; + +import org.eclipse.jdt.ui.PreferenceConstants; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DefaultInformationControl; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.TextAttribute; +import org.eclipse.jface.text.contentassist.ContentAssistant; +import org.eclipse.jface.text.contentassist.IContentAssistant; +import org.eclipse.jface.text.presentation.IPresentationReconciler; +import org.eclipse.jface.text.presentation.PresentationReconciler; +import org.eclipse.jface.text.rules.DefaultDamagerRepairer; +import org.eclipse.jface.text.rules.IRule; +import org.eclipse.jface.text.rules.IWordDetector; +import org.eclipse.jface.text.rules.Token; +import org.eclipse.jface.text.rules.WhitespaceRule; +import org.eclipse.jface.text.rules.WordRule; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.pde.internal.ui.PDEPluginImages; +import org.eclipse.pde.internal.ui.editor.PDESourcePage; +import org.eclipse.pde.internal.ui.editor.text.ArgumentRule; +import org.eclipse.pde.internal.ui.editor.text.BasePDEScanner; +import org.eclipse.pde.internal.ui.editor.text.ChangeAwareSourceViewerConfiguration; +import org.eclipse.pde.internal.ui.editor.text.IColorManager; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; + +public class P2infViewerConfiguration extends ChangeAwareSourceViewerConfiguration { + + protected static String PROPERTIES_FILE_PARTITIONING = "___pf_partitioning"; //$NON-NLS-1$ + protected static String COMMENT = "__pf_comment"; //$NON-NLS-1$ + protected static String PROPERTY_VALUE = "__pf_roperty_value"; //$NON-NLS-1$ + protected static String[] PARTITIONS = new String[] { COMMENT, PROPERTY_VALUE }; + + private BasePDEScanner fPropertyKeyScanner; + private BasePDEScanner fCommentScanner; + private BasePDEScanner fPropertyValueScanner; + + private ContentAssistant fContentAssistant; + + private abstract class AbstractJavaScanner extends BasePDEScanner { + + @Override + public void adaptToPreferenceChange(PropertyChangeEvent event) { + String property = event.getProperty(); + if (affectsTextPresentation(property)) { + Token token = getTokenAffected(event); + if (property.endsWith(PreferenceConstants.EDITOR_BOLD_SUFFIX)) { + adaptToStyleChange(event, token, SWT.BOLD); + } else if (property.endsWith(PreferenceConstants.EDITOR_ITALIC_SUFFIX)) { + adaptToStyleChange(event, token, SWT.ITALIC); + } else if (property.endsWith(PreferenceConstants.EDITOR_STRIKETHROUGH_SUFFIX)) { + adaptToStyleChange(event, token, TextAttribute.STRIKETHROUGH); + } else if (property.endsWith(PreferenceConstants.EDITOR_UNDERLINE_SUFFIX)) { + adaptToStyleChange(event, token, TextAttribute.UNDERLINE); + } else { + adaptToColorChange(event, token); + } + } + } + + @Override + protected TextAttribute createTextAttribute(String property) { + Color color = fColorManager.getColor(property); + int style = SWT.NORMAL; + if (fPreferenceStore.getBoolean(property + PreferenceConstants.EDITOR_BOLD_SUFFIX)) { + style |= SWT.BOLD; + } + if (fPreferenceStore.getBoolean(property + PreferenceConstants.EDITOR_ITALIC_SUFFIX)) { + style |= SWT.ITALIC; + } + if (fPreferenceStore.getBoolean(property + PreferenceConstants.EDITOR_STRIKETHROUGH_SUFFIX)) { + style |= TextAttribute.STRIKETHROUGH; + } + if (fPreferenceStore.getBoolean(property + PreferenceConstants.EDITOR_UNDERLINE_SUFFIX)) { + style |= TextAttribute.UNDERLINE; + } + return new TextAttribute(color, null, style); + } + } + + private class SingleTokenJavaScanner extends AbstractJavaScanner { + + private final String fProperty; + + public SingleTokenJavaScanner(String property) { + fProperty = property; + setColorManager(fColorManager); + initialize(); + } + + @Override + public boolean affectsTextPresentation(String property) { + return property.startsWith(fProperty); + } + + @Override + protected Token getTokenAffected(PropertyChangeEvent event) { + return (Token) fDefaultReturnToken; + } + + @Override + protected void initialize() { + setDefaultReturnToken(new Token(createTextAttribute(fProperty))); + } + } + + public class PropertyValueScanner extends AbstractJavaScanner { + + public class AssignmentDetector implements IWordDetector { + + @Override + public boolean isWordStart(char c) { + if ('=' != c && ':' != c || fDocument == null) { + return false; + } + + try { + // check whether it is the first '=' + IRegion lineInfo = fDocument.getLineInformationOfOffset(fOffset); + int offset = lineInfo.getOffset(); + String line = fDocument.get(offset, lineInfo.getLength()); + int i = line.indexOf(c); + return i != -1 && i + lineInfo.getOffset() + 1 == fOffset; + } catch (BadLocationException ex) { + return false; + } + } + + @Override + public boolean isWordPart(char c) { + return false; + } + } + + private Token fArgumentToken; + private Token fAssignmentToken; + + public PropertyValueScanner() { + setColorManager(fColorManager); + initialize(); + } + + @Override + public boolean affectsTextPresentation(String property) { + return property.startsWith(PreferenceConstants.PROPERTIES_FILE_COLORING_VALUE) + || property.startsWith(PreferenceConstants.PROPERTIES_FILE_COLORING_ARGUMENT) + || property.startsWith(PreferenceConstants.PROPERTIES_FILE_COLORING_ASSIGNMENT); + } + + @Override + protected Token getTokenAffected(PropertyChangeEvent event) { + String property = event.getProperty(); + if (property.startsWith(PreferenceConstants.PROPERTIES_FILE_COLORING_ARGUMENT)) { + return fArgumentToken; + } + if (property.startsWith(PreferenceConstants.PROPERTIES_FILE_COLORING_ASSIGNMENT)) { + return fAssignmentToken; + } + return (Token) fDefaultReturnToken; + } + + @Override + protected void initialize() { + IRule[] rules = new IRule[3]; + fArgumentToken = new Token(createTextAttribute(PreferenceConstants.PROPERTIES_FILE_COLORING_ARGUMENT)); + rules[0] = new ArgumentRule(fArgumentToken); + + fAssignmentToken = new Token(createTextAttribute(PreferenceConstants.PROPERTIES_FILE_COLORING_ASSIGNMENT)); + rules[1] = new WordRule(new AssignmentDetector(), fAssignmentToken); + + rules[2] = new WhitespaceRule(Character::isWhitespace); + setRules(rules); + setDefaultReturnToken(new Token(createTextAttribute(PreferenceConstants.PROPERTIES_FILE_COLORING_VALUE))); + } + } + + public P2infViewerConfiguration(IColorManager colorManager, IPreferenceStore store, + PDESourcePage sourcePage) { + super(sourcePage, colorManager, store); + initializeScanners(); + } + + private void initializeScanners() { + fPropertyKeyScanner = new SingleTokenJavaScanner(PreferenceConstants.PROPERTIES_FILE_COLORING_KEY); + fPropertyValueScanner = new PropertyValueScanner(); + fCommentScanner = new SingleTokenJavaScanner(PreferenceConstants.PROPERTIES_FILE_COLORING_COMMENT); + } + + @Override + public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) { + PresentationReconciler reconciler = new PresentationReconciler(); + reconciler.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer)); + + DefaultDamagerRepairer dr = new DefaultDamagerRepairer(fPropertyKeyScanner); + reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE); + reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE); + + dr = new DefaultDamagerRepairer(fCommentScanner); + reconciler.setDamager(dr, COMMENT); + reconciler.setRepairer(dr, COMMENT); + + dr = new DefaultDamagerRepairer(fPropertyValueScanner); + reconciler.setDamager(dr, PROPERTY_VALUE); + reconciler.setRepairer(dr, PROPERTY_VALUE); + + return reconciler; + } + + @Override + public void adaptToPreferenceChange(PropertyChangeEvent event) { + if (affectsColorPresentation(event)) { + fColorManager.handlePropertyChangeEvent(event); + } + fPropertyKeyScanner.adaptToPreferenceChange(event); + fCommentScanner.adaptToPreferenceChange(event); + fPropertyValueScanner.adaptToPreferenceChange(event); + } + + @Override + public String[] getConfiguredContentTypes(ISourceViewer sourceViewer) { + int length = PARTITIONS.length; + String[] contentTypes = new String[length + 1]; + contentTypes[0] = IDocument.DEFAULT_CONTENT_TYPE; + System.arraycopy(PARTITIONS, 0, contentTypes, 1, length); + + return contentTypes; + } + + @Override + public String getConfiguredDocumentPartitioning(ISourceViewer sourceViewer) { + return PROPERTIES_FILE_PARTITIONING; + } + + @Override + public boolean affectsTextPresentation(PropertyChangeEvent event) { + String property = event.getProperty(); + return fCommentScanner.affectsTextPresentation(property) + || fPropertyKeyScanner.affectsTextPresentation(property) + || fPropertyValueScanner.affectsTextPresentation(property); + } + + @Override + public boolean affectsColorPresentation(PropertyChangeEvent event) { + String property = event.getProperty(); + return property.equals(PreferenceConstants.PROPERTIES_FILE_COLORING_VALUE) + || property.equals(PreferenceConstants.PROPERTIES_FILE_COLORING_ARGUMENT) + || property.equals(PreferenceConstants.PROPERTIES_FILE_COLORING_ASSIGNMENT) + || property.equals(PreferenceConstants.PROPERTIES_FILE_COLORING_KEY) + || property.equals(PreferenceConstants.PROPERTIES_FILE_COLORING_COMMENT); + } + + @Override + public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) { + if (fSourcePage != null && fSourcePage.isEditable()) { + if (fContentAssistant == null) { + // Initialize in SWT thread before using in background thread: + PDEPluginImages.get(null); + fContentAssistant = new ContentAssistant(true); + fContentAssistant.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer)); + var fContentAssistantProcessor = new P2InfContentAssistProcessor(fSourcePage); + fContentAssistant.setContentAssistProcessor(fContentAssistantProcessor, IDocument.DEFAULT_CONTENT_TYPE); + fContentAssistant.setContentAssistProcessor(fContentAssistantProcessor, PROPERTY_VALUE); + fContentAssistant.enableAutoInsert(true); + fContentAssistant.setInformationControlCreator(parent -> new DefaultInformationControl(parent, false)); + fContentAssistant.setContextInformationPopupOrientation(IContentAssistant.CONTEXT_INFO_ABOVE); + fContentAssistant.enableAutoActivation(true); + } + return fContentAssistant; + } + return null; + } + +} diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/messages.properties b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/messages.properties new file mode 100644 index 00000000000..4add487a846 --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/p2inf/messages.properties @@ -0,0 +1,29 @@ +############################################################################### +# Copyright (c) 2026 IBM Corporation 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: +# IBM Corporation - initial API and implementation +############################################################################### +P2InfHeader_provides=provides +P2InfHeader_requires=requires +P2InfHeader_metaRequirements=metaRequirements +P2InfHeader_properties=properties +P2InfHeader_update=update +P2InfHeader_instructions=instructions +P2InfHeader_units=units +P2InfHeader_filter=filter +P2InfHeader_greedy=greedy +P2InfHeader_matchExp=matchExp +P2InfHeader_multiple=multiple +P2InfHeader_name=name +P2InfHeader_namespace=namespace +P2InfHeader_optional=optional +P2InfHeader_range=range +P2InfHeader_version=version diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ManifestEditor.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ManifestEditor.java index fcbfc80bc5b..796d69d4951 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ManifestEditor.java +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ManifestEditor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2024 IBM Corporation and others. + * Copyright (c) 2000, 2024, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -82,6 +82,8 @@ import org.eclipse.pde.internal.ui.editor.build.BuildSourcePage; import org.eclipse.pde.internal.ui.editor.context.InputContext; import org.eclipse.pde.internal.ui.editor.context.InputContextManager; +import org.eclipse.pde.internal.ui.editor.p2inf.P2InfInputContext; +import org.eclipse.pde.internal.ui.editor.p2inf.P2InfSourcePage; import org.eclipse.pde.internal.ui.wizards.tools.OrganizeManifestsAction; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IEditorInput; @@ -274,6 +276,12 @@ protected void createResourceContexts(InputContextManager manager, IFileEditorIn manager.putContext(in, new BuildInputContext(this, in, false)); } manager.monitorFile(buildFile); + IFile p2InfFile = container.getFile(ICoreConstants.P2_INF_BUNDLE_PATH); + if (p2InfFile != null && p2InfFile.exists()) { + FileEditorInput in = new FileEditorInput(p2InfFile); + manager.putContext(in, new P2InfInputContext(this, in, false)); + } + manager.monitorFile(p2InfFile); fPrefs = new ProjectScope(container.getProject()).getNode(PDECore.PLUGIN_ID); if (fPrefs != null) { @@ -325,6 +333,16 @@ public void monitoredFileAdded(IFile file) { IEditorInput in = new FileEditorInput(file); fInputContextManager.putContext(in, new BndInputContext(this, in, false)); } + } else if (name.equalsIgnoreCase(ICoreConstants.P2_INF_FILENAME)) { + // check if p2.inf is in META-INF folder (for bundles) + if (!fInputContextManager.hasContext(P2InfInputContext.CONTEXT_ID)) { + IContainer parent = file.getParent(); + // compare with folder name "META-INF/" + if (parent != null && "META-INF".equals(parent.getName())) { //$NON-NLS-1$ + IEditorInput in = new FileEditorInput(file); + fInputContextManager.putContext(in, new P2InfInputContext(this, in, false)); + } + } } } @@ -561,6 +579,7 @@ protected void addEditorPages() { addSourcePage(PluginInputContext.CONTEXT_ID); addSourcePage(BuildInputContext.CONTEXT_ID); addSourcePage(BndInputContext.CONTEXT_ID); + addSourcePage(P2InfInputContext.CONTEXT_ID); } private boolean isSourcePageID(String pageID) { @@ -574,6 +593,7 @@ private boolean isSourcePageID(String pageID) { case PluginInputContext.CONTEXT_ID: // plugin.xml case BundleInputContext.CONTEXT_ID: // MANIFEST.MF case BndInputContext.CONTEXT_ID: // bnd instruction + case P2InfInputContext.CONTEXT_ID: // p2.inf return true; default: break; @@ -688,6 +708,9 @@ protected IEditorPart createSourcePage(PDEFormEditor editor, String title, Strin if (contextId.equals(BndInputContext.CONTEXT_ID)) { return new BndSourcePage(editor, contextId, title); } + if (contextId.equals(P2InfInputContext.CONTEXT_ID)) { + return new P2InfSourcePage(editor, contextId, title); + } return super.createSourcePage(editor, title, name, contextId); } @@ -785,6 +808,10 @@ public void showEditorInput(IEditorInput editorInput) { setActivePage(OverviewPage.PAGE_ID); } } + } else if (name.equals(ICoreConstants.P2_INF_FILENAME)) { + if (!P2InfInputContext.CONTEXT_ID.equals(id)) { + setActivePage(P2InfInputContext.CONTEXT_ID); + } } else if (!BundleInputContext.CONTEXT_ID.equals(id)) { setActivePage(SHOW_SOURCE ? BundleInputContext.CONTEXT_ID : OverviewPage.PAGE_ID); } diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ManifestEditorMatchingStrategy.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ManifestEditorMatchingStrategy.java index 997eb03dc3c..17da18b858c 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ManifestEditorMatchingStrategy.java +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ManifestEditorMatchingStrategy.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2005, 2015 IBM Corporation and others. + * Copyright (c) 2005, 2015, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -71,6 +71,15 @@ public boolean matches(IEditorReference editorRef, IEditorInput input) { IFile file = parent.getFile(ICoreConstants.MANIFEST_PATH); return file.exists() && editorFile.equals(file); } + // if a p2.inf is being opened, only return a positive match + // if an editor is already open on a sibling + // plugin.xml/fragment.xml or a META-INF/MANIFEST.MF + if (inputFile.getName().equals(ICoreConstants.P2_INF_FILENAME)) { + IContainer parent = inputFile.getParent().getParent(); + IFile file = parent.getFile(ICoreConstants.MANIFEST_PATH); + return file.exists() && editorFile.equals(file); + + } } catch (PartInitException e) { return false; }