Skip to content

Commit ee768a5

Browse files
introduce DocumentFooterCodeMining to draw minings at the document end
1 parent 033cedf commit ee768a5

File tree

5 files changed

+185
-9
lines changed

5 files changed

+185
-9
lines changed

bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningLineHeaderAnnotation.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ public class CodeMiningLineHeaderAnnotation extends LineHeaderAnnotation impleme
6262
*/
6363
private IProgressMonitor fMonitor;
6464

65+
/**
66+
* Indicates whether the code mining line annotation should be rendered at the very end of the
67+
* document
68+
*/
69+
private final boolean isDocumentFooterCodeMining;
70+
6571
/**
6672
* Code mining annotation constructor.
6773
*
@@ -84,10 +90,29 @@ public CodeMiningLineHeaderAnnotation(Position position, ISourceViewer viewer) {
8490
* @param onMouseMove the consumer to be called on mouse move
8591
*/
8692
public CodeMiningLineHeaderAnnotation(Position position, ISourceViewer viewer, Consumer<MouseEvent> onMouseHover, Consumer<MouseEvent> onMouseOut, Consumer<MouseEvent> onMouseMove) {
93+
this(position, viewer, onMouseHover, onMouseOut, onMouseMove, false);
94+
}
95+
96+
/**
97+
* Code mining annotation constructor.
98+
*
99+
* @param position the position
100+
* @param viewer the viewer
101+
* @param onMouseHover the consumer to be called on mouse hover. If set, the implementor needs
102+
* to take care of setting the cursor if wanted.
103+
* @param onMouseOut the consumer to be called on mouse out. If set, the implementor needs to
104+
* take care of resetting the cursor.
105+
* @param onMouseMove the consumer to be called on mouse move
106+
* @param isDocumentFooterCodeMining <code>true</code> if the annotation is at the beginning of
107+
* the last line
108+
*/
109+
public CodeMiningLineHeaderAnnotation(Position position, ISourceViewer viewer, Consumer<MouseEvent> onMouseHover, Consumer<MouseEvent> onMouseOut, Consumer<MouseEvent> onMouseMove,
110+
boolean isDocumentFooterCodeMining) {
87111
super(position, viewer, onMouseHover, onMouseOut, onMouseMove);
88112
fResolvedMinings= null;
89113
fMinings= new ArrayList<>();
90114
fBounds= new ArrayList<>();
115+
this.isDocumentFooterCodeMining= isDocumentFooterCodeMining;
91116
}
92117

93118
@Override
@@ -282,4 +307,8 @@ public Consumer<MouseEvent> getAction(MouseEvent e) {
282307
public boolean isInVisibleLines() {
283308
return super.isInVisibleLines();
284309
}
310+
311+
public boolean isDocumentFooterCodeMining() {
312+
return isDocumentFooterCodeMining;
313+
}
285314
}

bundles/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningManager.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.eclipse.jface.text.IDocument;
4343
import org.eclipse.jface.text.ITextViewer;
4444
import org.eclipse.jface.text.Position;
45+
import org.eclipse.jface.text.codemining.DocumentFooterCodeMining;
4546
import org.eclipse.jface.text.codemining.ICodeMining;
4647
import org.eclipse.jface.text.codemining.ICodeMiningProvider;
4748
import org.eclipse.jface.text.codemining.LineContentCodeMining;
@@ -273,7 +274,7 @@ private void renderCodeMinings(Map<Position, List<ICodeMining>> groups, ISourceV
273274
mouseMove= first.getMouseMove();
274275
}
275276
ann= inLineHeader
276-
? new CodeMiningLineHeaderAnnotation(pos, viewer, mouseHover, mouseOut, mouseMove)
277+
? new CodeMiningLineHeaderAnnotation(pos, viewer, mouseHover, mouseOut, mouseMove, first instanceof DocumentFooterCodeMining)
277278
: new CodeMiningLineContentAnnotation(pos, viewer, afterPosition, mouseHover, mouseOut, mouseMove);
278279
} else if (ann instanceof ICodeMiningAnnotation && ((ICodeMiningAnnotation) ann).isInVisibleLines()) {
279280
// annotation is in visible lines
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 SAP SE
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
******************************************************************************/
11+
package org.eclipse.jface.text.codemining;
12+
13+
import java.util.function.Consumer;
14+
15+
import org.eclipse.swt.events.MouseEvent;
16+
17+
import org.eclipse.jface.text.BadLocationException;
18+
import org.eclipse.jface.text.IDocument;
19+
import org.eclipse.jface.text.Position;
20+
21+
/**
22+
* A code mining rendered at the start of the line, located at the very end of the document.
23+
*
24+
* @since 3.27
25+
*/
26+
public class DocumentFooterCodeMining extends LineHeaderCodeMining {
27+
28+
public DocumentFooterCodeMining(IDocument document, ICodeMiningProvider provider, Consumer<MouseEvent> action) throws BadLocationException {
29+
super(new Position(document.getLength(), 0), provider, action);
30+
}
31+
}

bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,11 @@ private static void draw(LineHeaderAnnotation annotation, GC gc, StyledText text
166166
// Setting vertical indent first, before computing bounds
167167
int height;
168168
if (annotation instanceof CodeMiningLineHeaderAnnotation cmlha) {
169-
height= cmlha.getHeight(gc);
169+
if (cmlha.isDocumentFooterCodeMining()) {
170+
height= 0;
171+
} else {
172+
height= cmlha.getHeight(gc);
173+
}
170174
} else {
171175
height= annotation.getHeight();
172176
}
@@ -188,9 +192,24 @@ private static void draw(LineHeaderAnnotation annotation, GC gc, StyledText text
188192
x= bounds.x;
189193
y= bounds.y;
190194
} else {
191-
Point locAtOff= textWidget.getLocationAtOffset(offset);
192-
x= locAtOff.x;
193-
y= locAtOff.y - height;
195+
if (annotation instanceof CodeMiningLineHeaderAnnotation cmlha && cmlha.isDocumentFooterCodeMining()) {
196+
int lineAtOffset= textWidget.getLineAtOffset(offset);
197+
int offsetAtBeginningOfLine= textWidget.getOffsetAtLine(lineAtOffset);
198+
if (offsetAtBeginningOfLine >= charCount) {
199+
Point locAtOff= textWidget.getLocationAtOffset(offsetAtBeginningOfLine);
200+
x= locAtOff.x;
201+
y= locAtOff.y;
202+
} else {
203+
Rectangle bounds= textWidget.getTextBounds(offsetAtBeginningOfLine, offsetAtBeginningOfLine);
204+
int lineSpacing= textWidget.getLineSpacing();
205+
x= bounds.x;
206+
y= bounds.y + bounds.height + lineSpacing;
207+
}
208+
} else {
209+
Point locAtOff= textWidget.getLocationAtOffset(offset);
210+
x= locAtOff.x;
211+
y= locAtOff.y - height;
212+
}
194213
}
195214
// Draw the line header annotation
196215
gc.setBackground(textWidget.getBackground());

tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.eclipse.jface.text.ITextViewer;
5353
import org.eclipse.jface.text.Position;
5454
import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider;
55+
import org.eclipse.jface.text.codemining.DocumentFooterCodeMining;
5556
import org.eclipse.jface.text.codemining.ICodeMining;
5657
import org.eclipse.jface.text.codemining.ICodeMiningProvider;
5758
import org.eclipse.jface.text.codemining.LineHeaderCodeMining;
@@ -289,6 +290,90 @@ protected boolean condition() {
289290
}.waitForCondition(fViewer.getTextWidget().getDisplay(), 10_000));
290291
}
291292

293+
@Test
294+
public void testFinalLineCodeMining() throws Exception {
295+
String source= "first\nsecond";
296+
fViewer.getDocument().set(source);
297+
fViewer.setCodeMiningProviders(new ICodeMiningProvider[] { new ICodeMiningProvider() {
298+
@Override
299+
public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextViewer viewer, IProgressMonitor monitor) {
300+
List<ICodeMining> minings= new ArrayList<>();
301+
try {
302+
minings.add(new DocumentFooterCodeMining(viewer.getDocument(), this, null) {
303+
@Override
304+
public String getLabel() {
305+
return "multiline first line\nmultiline second line";
306+
}
307+
});
308+
} catch (BadLocationException e) {
309+
e.printStackTrace();
310+
}
311+
return CompletableFuture.completedFuture(minings);
312+
}
313+
314+
@Override
315+
public void dispose() {
316+
}
317+
} });
318+
Assert.assertTrue("Code mining is not visible at end of document", new DisplayHelper() {
319+
@Override
320+
protected boolean condition() {
321+
try {
322+
boolean res= hasCodeMiningPrintedBelowLine(fViewer, 1);
323+
if (!res) {
324+
fViewer.getTextWidget().redraw();
325+
}
326+
return res;
327+
} catch (BadLocationException e) {
328+
e.printStackTrace();
329+
return false;
330+
}
331+
}
332+
}.waitForCondition(fViewer.getTextWidget().getDisplay(), 10_000));
333+
}
334+
335+
@Test
336+
public void testFinalLineCodeMiningEmptyDocument() throws Exception {
337+
String source= "";
338+
fViewer.getDocument().set(source);
339+
fViewer.setCodeMiningProviders(new ICodeMiningProvider[] { new ICodeMiningProvider() {
340+
@Override
341+
public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextViewer viewer, IProgressMonitor monitor) {
342+
List<ICodeMining> minings= new ArrayList<>();
343+
try {
344+
minings.add(new DocumentFooterCodeMining(viewer.getDocument(), this, null) {
345+
@Override
346+
public String getLabel() {
347+
return "multiline first line\nmultiline second line";
348+
}
349+
});
350+
} catch (BadLocationException e) {
351+
e.printStackTrace();
352+
}
353+
return CompletableFuture.completedFuture(minings);
354+
}
355+
356+
@Override
357+
public void dispose() {
358+
}
359+
} });
360+
Assert.assertTrue("Code mining is not visible at end of document", new DisplayHelper() {
361+
@Override
362+
protected boolean condition() {
363+
try {
364+
boolean res= hasCodeMiningPrintedBelowLine(fViewer, 0);
365+
if (!res) {
366+
fViewer.getTextWidget().redraw();
367+
}
368+
return res;
369+
} catch (BadLocationException e) {
370+
e.printStackTrace();
371+
return false;
372+
}
373+
}
374+
}.waitForCondition(fViewer.getTextWidget().getDisplay(), 10_000));
375+
}
376+
292377
@Test
293378
public void testCodeMiningAtEndOfDocumentWithEmptyLine() throws Exception {
294379
String source= "first\nsecond\n";
@@ -425,16 +510,27 @@ private static boolean hasCodeMiningPrintedBelowLine(ITextViewer viewer, int lin
425510
if (lineLength < 0) {
426511
lineLength= 0;
427512
}
428-
Rectangle lineBounds= widget.getTextBounds(document.getLineOffset(line), document.getLineOffset(line) + lineLength);
429-
lineBounds.y= lineBounds.y + lineBounds.height;
513+
int lineOffset= document.getLineOffset(line);
514+
int startx, starty;
515+
if (lineOffset + lineLength >= widget.getCharCount()) {
516+
Point loc= widget.getLocationAtOffset(lineOffset);
517+
startx= loc.x;
518+
starty= loc.y + widget.getLineHeight(lineOffset);
519+
} else {
520+
Rectangle lineBounds= widget.getTextBounds(lineOffset, lineOffset + lineLength);
521+
lineBounds.y= lineBounds.y + lineBounds.height;
522+
startx= lineBounds.x;
523+
starty= lineBounds.y;
524+
}
525+
430526
Image image= new Image(widget.getDisplay(), widget.getSize().x, widget.getSize().y);
431527
try {
432528
GC gc= new GC(widget);
433529
gc.copyArea(image, 0, 0);
434530
gc.dispose();
435531
ImageData imageData= image.getImageData();
436-
for (int x= lineBounds.x + 1; x < image.getBounds().width && x < imageData.width; x++) {
437-
for (int y= lineBounds.y; y < imageData.height - 10 /*do not include the border*/; y++) {
532+
for (int x= startx + 1; x < image.getBounds().width && x < imageData.width; x++) {
533+
for (int y= starty; y < imageData.height - 10 /*do not include the border*/; y++) {
438534
if (!imageData.palette.getRGB(imageData.getPixel(x, y)).equals(widget.getBackground().getRGB())) {
439535
// code mining printed
440536
return true;

0 commit comments

Comments
 (0)