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 0c2f05e44c1..2efdf7a5cd3 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 92a3d48a385..fdcd1091798 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 2e5a7267edc..0193e814379 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 3c1a98e38e5..a1ca2f26191 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.? + */ + 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/plugin.properties b/bundles/org.eclipse.ui.workbench.texteditor/plugin.properties index f647d810d8d..309a98d7429 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 e2ef210299f..aaf9928b511 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 00000000000..4995b8e707f --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/codemining/ZeroWidthCharactersLineContentCodeMining.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * 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.StyledText; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Display; + +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.codemining.ICodeMiningProvider; +import org.eclipse.jface.text.codemining.LineContentCodeMining; + +/** + * 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$ + + public ZeroWidthCharactersLineContentCodeMining(int offset, ICodeMiningProvider provider) { + super(new Position(offset, 1), true, provider); + } + + @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) { + gc.setForeground(getColor(color)); + Point point = super.draw(gc, textWidget, color, x, y); + gc.setForeground(color); + return point; + } + + private Color getColor(Color predefinedColor) { + return Display.getCurrent() != null ? Display.getCurrent().getSystemColor(SWT.COLOR_LIST_FOREGROUND) + : predefinedColor; + } +} 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 00000000000..49fb9369161 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/codemining/ZeroWidthCharactersLineContentCodeMiningProvider.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * 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, this); + } + + 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 db9e8bb4f62..19d9aa9d5a5 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 @@ -2334,10 +2334,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.? + */ + 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. *

*