Skip to content

Commit a78a32a

Browse files
tobias-melcherBeckerWdf
authored andcommitted
introduce DocumentFooterCodeMining to draw minings at the document end
1 parent 139fb8c commit a78a32a

File tree

10 files changed

+504
-62
lines changed

10 files changed

+504
-62
lines changed

bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
22
Bundle-ManifestVersion: 2
33
Bundle-Name: %pluginName
44
Bundle-SymbolicName: org.eclipse.jface.text
5-
Bundle-Version: 3.27.100.qualifier
5+
Bundle-Version: 3.28.0.qualifier
66
Bundle-Vendor: %providerName
77
Bundle-Localization: plugin
88
Export-Package:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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.internal.text.codemining;
12+
13+
import static org.eclipse.jface.internal.text.codemining.CodeMiningLineHeaderAnnotation.disposeMinings;
14+
import static org.eclipse.jface.internal.text.codemining.CodeMiningLineHeaderAnnotation.getMultilineHeight;
15+
import static org.eclipse.jface.internal.text.codemining.CodeMiningLineHeaderAnnotation.hasAtLeastOneResolvedMiningNotEmpty;
16+
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
import java.util.function.Consumer;
20+
21+
import org.eclipse.swt.custom.StyledText;
22+
import org.eclipse.swt.events.MouseEvent;
23+
import org.eclipse.swt.graphics.Color;
24+
import org.eclipse.swt.graphics.GC;
25+
import org.eclipse.swt.graphics.Rectangle;
26+
27+
import org.eclipse.core.runtime.IProgressMonitor;
28+
29+
import org.eclipse.jface.text.Position;
30+
import org.eclipse.jface.text.codemining.ICodeMining;
31+
import org.eclipse.jface.text.source.ISourceViewer;
32+
import org.eclipse.jface.text.source.inlined.LineFooterAnnotation;
33+
34+
/**
35+
* Code Mining annotation at the end of the document.
36+
*
37+
*/
38+
public class CodeMiningDocumentFooterAnnotation extends LineFooterAnnotation implements ICodeMiningAnnotation {
39+
40+
/**
41+
* List of resolved minings which contains current resolved minings and last resolved minings
42+
* and null if mining is not resolved.
43+
*/
44+
private ICodeMining[] fResolvedMinings;
45+
46+
/**
47+
* List of current resolved/unresolved minings
48+
*/
49+
private final List<ICodeMining> fMinings;
50+
51+
/**
52+
* List of bounds minings
53+
*/
54+
private final List<Rectangle> fBounds;
55+
56+
/**
57+
* The current progress monitor
58+
*/
59+
private IProgressMonitor fMonitor;
60+
61+
/**
62+
* Code mining annotation constructor.
63+
*
64+
* @param position the position
65+
* @param viewer the viewer
66+
* @param onMouseHover the consumer to be called on mouse hover. If set, the implementor needs
67+
* to take care of setting the cursor if wanted.
68+
* @param onMouseOut the consumer to be called on mouse out. If set, the implementor needs to
69+
* take care of resetting the cursor.
70+
* @param onMouseMove the consumer to be called on mouse move
71+
*/
72+
public CodeMiningDocumentFooterAnnotation(Position position, ISourceViewer viewer, Consumer<MouseEvent> onMouseHover, Consumer<MouseEvent> onMouseOut, Consumer<MouseEvent> onMouseMove) {
73+
super(position, viewer, onMouseHover, onMouseOut, onMouseMove);
74+
fResolvedMinings= null;
75+
fMinings= new ArrayList<>();
76+
fBounds= new ArrayList<>();
77+
}
78+
79+
@Override
80+
public int getHeight() {
81+
return hasAtLeastOneResolvedMiningNotEmpty(fMinings, fResolvedMinings)
82+
? getMultilineHeight(null, fMinings, super.getTextWidget(), super.getHeight())
83+
: 0;
84+
}
85+
86+
@Override
87+
public void update(List<ICodeMining> minings, IProgressMonitor monitor) {
88+
if (fResolvedMinings == null || (fResolvedMinings.length != minings.size())) {
89+
// size of resolved minings are different from size of minings to update, initialize it with size of minings to update
90+
fResolvedMinings= new ICodeMining[minings.size()];
91+
}
92+
// fill valid resolved minings with old minings.
93+
int length= Math.min(fMinings.size(), minings.size());
94+
for (int i= 0; i < length; i++) {
95+
ICodeMining mining= fMinings.get(i);
96+
if (mining.getLabel() != null) {
97+
fResolvedMinings[i]= mining;
98+
}
99+
}
100+
disposeMinings(fMinings);
101+
fMonitor= monitor;
102+
fMinings.addAll(minings);
103+
}
104+
105+
@Override
106+
public void markDeleted(boolean deleted) {
107+
super.markDeleted(deleted);
108+
if (deleted) {
109+
disposeMinings(fMinings);
110+
fResolvedMinings= null;
111+
}
112+
}
113+
114+
@Override
115+
public void draw(GC gc, StyledText textWidget, int offset, int length, Color color, int x, int y) {
116+
int singleLineHeight= super.getHeight();
117+
CodeMiningLineHeaderAnnotation.draw(fMinings, fBounds, singleLineHeight, fResolvedMinings, gc, textWidget, color, x, y, new Runnable() {
118+
119+
@Override
120+
public void run() {
121+
redraw();
122+
}
123+
});
124+
}
125+
126+
@Override
127+
public void redraw() {
128+
// redraw codemining annotation is done only if all current minings are resolved.
129+
List<ICodeMining> minings= new ArrayList<>(fMinings);
130+
for (ICodeMining mining : minings) {
131+
if (!mining.isResolved()) {
132+
// one of mining is not resolved, resolve it and then redraw the annotation.
133+
mining.resolve(getViewer(), fMonitor).thenRunAsync(() -> {
134+
this.redraw();
135+
});
136+
return;
137+
}
138+
}
139+
// all minings are resolved, redraw the annotation
140+
super.redraw();
141+
}
142+
143+
@Override
144+
public Consumer<MouseEvent> getAction(MouseEvent e) {
145+
ICodeMining mining= CodeMiningManager.getValidCodeMiningAtLocation(fResolvedMinings, fBounds, e.x, e.y);
146+
return mining != null ? mining.getAction() : null;
147+
}
148+
149+
@Override
150+
public boolean isInVisibleLines() {
151+
return super.isInVisibleLines();
152+
}
153+
}

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

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
* @since 3.13
3939
*/
4040
public class CodeMiningLineHeaderAnnotation extends LineHeaderAnnotation implements ICodeMiningAnnotation {
41-
4241
private static final String SEPARATOR= " | "; //$NON-NLS-1$
4342

4443
/**
@@ -92,20 +91,19 @@ public CodeMiningLineHeaderAnnotation(Position position, ISourceViewer viewer, C
9291

9392
@Override
9493
public int getHeight() {
95-
return hasAtLeastOneResolvedMiningNotEmpty() ? getMultilineHeight(null) : 0;
94+
return hasAtLeastOneResolvedMiningNotEmpty(fMinings, fResolvedMinings) ? getMultilineHeight(null, fMinings, super.getTextWidget(), super.getHeight()) : 0;
9695
}
9796

9897
public int getHeight(GC gc) {
99-
return hasAtLeastOneResolvedMiningNotEmpty() ? getMultilineHeight(gc) : 0;
98+
return hasAtLeastOneResolvedMiningNotEmpty(fMinings, fResolvedMinings) ? getMultilineHeight(gc, fMinings, super.getTextWidget(), super.getHeight()) : 0;
10099
}
101100

102-
private int getMultilineHeight(GC gc) {
101+
static int getMultilineHeight(GC gc, List<ICodeMining> minings, StyledText styledText, int superHeight) {
103102
int numLinesOfAllMinings= 0;
104-
StyledText styledText= super.getTextWidget();
105103
boolean ignoreFirstLine= false;
106104
int sumLineHeight= 0;
107-
for (int i= 0; i < fMinings.size(); i++) {
108-
ICodeMining mining= fMinings.get(i);
105+
for (int i= 0; i < minings.size(); i++) {
106+
ICodeMining mining= minings.get(i);
109107
String label= mining.getLabel();
110108
if (label == null) {
111109
continue;
@@ -117,12 +115,12 @@ private int getMultilineHeight(GC gc) {
117115
}
118116
numLinesOfAllMinings+= numLines;
119117
if (gc != null) {
120-
sumLineHeight= calculateLineHeight(gc, styledText, ignoreFirstLine, sumLineHeight, i, splitted);
118+
sumLineHeight= calculateLineHeight(minings, gc, styledText, ignoreFirstLine, sumLineHeight, i, splitted);
121119
}
122120
ignoreFirstLine= true;
123121
}
124-
if (sumLineHeight == 0) {
125-
return super.getHeight();
122+
if (sumLineHeight == 0 && numLinesOfAllMinings == 0) {
123+
return superHeight;
126124
}
127125
if (gc != null) {
128126
return sumLineHeight;
@@ -133,14 +131,14 @@ private int getMultilineHeight(GC gc) {
133131
}
134132
}
135133

136-
private int calculateLineHeight(GC gc, StyledText styledText, boolean ignoreFirstLine, int sumLineHeight, int miningIndex, String[] splitted) {
134+
private static int calculateLineHeight(List<ICodeMining> minings, GC gc, StyledText styledText, boolean ignoreFirstLine, int sumLineHeight, int miningIndex, String[] splitted) {
137135
for (int j= 0; j < splitted.length; j++) {
138136
String line= splitted[j];
139137
if (j == 0 && ignoreFirstLine) {
140138
continue;
141139
}
142-
if (j == splitted.length - 1 && miningIndex + 1 < fMinings.size()) { // last line, take first line from next mining
143-
String nextLabel= fMinings.get(miningIndex + 1).getLabel();
140+
if (j == splitted.length - 1 && miningIndex + 1 < minings.size()) { // last line, take first line from next mining
141+
String nextLabel= minings.get(miningIndex + 1).getLabel();
144142
if (nextLabel != null) {
145143
String firstFromNext= nextLabel.split("\\r?\\n|\\r")[0]; //$NON-NLS-1$
146144
line+= firstFromNext;
@@ -159,15 +157,15 @@ private int calculateLineHeight(GC gc, StyledText styledText, boolean ignoreFirs
159157
* @return <code>true</code> if the annotation has at least one resolved mining which have a
160158
* label and <code>false</code> otherwise.
161159
*/
162-
private boolean hasAtLeastOneResolvedMiningNotEmpty() {
163-
if (fMinings.stream().anyMatch(m -> m.getLabel() != null && !m.getLabel().isEmpty())) {
160+
static boolean hasAtLeastOneResolvedMiningNotEmpty(List<ICodeMining> minings, ICodeMining[] resolvedMinings) {
161+
if (minings.stream().anyMatch(m -> m.getLabel() != null && !m.getLabel().isEmpty())) {
164162
return true; // will have a resolved mining.
165163
}
166164

167-
if (fResolvedMinings == null || fResolvedMinings.length == 0) {
165+
if (resolvedMinings == null || resolvedMinings.length == 0) {
168166
return false;
169167
}
170-
return Stream.of(fResolvedMinings).anyMatch(CodeMiningManager::isValidMining);
168+
return Stream.of(resolvedMinings).anyMatch(CodeMiningManager::isValidMining);
171169
}
172170

173171
@Override
@@ -184,7 +182,7 @@ public void update(List<ICodeMining> minings, IProgressMonitor monitor) {
184182
fResolvedMinings[i]= mining;
185183
}
186184
}
187-
disposeMinings();
185+
disposeMinings(fMinings);
188186
fMonitor= monitor;
189187
fMinings.addAll(minings);
190188
}
@@ -193,38 +191,51 @@ public void update(List<ICodeMining> minings, IProgressMonitor monitor) {
193191
public void markDeleted(boolean deleted) {
194192
super.markDeleted(deleted);
195193
if (deleted) {
196-
disposeMinings();
194+
disposeMinings(fMinings);
197195
fResolvedMinings= null;
198196
}
199197
}
200198

201-
private void disposeMinings() {
202-
fMinings.stream().forEach(ICodeMining::dispose);
203-
fMinings.clear();
199+
static void disposeMinings(List<ICodeMining> minings) {
200+
minings.stream().forEach(ICodeMining::dispose);
201+
minings.clear();
204202
}
205203

206204
@Override
207205
public void draw(GC gc, StyledText textWidget, int offset, int length, Color color, int x, int y) {
208-
List<ICodeMining> minings= new ArrayList<>(fMinings);
206+
int singleLineHeight= super.getHeight();
207+
draw(fMinings, fBounds, singleLineHeight, fResolvedMinings, gc, textWidget, color, x, y, new Runnable() {
208+
209+
@Override
210+
public void run() {
211+
redraw();
212+
}
213+
});
214+
}
215+
216+
static void draw(List<ICodeMining> pMinings, List<Rectangle> fBounds, int singleLineHeight, ICodeMining[] fResolvedMinings, GC gc, StyledText textWidget, Color color,
217+
int x, int y, Runnable redrawRunnable) {
218+
List<ICodeMining> minings= new ArrayList<>(pMinings);
209219
int nbDraw= 0;
210220
int separatorWidth= -1;
211221
boolean redrawn= false;
212222
fBounds.clear();
213-
int singleLineHeight= super.getHeight();
214223
int lineSpacing= textWidget.getLineSpacing();
215224
for (int i= 0; i < minings.size(); i++) {
216225
ICodeMining mining= minings.get(i);
217226
// try to get the last resolved mining.
218227
ICodeMining lastResolvedMining= (fResolvedMinings != null && fResolvedMinings.length > i) ? fResolvedMinings[i] : null;
219228
if (mining.getLabel() != null) {
220229
// mining is resolved without error, update the resolved mining list
221-
fResolvedMinings[i]= mining;
230+
if (fResolvedMinings != null) {
231+
fResolvedMinings[i]= mining;
232+
}
222233
} else if (!mining.isResolved()) {
223234
// the mining is not resolved, draw the last resolved mining
224235
mining= lastResolvedMining;
225236
if (!redrawn) {
226237
// redraw the annotation when mining is resolved.
227-
redraw();
238+
redrawRunnable.run();
228239
redrawn= true;
229240
}
230241
} else {

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

Lines changed: 11 additions & 3 deletions
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;
@@ -272,9 +273,16 @@ private void renderCodeMinings(Map<Position, List<ICodeMining>> groups, ISourceV
272273
mouseOut= first.getMouseOut();
273274
mouseMove= first.getMouseMove();
274275
}
275-
ann= inLineHeader
276-
? new CodeMiningLineHeaderAnnotation(pos, viewer, mouseHover, mouseOut, mouseMove)
277-
: new CodeMiningLineContentAnnotation(pos, viewer, afterPosition, mouseHover, mouseOut, mouseMove);
276+
if (inLineHeader) {
277+
ann= new CodeMiningLineHeaderAnnotation(pos, viewer, mouseHover, mouseOut, mouseMove);
278+
} else {
279+
boolean inFooter= !minings.isEmpty() ? (first instanceof DocumentFooterCodeMining) : false;
280+
if (inFooter) {
281+
ann= new CodeMiningDocumentFooterAnnotation(pos, viewer, mouseHover, mouseOut, mouseMove);
282+
} else {
283+
ann= new CodeMiningLineContentAnnotation(pos, viewer, afterPosition, mouseHover, mouseOut, mouseMove);
284+
}
285+
}
278286
} else if (ann instanceof ICodeMiningAnnotation && ((ICodeMiningAnnotation) ann).isInVisibleLines()) {
279287
// annotation is in visible lines
280288
annotationsToRedraw.add((ICodeMiningAnnotation) ann);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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.concurrent.Callable;
14+
import java.util.function.Consumer;
15+
16+
import org.eclipse.swt.custom.StyledText;
17+
import org.eclipse.swt.events.MouseEvent;
18+
import org.eclipse.swt.graphics.Color;
19+
import org.eclipse.swt.graphics.GC;
20+
import org.eclipse.swt.graphics.Point;
21+
22+
import org.eclipse.jface.text.IDocument;
23+
import org.eclipse.jface.text.Position;
24+
25+
/**
26+
* A code mining rendered at the start of the line, located at the very end of the document.
27+
*
28+
* @since 3.28
29+
*/
30+
public class DocumentFooterCodeMining extends AbstractCodeMining {
31+
32+
public DocumentFooterCodeMining(IDocument document, ICodeMiningProvider provider, Consumer<MouseEvent> action) {
33+
super(new Position(document.getLength(), 0), provider, action);
34+
}
35+
36+
@Override
37+
public Point draw(GC gc, StyledText textWidget, Color color, int x, int y) {
38+
return LineHeaderCodeMining.draw(getLabel(), gc, textWidget, x, y, new Callable<Point>() {
39+
40+
@Override
41+
public Point call() throws Exception {
42+
return DocumentFooterCodeMining.super.draw(gc, textWidget, color, x, y);
43+
}
44+
});
45+
}
46+
}

0 commit comments

Comments
 (0)