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 ba3cbf1c7db..2f9424847d5 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
@@ -28,6 +28,7 @@
import org.eclipse.jface.internal.text.codemining.CodeMiningLineHeaderAnnotation;
import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.AnnotationPainter.IDrawingStrategy;
@@ -99,7 +100,7 @@ private Font getAnnotationFont(StyledText textWidget) {
if (annotationFont == null) {
annotationFont = createInlineAnnotationFont(textWidget);
textWidget.setData(INLINE_ANNOTATION_FONT, annotationFont);
- textWidget.addDisposeListener(e -> ((Font)textWidget.getData(INLINE_ANNOTATION_FONT)).dispose());
+ textWidget.addDisposeListener(e -> ((Font) textWidget.getData(INLINE_ANNOTATION_FONT)).dispose());
}
return annotationFont;
}
@@ -154,7 +155,8 @@ public static void draw(AbstractInlinedAnnotation annotation, GC gc, StyledText
private static void draw(LineHeaderAnnotation annotation, GC gc, StyledText textWidget, int offset, int length,
Color color) {
int line= textWidget.getLineAtOffset(offset);
- if (isDeleted(annotation)) {
+ int charCount= textWidget.getCharCount();
+ if (isDeleted(annotation, charCount)) {
// When annotation is deleted, update metrics to null to remove extra spaces of the line header annotation.
if (textWidget.getLineVerticalIndent(line) > 0)
textWidget.setLineVerticalIndent(line, 0);
@@ -180,9 +182,16 @@ private static void draw(LineHeaderAnnotation annotation, GC gc, StyledText text
textWidget.setLineVerticalIndent(line, 0);
}
// Compute the location of the annotation
- Rectangle bounds= textWidget.getTextBounds(offset, offset);
- int x= bounds.x;
- int y= bounds.y;
+ int x, y;
+ if (offset < charCount) {
+ Rectangle bounds= textWidget.getTextBounds(offset, offset);
+ x= bounds.x;
+ y= bounds.y;
+ } else {
+ Point locAtOff= textWidget.getLocationAtOffset(offset);
+ x= locAtOff.x;
+ y= locAtOff.y - height;
+ }
// Draw the line header annotation
gc.setBackground(textWidget.getBackground());
annotation.setLocation(x, y);
@@ -193,7 +202,16 @@ private static void draw(LineHeaderAnnotation annotation, GC gc, StyledText text
Rectangle client= textWidget.getClientArea();
textWidget.redraw(0, bounds.y, client.width, bounds.height, false);
} else {
- textWidget.redrawRange(offset, length, true);
+ if (offset >= charCount) {
+ if (charCount > 0) {
+ textWidget.redrawRange(charCount - 1, 1, true);
+ } else {
+ Rectangle client= textWidget.getClientArea();
+ textWidget.redraw(0, 0, client.width, client.height, false);
+ }
+ } else {
+ textWidget.redrawRange(offset, length, true);
+ }
}
}
@@ -212,7 +230,11 @@ private static void draw(LineContentAnnotation annotation, GC gc, StyledText tex
Color color) {
if (annotation instanceof CodeMiningLineContentAnnotation a) {
if (a.isAfterPosition()) {
- drawAsLeftOf1stCharacter(annotation, gc, textWidget, widgetOffset, length, color);
+ if (widgetOffset < textWidget.getCharCount()) {
+ drawAsLeftOf1stCharacter(annotation, gc, textWidget, widgetOffset, length, color);
+ } else {
+ drawAtEndOfDocumentInFirstColumn(annotation, gc, textWidget, widgetOffset, length, color);
+ }
return;
}
}
@@ -226,7 +248,7 @@ private static void draw(LineContentAnnotation annotation, GC gc, StyledText tex
}
private static void drawAfterLine(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) {
- if (isDeleted(annotation)) {
+ if (isDeleted(annotation, textWidget.getCharCount())) {
return;
}
if (gc != null) {
@@ -247,6 +269,35 @@ private static void drawAfterLine(LineContentAnnotation annotation, GC gc, Style
}
}
+ private static void drawAtEndOfDocumentInFirstColumn(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) {
+ if (isDeleted(annotation, textWidget.getCharCount())) {
+ return;
+ }
+ if (gc != null) {
+ Point locAtOff= textWidget.getLocationAtOffset(widgetOffset);
+ int x= locAtOff.x;
+ int y= locAtOff.y;
+ annotation.setLocation(x, y);
+ annotation.draw(gc, textWidget, widgetOffset, length, color, x, y);
+ int width= annotation.getWidth();
+ if (width != 0) {
+ if (!gc.getClipping().contains(x, y)) {
+ Rectangle client= textWidget.getClientArea();
+ int height= textWidget.getLineHeight();
+ textWidget.redraw(x, y, client.width, height, false);
+ }
+ }
+ } else {
+ int charCount= textWidget.getCharCount();
+ if (charCount > 0) {
+ textWidget.redrawRange(charCount - 1, 1, true);
+ } else {
+ Rectangle client= textWidget.getClientArea();
+ textWidget.redraw(0, 0, client.width, client.height, false);
+ }
+ }
+ }
+
protected static void drawAsLeftOf1stCharacter(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) {
StyleRange style= null;
try {
@@ -254,7 +305,7 @@ protected static void drawAsLeftOf1stCharacter(LineContentAnnotation annotation,
} catch (Exception e) {
return;
}
- if (isDeleted(annotation)) {
+ if (isDeleted(annotation, textWidget.getCharCount())) {
// When annotation is deleted, update metrics to null to remove extra spaces of the line content annotation.
if (style != null && style.metrics != null) {
style.metrics= null;
@@ -369,7 +420,7 @@ protected static void drawAsRightOfPreviousCharacter(LineContentAnnotation annot
} catch (Exception e) {
return;
}
- if (isDeleted(annotation)) {
+ if (isDeleted(annotation, textWidget.getCharCount())) {
// When annotation is deleted, update metrics to null to remove extra spaces of the line content annotation.
if (style != null && style.metrics != null) {
style.metrics= null;
@@ -449,7 +500,17 @@ protected static void drawAsRightOfPreviousCharacter(LineContentAnnotation annot
* @param annotation the inlined annotation to check
* @return true if inlined annotation is deleted and false otherwise.
*/
- private static boolean isDeleted(AbstractInlinedAnnotation annotation) {
- return annotation.isMarkedDeleted() || annotation.getPosition().isDeleted() || annotation.getPosition().getLength() == 0;
+ private static boolean isDeleted(AbstractInlinedAnnotation annotation,int maxOffset) {
+ if (annotation.isMarkedDeleted()) {
+ return true;
+ }
+ Position pos= annotation.getPosition();
+ if (pos.isDeleted()) {
+ return true;
+ }
+ if (pos.getLength() == 0 && pos.getOffset() < maxOffset) {
+ return true;
+ }
+ return false;
}
}
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 b5558b2ff6e..d2442eb1c5f 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
@@ -35,6 +35,7 @@
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
@@ -250,6 +251,71 @@ protected boolean condition() {
}.waitForCondition(fViewer.getTextWidget().getDisplay(), 1000));
}
+ @Test
+ public void testLineHeaderCodeMiningAtEndOfDocumentWithEmptyLine() throws Exception {
+ String source= "first\nsecond\n";
+ fViewer.getDocument().set(source);
+ fViewer.setCodeMiningProviders(new ICodeMiningProvider[] { new ICodeMiningProvider() {
+ @Override
+ public CompletableFuture> provideCodeMinings(ITextViewer viewer, IProgressMonitor monitor) {
+ List minings= new ArrayList<>();
+ try {
+ minings.add(new LineHeaderCodeMining(new Position(source.length(), 0), this, null) {
+ @Override
+ public String getLabel() {
+ return "multiline first line\nmultiline second line\nmultiline third line\nmultiline fourth line";
+ }
+ });
+ } catch (BadLocationException e) {
+ e.printStackTrace();
+ }
+ return CompletableFuture.completedFuture(minings);
+ }
+
+ @Override
+ public void dispose() {
+ }
+ } });
+ Assert.assertTrue("Code mining is not visible at end of document", new DisplayHelper() {
+ @Override
+ protected boolean condition() {
+ try {
+ return hasCodeMiningPrintedAfterTextOnLine(fViewer, 2);
+ } catch (BadLocationException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+ }.waitForCondition(fViewer.getTextWidget().getDisplay(), 10_000));
+ }
+
+ @Test
+ public void testCodeMiningAtEndOfDocumentWithEmptyLine() throws Exception {
+ String source= "first\nsecond\n";
+ fViewer.getDocument().set(source);
+ fViewer.setCodeMiningProviders(new ICodeMiningProvider[] { new ICodeMiningProvider() {
+ @Override
+ public CompletableFuture> provideCodeMinings(ITextViewer viewer, IProgressMonitor monitor) {
+ return CompletableFuture.completedFuture(Collections.singletonList(new StaticContentLineCodeMining(new Position(source.length(), 0), true, "mining", this)));
+ }
+
+ @Override
+ public void dispose() {
+ }
+ } });
+ Assert.assertTrue("Code mining is not visible at end of document", new DisplayHelper() {
+ @Override
+ protected boolean condition() {
+ try {
+ return hasCodeMiningPrintedAfterTextOnLine(fViewer, 2);
+ } catch (BadLocationException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+ }.waitForCondition(fViewer.getTextWidget().getDisplay(), 10_000));
+ }
+
@Test
public void testCodeMiningEndOfLine() {
fViewer.getDocument().set("a\n");
@@ -389,7 +455,18 @@ private static boolean hasCodeMiningPrintedAfterTextOnLine(ITextViewer viewer, i
if (lineLength < 0) {
lineLength= 0;
}
- Rectangle secondLineBounds= widget.getTextBounds(document.getLineOffset(line), document.getLineOffset(line) + lineLength);
+ Rectangle secondLineBounds= null;
+ int lineOffset= document.getLineOffset(line);
+ if (lineOffset >= document.getLength()) {
+ int off= document.getLength() - 1;
+ secondLineBounds= widget.getTextBounds(off, off + lineLength);
+ Point l= widget.getLocationAtOffset(lineOffset);
+ int lineVerticalIndent= widget.getLineVerticalIndent(line);
+ secondLineBounds.x= l.x;
+ secondLineBounds.y= l.y - lineVerticalIndent;
+ } else {
+ secondLineBounds= widget.getTextBounds(lineOffset, lineOffset + lineLength);
+ }
Image image = new Image(widget.getDisplay(), widget.getSize().x, widget.getSize().y);
GC gc = new GC(widget);
gc.copyArea(image, 0, 0);
diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/StaticContentLineCodeMining.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/StaticContentLineCodeMining.java
index 726b7a698ca..f44ca28dd22 100644
--- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/StaticContentLineCodeMining.java
+++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/StaticContentLineCodeMining.java
@@ -24,6 +24,11 @@ public StaticContentLineCodeMining(Position position, String message, ICodeMinin
setLabel(message);
}
+ public StaticContentLineCodeMining(Position position, boolean afterPosition, String message, ICodeMiningProvider provider) {
+ super(position, afterPosition, provider);
+ setLabel(message);
+ }
+
public StaticContentLineCodeMining(int i, char c, ICodeMiningProvider repeatLettersCodeMiningProvider) {
super(new Position(i, 1), repeatLettersCodeMiningProvider);
setLabel(Character.toString(c));