From 8059c711752247e2ba87336b24a4dda0dfd6a5a2 Mon Sep 17 00:00:00 2001
From: Marcus Hoepfner
Date: Tue, 19 Dec 2023 16:37:43 +0100
Subject: [PATCH] Show ZWSP (zero with space) as code mining
Issue: https://github.com/eclipse-platform/eclipse.platform.ui/issues/1002
---
.../TextEditorDefaultsPreferencePage.java | 17 ++-
.../editors/text/TextEditorMessages.java | 1 +
.../text/TextEditorMessages.properties | 1 +
...ecoratedTextEditorPreferenceConstants.java | 17 ++-
.../plugin.properties | 2 +
.../plugin.xml | 10 +-
...oWidthCharactersLineContentCodeMining.java | 105 ++++++++++++++++
...aractersLineContentCodeMiningProvider.java | 114 ++++++++++++++++++
.../ui/texteditor/AbstractTextEditor.java | 15 ++-
9 files changed, 277 insertions(+), 5 deletions(-)
create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/codemining/ZeroWidthCharactersLineContentCodeMining.java
create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/codemining/ZeroWidthCharactersLineContentCodeMiningProvider.java
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.
*
*
* - {@link #EDITOR_SHOW_LEADING_SPACES}
@@ -516,6 +517,7 @@ private AbstractDecoratedTextEditorPreferenceConstants() {
* - {@link #EDITOR_SHOW_TRAILING_TABS}
* - {@link #EDITOR_SHOW_CARRIAGE_RETURN}
* - {@link #EDITOR_SHOW_LINE_FEED}
+ * - {@link #EDITOR_SHOW_ZW_CHARACTERS}
* - {@link #EDITOR_WHITESPACE_CHARACTER_ALPHA_VALUE}
*
*
@@ -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/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..a4905cb25c99 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.20
+ */
+ 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.
*
*