diff --git a/bundles/org.eclipse.ui.editors/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.editors/META-INF/MANIFEST.MF index 5a78bb6d2b6f..8e375d7187a2 100644 --- a/bundles/org.eclipse.ui.editors/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.editors/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.ui.editors; singleton:=true -Bundle-Version: 3.21.0.qualifier +Bundle-Version: 3.22.0.qualifier Bundle-Activator: org.eclipse.ui.internal.editors.text.EditorsPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName diff --git a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorDefaultsPreferencePage.java b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorDefaultsPreferencePage.java index 0c2f05e44c1f..2efdf7a5cd35 100644 --- a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorDefaultsPreferencePage.java +++ b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorDefaultsPreferencePage.java @@ -68,6 +68,8 @@ import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.widgets.ButtonFactory; +import org.eclipse.jface.widgets.WidgetFactory; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPreferencePage; @@ -451,7 +453,9 @@ private OverlayPreferenceStore createDialogOverlayStore() { overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ENCLOSED_TABS)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TRAILING_TABS)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_CARRIAGE_RETURN)); - overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LINE_FEED)); + overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LINE_FEED)); + overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, + AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ZW_CHARACTERS)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.INT, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_WHITESPACE_CHARACTER_ALPHA_VALUE)); OverlayPreferenceStore.OverlayKey[] keys= new OverlayPreferenceStore.OverlayKey[overlayKeys.size()]; @@ -571,6 +575,15 @@ protected Control createDialogArea(Composite parent) { preference= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LINE_FEED, "", null); //$NON-NLS-1$ addCheckBox(tabularComposite, preference, new BooleanDomain(), 0); + WidgetFactory.label(SWT.NONE).text(TextEditorMessages.TextEditorDefaultsPreferencePage_zwcharacters) + .layoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)).create(tabularComposite); + ButtonFactory checkboxFactory = WidgetFactory.button(SWT.CHECK) + .supplyLayoutData(() -> new GridData(SWT.CENTER, SWT.CENTER, false, false)).enabled(false); + checkboxFactory.create(tabularComposite); + preference = new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ZW_CHARACTERS, "", null); //$NON-NLS-1$ + addCheckBox(tabularComposite, preference, new BooleanDomain(), 0); + checkboxFactory.create(tabularComposite); + Composite alphaComposite= new Composite(composite, SWT.NONE); layout= new GridLayout(); layout.numColumns= 2; @@ -809,6 +822,8 @@ private OverlayPreferenceStore createOverlayStore() { overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TRAILING_TABS)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_CARRIAGE_RETURN)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LINE_FEED)); + overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, + AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ZW_CHARACTERS)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.INT, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_WHITESPACE_CHARACTER_ALPHA_VALUE)); OverlayPreferenceStore.OverlayKey[] keys= new OverlayPreferenceStore.OverlayKey[overlayKeys.size()]; diff --git a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorMessages.java b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorMessages.java index 92a3d48a385b..fdcd10917980 100644 --- a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorMessages.java +++ b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorMessages.java @@ -159,6 +159,7 @@ private TextEditorMessages() { public static String TextEditorDefaultsPreferencePage_ideographicSpace; public static String TextEditorDefaultsPreferencePage_leading; public static String TextEditorDefaultsPreferencePage_lineFeed; + public static String TextEditorDefaultsPreferencePage_zwcharacters; public static String TextEditorDefaultsPreferencePage_range_indicator; public static String TextEditorDefaultsPreferencePage_smartHomeEnd; public static String TextEditorDefaultsPreferencePage_warn_if_derived; diff --git a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorMessages.properties b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorMessages.properties index 2e5a7267edc7..0193e8143792 100644 --- a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorMessages.properties +++ b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorMessages.properties @@ -61,6 +61,7 @@ TextEditorDefaultsPreferencePage_enrichHover_onClick=Enrich on click TextEditorDefaultsPreferencePage_ideographicSpace=Ideographic space ( \u00b0 ) TextEditorDefaultsPreferencePage_leading=Leading TextEditorDefaultsPreferencePage_lineFeed=Line Feed ( \u00b6 ) +TextEditorDefaultsPreferencePage_zwcharacters=Zero-Width Characters TextEditorDefaultsPreferencePage_range_indicator=Show &range indicator TextEditorDefaultsPreferencePage_warn_if_derived= War&n before editing a derived file TextEditorDefaultsPreferencePage_smartHomeEnd= &Smart caret positioning at line start and end diff --git a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/AbstractDecoratedTextEditorPreferenceConstants.java b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/AbstractDecoratedTextEditorPreferenceConstants.java index 3c1a98e38e58..2f50bf9e00eb 100644 --- a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/AbstractDecoratedTextEditorPreferenceConstants.java +++ b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/AbstractDecoratedTextEditorPreferenceConstants.java @@ -502,7 +502,8 @@ private AbstractDecoratedTextEditorPreferenceConstants() { *

* *

- * The following preferences can be used for fine-grained configuration when enabled. + * The following preferences can be used for fine-grained configuration when + * enabled. *

* * @@ -647,6 +649,18 @@ private AbstractDecoratedTextEditorPreferenceConstants() { */ public static final String EDITOR_SHOW_LINE_FEED= AbstractTextEditor.PREFERENCE_SHOW_LINE_FEED; + /** + * A named preference that controls the display of zero-width characters like + * zero-width space. The value is used only if the value of + * {@link #EDITOR_SHOW_WHITESPACE_CHARACTERS} is true. + *

+ * Value is of type Boolean. + *

+ * + * @since 3.22 + */ + public static final String EDITOR_SHOW_ZW_CHARACTERS = AbstractTextEditor.PREFERENCE_SHOW_ZW_CHARACTERS; + /** * A named preference that controls the alpha value of whitespace characters. The value is used * only if the value of {@link #EDITOR_SHOW_WHITESPACE_CHARACTERS} is true. @@ -858,6 +872,7 @@ public static void initializeDefaultValues(IPreferenceStore store) { store.setDefault(EDITOR_SHOW_TRAILING_TABS, true); store.setDefault(EDITOR_SHOW_CARRIAGE_RETURN, true); store.setDefault(EDITOR_SHOW_LINE_FEED, true); + store.setDefault(EDITOR_SHOW_ZW_CHARACTERS, true); store.setDefault(EDITOR_WHITESPACE_CHARACTER_ALPHA_VALUE, 80); store.setDefault(EDITOR_TEXT_DRAG_AND_DROP_ENABLED, true); diff --git a/bundles/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF index 7f40fef68bba..1f64abc9eb4d 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.ui.workbench.texteditor; singleton:=true -Bundle-Version: 3.20.0.qualifier +Bundle-Version: 3.21.0.qualifier Bundle-Activator: org.eclipse.ui.internal.texteditor.TextEditorPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName diff --git a/bundles/org.eclipse.ui.workbench.texteditor/plugin.properties b/bundles/org.eclipse.ui.workbench.texteditor/plugin.properties index f647d810d8d9..309a98d74290 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/plugin.properties +++ b/bundles/org.eclipse.ui.workbench.texteditor/plugin.properties @@ -215,3 +215,5 @@ blockSelectionModeFont.label= Text Editor Block Selection Font blockSelectionModeFont.description= The block selection mode font is used by text editors in block (column) mode. A monospace font should be used. MinimapView.name=Minimap + +CodeMining.show.ZWSP=Show ZWSP (Zero-Width Space) diff --git a/bundles/org.eclipse.ui.workbench.texteditor/plugin.xml b/bundles/org.eclipse.ui.workbench.texteditor/plugin.xml index e2ef210299f6..aaf9928b5119 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/plugin.xml +++ b/bundles/org.eclipse.ui.workbench.texteditor/plugin.xml @@ -1491,5 +1491,13 @@ visible="false"> - + + + + + diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/codemining/ZeroWidthCharactersLineContentCodeMining.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/codemining/ZeroWidthCharactersLineContentCodeMining.java new file mode 100644 index 000000000000..c78ba39d108b --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/codemining/ZeroWidthCharactersLineContentCodeMining.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2025 SAP S.E. 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: + * SAP S.E. - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.texteditor.codemining; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; + +import org.eclipse.jface.preference.IPreferenceStore; + +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.codemining.ICodeMiningProvider; +import org.eclipse.jface.text.codemining.LineContentCodeMining; + +import org.eclipse.ui.texteditor.AbstractTextEditor; + +/** + * A code mining that draws zero-width characters (like zero-width spaces) as + * line content code minings. + * + * @see ZeroWidthCharactersLineContentCodeMiningProvider + */ +class ZeroWidthCharactersLineContentCodeMining extends LineContentCodeMining { + + private static final String ZW_CHARACTERS_MINING = "ZWSP"; //$NON-NLS-1$ + private final IPreferenceStore store; + private final int offset; + + public ZeroWidthCharactersLineContentCodeMining(int offset, ICodeMiningProvider provider, IPreferenceStore store) { + super(new Position(offset, 1), false, provider); + this.store = store; + this.offset = offset; + } + + @Override + public boolean isResolved() { + return true; + } + + @Override + public String getLabel() { + return ZW_CHARACTERS_MINING; + } + + @Override + public Point draw(GC gc, StyledText textWidget, Color color, int x, int y) { + int oldAlpha = -1; + boolean isAdvancedGraphicsPresent = gc.getAdvanced(); + if (isAdvancedGraphicsPresent) { + int alpha = store.getInt(AbstractTextEditor.PREFERENCE_WHITESPACE_CHARACTER_ALPHA_VALUE); + oldAlpha = gc.getAlpha(); + gc.setAlpha(alpha); + } + try { + gc.setForeground(getColor(textWidget)); + Point point = super.draw(gc, textWidget, color, x, y); + gc.setForeground(color); + return point; + } finally { + if (oldAlpha != -1) { + gc.setAlpha(oldAlpha); + } + } + } + + private Color getColor(StyledText textWidget) { + int off = offset - 1; + Color fg; + boolean isFullSelectionStyle = (textWidget.getStyle() & SWT.FULL_SELECTION) != SWT.NONE; + if (!textWidget.getBlockSelection() && isFullSelectionStyle && isOffsetSelected(textWidget, off)) { + fg = textWidget.getSelectionForeground(); + } else { + if (off < 0 || off >= textWidget.getCharCount()) { + fg = textWidget.getForeground(); + } else { + StyleRange styleRange = textWidget.getStyleRangeAtOffset(off); + if (styleRange == null || styleRange.foreground == null) { + fg = textWidget.getForeground(); + } else { + fg = styleRange.foreground; + } + } + } + return fg; + } + + private static final boolean isOffsetSelected(StyledText widget, int offset) { + Point selection = widget.getSelection(); + return offset >= selection.x && offset < selection.y; + } +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/codemining/ZeroWidthCharactersLineContentCodeMiningProvider.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/codemining/ZeroWidthCharactersLineContentCodeMiningProvider.java new file mode 100644 index 000000000000..29b6071fa9b2 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/codemining/ZeroWidthCharactersLineContentCodeMiningProvider.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2025 SAP S.E. 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: + * SAP S.E. - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.texteditor.codemining; + +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_SHOW_WHITESPACE_CHARACTERS; +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_SHOW_ZW_CHARACTERS; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.core.runtime.IProgressMonitor; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; + +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider; +import org.eclipse.jface.text.codemining.ICodeMining; +import org.eclipse.jface.text.source.ISourceViewerExtension5; + +/** + * A code mining provider that draws zero-width characters (like zero-width + * spaces) as line content code minings. + *

+ * The code mining is only shown if configured in the preferences. + *

+ */ +public class ZeroWidthCharactersLineContentCodeMiningProvider extends AbstractCodeMiningProvider + implements IPropertyChangeListener { + + private static final char ZW_SPACE = '\u200b'; + private static final char ZW_NON_JOINER = '\u200c'; + private static final char ZW_JOINER = '\u200d'; + private static final char ZW_NO_BREAK_SPACE = '\ufeff'; + + private static final Set ZW_CHARACTERS = Set.of(ZW_SPACE, ZW_NON_JOINER, ZW_JOINER, ZW_NO_BREAK_SPACE); + + private IPreferenceStore store; + private boolean showZwsp = false; + + @Override + public CompletableFuture> provideCodeMinings(ITextViewer viewer, + IProgressMonitor monitor) { + if (store == null) { + loadStoreAndReadProperty(); + } + + if (!showZwsp) { + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + List list = new ArrayList<>(); + String content = viewer.getDocument().get(); + for (int i = 0; i < content.length(); i++) { + boolean isZwCharacter = ZW_CHARACTERS.contains(content.charAt(i)); + if (isZwCharacter) { + list.add(createCodeMining(i)); + } + } + return CompletableFuture.completedFuture(list); + } + + @Override + public void propertyChange(PropertyChangeEvent event) { + if (PREFERENCE_SHOW_ZW_CHARACTERS.equals(event.getProperty()) + || PREFERENCE_SHOW_WHITESPACE_CHARACTERS.equals(event.getProperty())) { + readShowZwspFromStore(); + updateCodeMinings(); + } + } + + private void updateCodeMinings() { + ITextViewer viewer = getAdapter(ITextViewer.class); + if (viewer instanceof ISourceViewerExtension5 codeMiningExtension) { + codeMiningExtension.updateCodeMinings(); + } + } + + @Override + public void dispose() { + store.removePropertyChangeListener(this); + super.dispose(); + } + + private void loadStoreAndReadProperty() { + store = getAdapter(IPreferenceStore.class); + readShowZwspFromStore(); + store.addPropertyChangeListener(this); + } + + private ICodeMining createCodeMining(int offset) { + return new ZeroWidthCharactersLineContentCodeMining(offset + 1, this, store); + } + + private void readShowZwspFromStore() { + showZwsp = store.getBoolean(PREFERENCE_SHOW_ZW_CHARACTERS) + && store.getBoolean(PREFERENCE_SHOW_WHITESPACE_CHARACTERS); + } +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java index 2b6c418fc017..61f8efa2d754 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java @@ -2335,10 +2335,21 @@ private int computeOffsetAtLocation(ITextViewer textViewer, int x, int y) { public static final String PREFERENCE_SHOW_LINE_FEED = "showLineFeed"; //$NON-NLS-1$ /** - * A named preference that controls the alpha value of whitespace characters. - * The value is used only if the value of + * A named preference that controls the display of zero-width characters like + * zero-width space. The value is used only if the value of * {@link #PREFERENCE_SHOW_WHITESPACE_CHARACTERS} is true. *

+ * Value is of type Boolean. + *

+ * + * @since 3.21 + */ + public static final String PREFERENCE_SHOW_ZW_CHARACTERS = "showZwsp"; //$NON-NLS-1$ + + /** + * A named preference that controls the alpha value of whitespace characters. The value is used + * only if the value of {@link #PREFERENCE_SHOW_WHITESPACE_CHARACTERS} is true. + *

* Value is of type Integer. *

*