diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java index 222aeac72bd..b447000d498 100644 --- a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java @@ -13,6 +13,8 @@ */ package org.eclipse.jface.text.source.inlined; +import java.util.Set; + import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.custom.StyledText; @@ -43,6 +45,16 @@ class InlinedAnnotationDrawingStrategy implements IDrawingStrategy { private final ITextViewer viewer; + 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); + public InlinedAnnotationDrawingStrategy(ITextViewer viewer) { this.viewer = viewer; } @@ -436,13 +448,17 @@ protected static void drawAsLeftOf1stCharacter(LineContentAnnotation annotation, // Get size of the character where GlyphMetrics width is added Point charBounds= gc.stringExtent(hostCharacter); int charWidth= charBounds.x; - if (charWidth == 0 && ("\r".equals(hostCharacter) || "\n".equals(hostCharacter))) { //$NON-NLS-1$ //$NON-NLS-2$ + boolean isZeroWidthCharacter= false; + if (hostCharacter.length() == 1 && ZW_CHARACTERS.contains(hostCharacter.charAt(0))) { + isZeroWidthCharacter= true; + charWidth= 0; + } else if (charWidth == 0 && ("\r".equals(hostCharacter) || "\n".equals(hostCharacter))) { //$NON-NLS-1$ //$NON-NLS-2$ // charWidth is 0 for '\r' on font Consolas, but not on other fonts, why? charWidth= gc.stringExtent(" ").x; //$NON-NLS-1$ } // FIXME: remove this code when we need not redraw the character (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=531769) // START TO REMOVE - annotation.setRedrawnCharacterWidth(charWidth); + annotation.setRedrawnCharacterWidth(charWidth, isZeroWidthCharacter); // END TO REMOVE // Annotation takes place, add GlyphMetrics width to the style @@ -523,6 +539,11 @@ protected static void drawAsRightOfPreviousCharacter(LineContentAnnotation annot char hostCharacter= textWidget.getText(widgetOffset - 1, widgetOffset - 1).charAt(0); // use gc.stringExtent instead of gc.geCharWidth because of bug 548866 int redrawnCharacterWidth= hostCharacter != '\t' ? gc.stringExtent(Character.toString(hostCharacter)).x : textWidget.getTabs() * gc.stringExtent(" ").x; //$NON-NLS-1$ + boolean isZeroWidthCharacter= false; + if (ZW_CHARACTERS.contains(hostCharacter)) { + redrawnCharacterWidth= 0; + isZeroWidthCharacter= true; + } Rectangle charBounds= textWidget.getTextBounds(widgetOffset - 1, widgetOffset - 1); Rectangle annotationBounds= new Rectangle(charBounds.x + redrawnCharacterWidth, charBounds.y, annotation.getWidth(), charBounds.height); @@ -539,7 +560,7 @@ protected static void drawAsRightOfPreviousCharacter(LineContentAnnotation annot if (width != 0) { // FIXME: remove this code when we need not redraw the character (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=531769) // START TO REMOVE - annotation.setRedrawnCharacterWidth(redrawnCharacterWidth); + annotation.setRedrawnCharacterWidth(redrawnCharacterWidth, isZeroWidthCharacter); // END TO REMOVE // Annotation takes place, add GlyphMetrics width to the style diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java index cc22e63395e..cf4b3c5302c 100644 --- a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java @@ -44,6 +44,8 @@ public class LineContentAnnotation extends AbstractInlinedAnnotation { private int redrawnCharacterWidth; + private boolean isZeroWidthCharacter; + /** * Line content annotation constructor. * @@ -113,8 +115,9 @@ int getRedrawnCharacterWidth() { return redrawnCharacterWidth; } - void setRedrawnCharacterWidth(int redrawnCharacterWidth) { + void setRedrawnCharacterWidth(int redrawnCharacterWidth, boolean isZeroWidthCharacter) { this.redrawnCharacterWidth= redrawnCharacterWidth; + this.isZeroWidthCharacter= isZeroWidthCharacter; } @Override @@ -151,7 +154,7 @@ StyleRange updateStyle(StyleRange style, FontMetrics fontMetrics, ITextViewer vi if (!afterPosition) { usePreviousChar= drawRightToPreviousChar(widgetPosition.getOffset(), textWidget); } - if (width == 0 || getRedrawnCharacterWidth() == 0) { + if (isZeroWidthCharacter == false && (width == 0 || getRedrawnCharacterWidth() == 0)) { return null; } int fullWidth= width + getRedrawnCharacterWidth(); diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java index e1012a411c2..0cb6bfdbfa6 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java @@ -27,7 +27,6 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.TestWatcher; @@ -512,6 +511,53 @@ protected boolean condition() { }.waitForCondition(widget.getDisplay(), 1000), "Code mining is unexpectedly rendered below last line"); } + @Test + public void testCodeMiningOnZeroWitdhCharacterAfterPosition() { + codeMiningOnZeroWidthCharacter(true); + } + + @Test + public void testCodeMiningOnZeroWidthCharacterBeforePosition() { + codeMiningOnZeroWidthCharacter(false); + } + + private void codeMiningOnZeroWidthCharacter(boolean afterPosition) { + char ZW_SPACE= '\u200b'; + String text= "a" + ZW_SPACE + "b"; + fViewer.getDocument().set(text); + fViewer.setCodeMiningProviders(new ICodeMiningProvider[] { + new AbstractCodeMiningProvider() { + @Override + public CompletableFuture> provideCodeMinings(ITextViewer viewer, IProgressMonitor monitor) { + List result= new ArrayList<>(); + result.add(new LineContentCodeMining(new Position(1, 1), afterPosition, this) { + @Override + public String getLabel() { + return "ZWSP"; + } + }); + return CompletableFuture.completedFuture(result); + } + } + }); + fViewer.updateCodeMinings(); + Assertions.assertTrue(new DisplayHelper() { + @Override + protected boolean condition() { + var tw= fViewer.getTextWidget(); + int off= afterPosition ? 1 : 0; + StyleRange styleRange= tw.getStyleRangeAtOffset(off); + if (styleRange != null && styleRange.metrics != null) { + // code mining applied a style range with metrics at offset 1 + return true; + } else { + tw.redraw(); + } + return false; + } + }.waitForCondition(fViewer.getTextWidget().getDisplay(), 1000), "Line content code mining not rendered at zero width character"); + } + @Test public void testCodeMiningSwitchingBetweenInLineAndLineHeader() { String ref= "REF-X";