From 46e6ea38647f0c4937d46b7d035c31a8520195ee Mon Sep 17 00:00:00 2001 From: Tobias Melcher Date: Mon, 30 Sep 2024 11:34:25 +0200 Subject: [PATCH] additional constructor param afterPosition in LineContentCodeMining which allows to render a code mining where the cursor selection does not include the code mining at the given source position. --- .../CodeMiningLineContentAnnotation.java | 29 +++++++++++ .../text/codemining/CodeMiningManager.java | 10 +++- .../text/WhitespaceCharacterPainter.java | 45 ++++++++++++---- .../codemining/LineContentCodeMining.java | 36 +++++++++++++ .../InlinedAnnotationDrawingStrategy.java | 35 +++++++++++-- .../source/inlined/LineContentAnnotation.java | 2 +- .../examples/codemining/CodeMiningDemo.java | 18 +++++-- ...ontentCodeMiningAfterPositionProvider.java | 52 +++++++++++++++++++ .../tests/TestWhitespaceCharacterPainter.java | 36 +++++++++++-- .../source/inlined/AnnotationOnTabTest.java | 5 +- 10 files changed, 242 insertions(+), 26 deletions(-) create mode 100644 examples/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/LineContentCodeMiningAfterPositionProvider.java diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningLineContentAnnotation.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningLineContentAnnotation.java index df30b611bf0..bf80f6db9a8 100644 --- a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningLineContentAnnotation.java +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningLineContentAnnotation.java @@ -61,6 +61,8 @@ public class CodeMiningLineContentAnnotation extends LineContentAnnotation imple */ private IProgressMonitor fMonitor; + private final boolean afterPosition; + /** * Code mining annotation constructor. * @@ -72,6 +74,21 @@ public CodeMiningLineContentAnnotation(Position position, ISourceViewer viewer) fResolvedMinings= null; fMinings= new ArrayList<>(); fBounds= new ArrayList<>(); + afterPosition= false; + } + + /** + * Code mining annotation constructor. + * + * @param position the position + * @param viewer the viewer + */ + public CodeMiningLineContentAnnotation(Position position, ISourceViewer viewer, boolean afterPosition) { + super(position, viewer); + fResolvedMinings= null; + fMinings= new ArrayList<>(); + fBounds= new ArrayList<>(); + this.afterPosition= afterPosition; } @Override @@ -183,4 +200,16 @@ public Consumer getAction(MouseEvent e) { public boolean isInVisibleLines() { return super.isInVisibleLines(); } + + public final boolean isAfterPosition() { + return afterPosition; + } + + @Override + protected boolean drawRightToPreviousChar(int widgetOffset, StyledText textWidget) { + if (isAfterPosition()) { + return false; + } + return super.drawRightToPreviousChar(widgetOffset, textWidget); + } } diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningManager.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningManager.java index d9863b03261..2793d1447af 100644 --- a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningManager.java +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningManager.java @@ -42,6 +42,7 @@ import org.eclipse.jface.text.Position; import org.eclipse.jface.text.codemining.ICodeMining; import org.eclipse.jface.text.codemining.ICodeMiningProvider; +import org.eclipse.jface.text.codemining.LineContentCodeMining; import org.eclipse.jface.text.codemining.LineHeaderCodeMining; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.inlined.AbstractInlinedAnnotation; @@ -251,12 +252,17 @@ private void renderCodeMinings(Map> groups, ISourceV Position pos= new Position(g.getKey().offset, g.getKey().length); List minings= g.getValue(); - boolean inLineHeader= !minings.isEmpty() ? (minings.get(0) instanceof LineHeaderCodeMining) : true; + ICodeMining first= minings.get(0); + boolean inLineHeader= !minings.isEmpty() ? (first instanceof LineHeaderCodeMining) : true; // Try to find existing annotation AbstractInlinedAnnotation ann= fInlinedAnnotationSupport.findExistingAnnotation(pos); if (ann == null) { // The annotation doesn't exists, create it. - ann= inLineHeader ? new CodeMiningLineHeaderAnnotation(pos, viewer) : new CodeMiningLineContentAnnotation(pos, viewer); + boolean afterPosition= false; + if (first instanceof LineContentCodeMining m) { + afterPosition= m.isAfterPosition(); + } + ann= inLineHeader ? new CodeMiningLineHeaderAnnotation(pos, viewer) : new CodeMiningLineContentAnnotation(pos, viewer, afterPosition); } else if (ann instanceof ICodeMiningAnnotation && ((ICodeMiningAnnotation) ann).isInVisibleLines()) { // annotation is in visible lines annotationsToRedraw.add((ICodeMiningAnnotation) ann); diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/WhitespaceCharacterPainter.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/WhitespaceCharacterPainter.java index 24a6526f064..43c6e53e7c5 100644 --- a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/WhitespaceCharacterPainter.java +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/WhitespaceCharacterPainter.java @@ -17,8 +17,8 @@ *******************************************************************************/ package org.eclipse.jface.text; -import java.util.HashSet; -import java.util.Set; +import java.util.HashMap; +import java.util.Map; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; @@ -405,6 +405,10 @@ private void drawCharRange(GC gc, int startOffset, int endOffset, int lineOffset break; case '\r': if (fShowCarriageReturn) { + if (visibleChar.length() > 0 && cache.contains(fTextWidget, lineOffset + textOffset)) { + textOffset--; + break; + } visibleChar.append(CARRIAGE_RETURN_SIGN); } if (textOffset >= endOffsetInLine - 1 || lineText.charAt(textOffset + 1) != '\n') { @@ -414,6 +418,10 @@ private void drawCharRange(GC gc, int startOffset, int endOffset, int lineOffset continue; case '\n': if (fShowLineFeed) { + if (visibleChar.length() > 0 && cache.contains(fTextWidget, lineOffset + textOffset)) { + textOffset--; + break; + } visibleChar.append(LINE_FEED_SIGN); } eol= true; @@ -439,7 +447,7 @@ private void drawCharRange(GC gc, int startOffset, int endOffset, int lineOffset fg= styleRange.foreground; } } - draw(gc, widgetOffset, visibleChar.toString(), fg); + draw(gc, widgetOffset, visibleChar.toString(), fg, cache); } visibleChar.delete(0, visibleChar.length()); } @@ -492,7 +500,7 @@ private void redrawAll() { * @param s the string to be drawn * @param fg the foreground color */ - private void draw(GC gc, int offset, String s, Color fg) { + private void draw(GC gc, int offset, String s, Color fg,StyleRangeWithMetricsOffsets cache) { // Compute baseline delta (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=165640) int baseline= fTextWidget.getBaseline(offset); FontMetrics fontMetrics= gc.getFontMetrics(); @@ -500,32 +508,49 @@ private void draw(GC gc, int offset, String s, Color fg) { int baslineDelta= baseline - fontBaseline; Point pos= fTextWidget.getLocationAtOffset(offset); + StyleRange styleRange= cache.get(fTextWidget, offset); + if (styleRange != null && styleRange.metrics != null) { // code mining at \r or \n character - line break character should be drawn at end of code mining + String charBeforeOffset= " "; //$NON-NLS-1$ + if (offset > 0) { + charBeforeOffset= fTextWidget.getText(offset - 1, offset - 1); + } + Point extCharBeforeOffset= gc.textExtent(charBeforeOffset); + pos.x= pos.x + styleRange.metrics.width - extCharBeforeOffset.x; + } gc.setForeground(fg); gc.drawString(s, pos.x, pos.y + baslineDelta, true); } private static class StyleRangeWithMetricsOffsets { - private Set offsets= null; + private Map offsets= null; public boolean contains(StyledText st, int offset) { if (offsets == null) { - fillSet(st); + fillMap(st); } - if (offsets.contains(offset)) { + if (offsets.containsKey(offset)) { return true; } return false; } - private void fillSet(StyledText st) { - offsets= new HashSet<>(); + public StyleRange get(StyledText st, int offset) { + if (offsets == null) { + fillMap(st); + } + StyleRange styleRange= offsets.get(offset); + return styleRange; + } + + private void fillMap(StyledText st) { + offsets= new HashMap<>(); StyleRange[] ranges= st.getStyleRanges(); if (ranges == null) { return; } for (StyleRange range : ranges) { if (range != null && range.metrics != null) { - offsets.add(range.start); + offsets.put(range.start, range); } } } diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/LineContentCodeMining.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/LineContentCodeMining.java index d81cd0b1e0e..98a8d566449 100644 --- a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/LineContentCodeMining.java +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/LineContentCodeMining.java @@ -26,6 +26,12 @@ */ public abstract class LineContentCodeMining extends AbstractCodeMining { + /** + * indicates if code mining should be rendered after given position; cursor and selection does + * not include the code mining if set to true. + */ + protected boolean afterPosition= false; + /** * CodeMining constructor to locate the code mining in a given position. * @@ -36,6 +42,18 @@ public LineContentCodeMining(Position position, ICodeMiningProvider provider) { this(position, provider, null); } + /** + * CodeMining constructor to locate the code mining in a given position. + * + * @param position the position where the mining must be drawn. + * @param afterPosition if true code mining is treated as suffix code mining where cursor and + * selection is not including the mining + * @param provider the owner codemining provider which creates this mining. + */ + public LineContentCodeMining(Position position, boolean afterPosition, ICodeMiningProvider provider) { + this(position, afterPosition, provider, null); + } + /** * CodeMining constructor to locate the code mining in a given position. * @@ -44,7 +62,25 @@ public LineContentCodeMining(Position position, ICodeMiningProvider provider) { * @param action the action to execute when mining is clicked and null otherwise. */ public LineContentCodeMining(Position position, ICodeMiningProvider provider, Consumer action) { + this(position, false, provider, action); + } + + /** + * CodeMining constructor to locate the code mining in a given position. + * + * @param position the position where the mining must be drawn. + * @param provider the owner codemining provider which creates this mining. + * @param action the action to execute when mining is clicked and null otherwise. + * @param afterPosition if true code mining is treated as suffix code mining where cursor and + * selection is not including the mining + */ + public LineContentCodeMining(Position position, boolean afterPosition, ICodeMiningProvider provider, Consumer action) { super(position, provider, action); + this.afterPosition= afterPosition; + } + + public boolean isAfterPosition() { + return afterPosition; } } 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 09464aef653..8ae0d33f3ef 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 @@ -24,6 +24,8 @@ import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.jface.internal.text.codemining.CodeMiningLineContentAnnotation; + import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.AnnotationPainter.IDrawingStrategy; @@ -202,9 +204,15 @@ private static void draw(LineHeaderAnnotation annotation, GC gc, StyledText text */ private static void draw(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) { + if (annotation instanceof CodeMiningLineContentAnnotation a) { + if (a.isAfterPosition()) { + drawAsLeftOf1stCharacter(annotation, gc, textWidget, widgetOffset, length, color); + return; + } + } if (annotation.isEmptyLine(widgetOffset, textWidget)) { drawAfterLine(annotation, gc, textWidget, widgetOffset, length, color); - } else if (LineContentAnnotation.drawRightToPreviousChar(widgetOffset, textWidget)) { + } else if (annotation.drawRightToPreviousChar(widgetOffset, textWidget)) { drawAsRightOfPreviousCharacter(annotation, gc, textWidget, widgetOffset, length, color); } else { drawAsLeftOf1stCharacter(annotation, gc, textWidget, widgetOffset, length, color); @@ -254,9 +262,18 @@ protected static void drawAsLeftOf1stCharacter(LineContentAnnotation annotation, // Compute the location of the annotation Rectangle bounds= textWidget.getTextBounds(widgetOffset, widgetOffset); - int x= bounds.x + (isEndOfLine ? bounds.width * 2 : 0); - int y= bounds.y; + int x; + if (isEndOfLine) { + // getTextBounds at offset with char '\r' or '\n' returns incorrect x position, use getLocationAtOffset instead + x= textWidget.getLocationAtOffset(widgetOffset).x; + } else { + x= bounds.x; + } + int y= bounds.y; + if (isAfterPosition(annotation)) { + isEndOfLine= false; + } // When line text has line header annotation, there is a space on the top, adjust the y by using char height y+= bounds.height - textWidget.getLineHeight(); @@ -275,7 +292,10 @@ 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$ + // 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); @@ -328,6 +348,13 @@ protected static void drawAsLeftOf1stCharacter(LineContentAnnotation annotation, } } + private static boolean isAfterPosition(LineContentAnnotation annotation) { + if (annotation instanceof CodeMiningLineContentAnnotation a) { + return a.isAfterPosition(); + } + return false; + } + protected static void drawAsRightOfPreviousCharacter(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) { StyleRange style= null; try { 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 e6cc355c90f..86b048c47ee 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 @@ -158,7 +158,7 @@ StyleRange updateStyle(StyleRange style, FontMetrics fontMetrics, ITextViewer vi return style; } - static boolean drawRightToPreviousChar(int widgetOffset, StyledText textWidget) { + protected boolean drawRightToPreviousChar(int widgetOffset, StyledText textWidget) { return widgetOffset > 0 && widgetOffset < textWidget.getCharCount() && textWidget.getLineAtOffset(widgetOffset) == textWidget.getLineAtOffset(widgetOffset - 1); } diff --git a/examples/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/CodeMiningDemo.java b/examples/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/CodeMiningDemo.java index 1cc8a5cf656..b70080822bd 100644 --- a/examples/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/CodeMiningDemo.java +++ b/examples/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/CodeMiningDemo.java @@ -16,10 +16,12 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewerExtension2; +import org.eclipse.jface.text.WhitespaceCharacterPainter; import org.eclipse.jface.text.codemining.ICodeMiningProvider; import org.eclipse.jface.text.reconciler.DirtyRegion; import org.eclipse.jface.text.reconciler.IReconcilingStrategy; @@ -42,6 +44,8 @@ */ public class CodeMiningDemo { + private static boolean showWhitespaces = false; + public static void main(String[] args) throws Exception { Display display = new Display(); @@ -54,7 +58,13 @@ public static void main(String[] args) throws Exception { endOfLineText.setText(endOfLineString.get()); GridDataFactory.fillDefaults().grab(true, false).applyTo(endOfLineText); - ISourceViewer sourceViewer = new SourceViewer(shell, null, SWT.V_SCROLL | SWT.BORDER); + SourceViewer sourceViewer = new SourceViewer(shell, null, SWT.V_SCROLL | SWT.BORDER); + sourceViewer.getTextWidget().setFont(JFaceResources.getTextFont()); + if (showWhitespaces) { + WhitespaceCharacterPainter whitespaceCharPainter = new WhitespaceCharacterPainter(sourceViewer, true, true, + true, true, true, true, true, true, true, true, true, 100); + sourceViewer.addPainter(whitespaceCharPainter); + } sourceViewer.setDocument( new Document("// Type class & new keyword and see references CodeMining\n" + "// Name class with a number N to emulate Nms before resolving the references CodeMining\n" @@ -71,7 +81,8 @@ public static void main(String[] args) throws Exception { + "new 5\n" // + "new 5\n" // + "multiline \n" // - + "multiline \n\n"), + + "multiline \n\n" // + + "suffix \n"), new AnnotationModel()); GridDataFactory.fillDefaults().grab(true, true).applyTo(sourceViewer.getTextWidget()); // Add AnnotationPainter (required by CodeMining) @@ -83,7 +94,8 @@ public static void main(String[] args) throws Exception { new ToEchoWithHeaderAndInlineCodeMiningProvider("echo"), // new MultilineCodeMiningProvider(), // new EmptyLineCodeMiningProvider(), // - new EchoAtEndOfLineCodeMiningProvider(endOfLineString) }); + new EchoAtEndOfLineCodeMiningProvider(endOfLineString), // + new LineContentCodeMiningAfterPositionProvider() }); // Execute codemining in a reconciler MonoReconciler reconciler = new MonoReconciler(new IReconcilingStrategy() { diff --git a/examples/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/LineContentCodeMiningAfterPositionProvider.java b/examples/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/LineContentCodeMiningAfterPositionProvider.java new file mode 100644 index 00000000000..e6bf0bb646f --- /dev/null +++ b/examples/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/LineContentCodeMiningAfterPositionProvider.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2024, SAP SE + * + * 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 + *******************************************************************************/ +package org.eclipse.jface.text.examples.codemining; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider; +import org.eclipse.jface.text.codemining.ICodeMining; +import org.eclipse.jface.text.codemining.LineContentCodeMining; + +public class LineContentCodeMiningAfterPositionProvider extends AbstractCodeMiningProvider { + + public LineContentCodeMiningAfterPositionProvider() { + } + + @Override + public CompletableFuture> provideCodeMinings(ITextViewer viewer, + IProgressMonitor monitor) { + String suffix = "suffix"; + int index = 0; + List res = new ArrayList<>(); + while ((index = viewer.getDocument().get().indexOf(suffix, index)) != -1) { + index += suffix.length(); + res.add(new LineContentCodeMining(new Position(index, 1), true, this) { + @Override + public String getLabel() { + return suffix; + } + + @Override + public boolean isResolved() { + return true; + } + }); + } + return CompletableFuture.completedFuture(res); + } +} + diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TestWhitespaceCharacterPainter.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TestWhitespaceCharacterPainter.java index 5b3298d050d..9a1d57a3e34 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TestWhitespaceCharacterPainter.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TestWhitespaceCharacterPainter.java @@ -19,6 +19,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.Arrays; +import java.util.List; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -62,15 +65,30 @@ public void after() { @Test public void glyphMetricsTakenIntoAccount() throws Exception { + verifyDrawStringCalledNTimes("first \nsecond \nthird \n", Arrays.asList(6, 15), 5); + } + + @Test + public void glyphMetricsAtNewTakenIntoAccount() throws Exception { + verifyDrawStringCalledNTimes("first \nsecond", Arrays.asList(7), 2); + } + + @Test + public void glyphMetricsAtCarriageReturnTakenIntoAccount() throws Exception { + verifyDrawStringCalledNTimes("first \r\nsecond", Arrays.asList(7), 2); + } + + private void verifyDrawStringCalledNTimes(String str, List styleRangeOffsets, int times) { SourceViewer sourceViewer= new SourceViewer(shell, null, SWT.V_SCROLL | SWT.BORDER); - sourceViewer.setDocument(new Document("first \nsecond \nthird \n")); + sourceViewer.setDocument(new Document(str)); StyledText textWidget= sourceViewer.getTextWidget(); textWidget.setFont(JFaceResources.getTextFont()); WhitespaceCharacterPainter whitespaceCharPainter= new WhitespaceCharacterPainter(sourceViewer, true, true, true, true, true, true, true, true, true, true, true, 100); sourceViewer.addPainter(whitespaceCharPainter); - textWidget.setStyleRange(createStyleRangeWithMetrics(6)); - textWidget.setStyleRange(createStyleRangeWithMetrics(15)); + for (Integer offset : styleRangeOffsets) { + textWidget.setStyleRange(createStyleRangeWithMetrics(offset)); + } Event e= new Event(); e.widget= textWidget; PaintEvent ev= new PaintEvent(e); @@ -87,6 +105,16 @@ public Point answer(InvocationOnMock invocation) throws Throwable { return result; } }); + when(ev.gc.textExtent(anyString())).thenAnswer(new Answer() { + @Override + public Point answer(InvocationOnMock invocation) throws Throwable { + GC gc= new GC(shell); + gc.setFont(JFaceResources.getTextFont()); + Point result= gc.textExtent(invocation.getArgument(0)); + gc.dispose(); + return result; + } + }); when(ev.gc.getFontMetrics()).thenAnswer(new Answer() { @Override public FontMetrics answer(InvocationOnMock invocation) throws Throwable { @@ -102,7 +130,7 @@ public FontMetrics answer(InvocationOnMock invocation) throws Throwable { ev.width= 100; ev.height= 100; whitespaceCharPainter.paintControl(ev); - verify(ev.gc, times(5)).drawString(anyString(), anyInt(), anyInt(), anyBoolean()); + verify(ev.gc, times(times)).drawString(anyString(), anyInt(), anyInt(), anyBoolean()); } private StyleRange createStyleRangeWithMetrics(int start) { diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/AnnotationOnTabTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/AnnotationOnTabTest.java index 4cf6795c238..279a6fb8ccd 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/AnnotationOnTabTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/AnnotationOnTabTest.java @@ -74,7 +74,7 @@ public void testTextBoundsMatchPaintedArea() { // add annotations int annotationIndex = sourceViewer.getDocument().get().indexOf("annotated"); LineContentAnnotation annotation= new LineContentAnnotation(new Position(annotationIndex, 1), sourceViewer); - annotation.setText("a"); // single char, so overall annoation is 3 chars, less than default 4 chars + annotation.setText("a"); // single char, so overall annotation is 3 chars, less than default 4 chars support.updateAnnotations(Collections.singleton(annotation)); fParent.open(); Assert.assertTrue(new DisplayHelper() { @@ -87,6 +87,7 @@ protected boolean condition() { int referenceIndex = textWidget.getText().indexOf("reference"); Rectangle referenceBounds = textWidget.getTextBounds(referenceIndex, referenceIndex); Rectangle annotatedCharactedBounds = textWidget.getTextBounds(annotationIndex, annotationIndex); - Assert.assertTrue("Annotation didn't shift target character to the right, it most likely replaced the tab instead of expanding it", referenceBounds.x < annotatedCharactedBounds.x); + Assert.assertTrue("Annotation didn't shift target character to the right, it most likely replaced the tab instead of expanding it", + referenceBounds.x + referenceBounds.width < annotatedCharactedBounds.x + annotatedCharactedBounds.width); } }