Skip to content

Commit 29e4efd

Browse files
committed
Fix updating code mining annotations #3405
Drawing code minings depends on the type of code mining annotations that are created for code minings (that are created by code mining providers). When redrawing code minings, the corresponding annotations have to be updated or re-created. The annotations' types have to comply with the code minings' types, i.e. a LineContentCodeMining must go with a CodeMiningLineContentAnnotation, a LineHeaderCodeMining with a CodeMiningLineHeaderAnnotation, and a DocumentFooterCodeMining with a CodeMiningDocumentFooterAnnotation. This PR fixes re-creation of annotations. In some cases, annotations were reused, despite of their wrong type. Now, they are re-created with the required type. The PR also extends the code mining demo to reproduce the issue #3405 and adds an automated test. Now, we have to avoid using AbstractCodeMining class since otherwise we cannot determine which of the following three cases we have: in-line code mining, line header code minings, or line footer code mining. See org.eclipse.jface.internal.text.codemining.CodeMiningManager. Fixes #3405
1 parent 0ce1430 commit 29e4efd

File tree

8 files changed

+370
-26
lines changed

8 files changed

+370
-26
lines changed

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

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*
1111
* Contributors:
1212
* Angelo Zerr <[email protected]> - [CodeMining] Provide CodeMining support with CodeMiningManager - Bug 527720
13+
* Dietrich Travkin <[email protected]> - Fix code mining redrawing - Issue 3405
1314
*/
1415
package org.eclipse.jface.internal.text.codemining;
1516

@@ -230,6 +231,41 @@ private static Map<Position, List<ICodeMining>> groupByLines(List<? extends ICod
230231
Collectors.mapping(Function.identity(), Collectors.toList())));
231232
}
232233

234+
private static enum CodeMiningMode {
235+
InLine(LineContentCodeMining.class, CodeMiningLineContentAnnotation.class),
236+
HeaderLine(LineHeaderCodeMining.class, CodeMiningLineHeaderAnnotation.class),
237+
FooterLine(DocumentFooterCodeMining.class, CodeMiningDocumentFooterAnnotation.class);
238+
239+
public final Class<? extends ICodeMining> codeMiningType;
240+
public final Class<? extends AbstractInlinedAnnotation> annotationType;
241+
242+
CodeMiningMode(Class<? extends ICodeMining> codeMiningType,
243+
Class<? extends AbstractInlinedAnnotation> annotationType) {
244+
this.codeMiningType= codeMiningType;
245+
this.annotationType= annotationType;
246+
}
247+
248+
public static CodeMiningMode createFor(List<ICodeMining> minings) {
249+
Assert.isNotNull(minings);
250+
251+
CodeMiningMode mode= CodeMiningMode.InLine;
252+
if (!minings.isEmpty()) {
253+
ICodeMining first= minings.get(0);
254+
255+
if (CodeMiningMode.InLine.codeMiningType.isInstance(first)) {
256+
mode= CodeMiningMode.InLine;
257+
} else if (CodeMiningMode.HeaderLine.codeMiningType.isInstance(first)) {
258+
mode= CodeMiningMode.HeaderLine;
259+
} else if (CodeMiningMode.FooterLine.codeMiningType.isInstance(first)) {
260+
mode= CodeMiningMode.FooterLine;
261+
} else {
262+
throw new IllegalStateException("Unexpected code mining type: " + first.getClass()); //$NON-NLS-1$
263+
}
264+
}
265+
return mode;
266+
}
267+
}
268+
233269
/**
234270
* Render the codemining grouped by line position.
235271
*
@@ -257,11 +293,13 @@ private void renderCodeMinings(Map<Position, List<ICodeMining>> groups, ISourceV
257293
Position pos= new Position(g.getKey().offset, g.getKey().length);
258294
List<ICodeMining> minings= g.getValue();
259295
ICodeMining first= minings.get(0);
260-
boolean inLineHeader= !minings.isEmpty() ? (first instanceof LineHeaderCodeMining) : true;
296+
297+
CodeMiningMode mode= CodeMiningMode.createFor(minings);
298+
261299
// Try to find existing annotation
262300
AbstractInlinedAnnotation ann= fInlinedAnnotationSupport.findExistingAnnotation(pos);
263-
if (ann == null) {
264-
// The annotation doesn't exists, create it.
301+
if (ann == null || !mode.annotationType.isInstance(ann)) {
302+
// The annotation doesn't exists or has wrong type => create a new one.
265303
boolean afterPosition= false;
266304
if (first instanceof LineContentCodeMining m) {
267305
afterPosition= m.isAfterPosition();
@@ -274,16 +312,14 @@ private void renderCodeMinings(Map<Position, List<ICodeMining>> groups, ISourceV
274312
mouseOut= first.getMouseOut();
275313
mouseMove= first.getMouseMove();
276314
}
277-
if (inLineHeader) {
278-
ann= new CodeMiningLineHeaderAnnotation(pos, viewer, mouseHover, mouseOut, mouseMove);
279-
} else {
280-
boolean inFooter= !minings.isEmpty() ? (first instanceof DocumentFooterCodeMining) : false;
281-
if (inFooter) {
282-
ann= new CodeMiningDocumentFooterAnnotation(pos, viewer, mouseHover, mouseOut, mouseMove);
283-
} else {
284-
ann= new CodeMiningLineContentAnnotation(pos, viewer, afterPosition, mouseHover, mouseOut, mouseMove);
285-
}
286-
}
315+
316+
ann = switch (mode) {
317+
case InLine -> new CodeMiningLineContentAnnotation(pos, viewer, afterPosition, mouseHover, mouseOut, mouseMove);
318+
case HeaderLine -> new CodeMiningLineHeaderAnnotation(pos, viewer, mouseHover, mouseOut, mouseMove);
319+
case FooterLine -> new CodeMiningDocumentFooterAnnotation(pos, viewer, mouseHover, mouseOut, mouseMove);
320+
321+
default -> throw new IllegalStateException("Found unexpected code mining display mode: " + mode); //$NON-NLS-1$
322+
};
287323
} else if (ann instanceof ICodeMiningAnnotation && ((ICodeMiningAnnotation) ann).isInVisibleLines()) {
288324
// annotation is in visible lines
289325
annotationsToRedraw.add((ICodeMiningAnnotation) ann);

examples/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/CodeMiningDemo.java

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*
1111
* Contributors:
1212
* Angelo Zerr <[email protected]> - [CodeMining] Add CodeMining support in SourceViewer - Bug 527515
13+
* Dietrich Travkin <[email protected]> - Fix code mining redrawing - Issue 3405
1314
*/
1415
package org.eclipse.jface.text.examples.codemining;
1516

@@ -34,7 +35,9 @@
3435
import org.eclipse.jface.text.source.ISourceViewerExtension5;
3536
import org.eclipse.jface.text.source.SourceViewer;
3637
import org.eclipse.swt.SWT;
38+
import org.eclipse.swt.events.SelectionListener;
3739
import org.eclipse.swt.layout.GridLayout;
40+
import org.eclipse.swt.widgets.Button;
3841
import org.eclipse.swt.widgets.Display;
3942
import org.eclipse.swt.widgets.Shell;
4043
import org.eclipse.swt.widgets.Text;
@@ -45,14 +48,22 @@
4548
public class CodeMiningDemo {
4649

4750
private static boolean showWhitespaces = false;
51+
private static AtomicReference<Boolean> useInLineCodeMinings = new AtomicReference<>(false);
52+
53+
private static String LINE_HEADER = "Line header";
54+
private static String IN_LINE = "In-line";
4855

4956
public static void main(String[] args) throws Exception {
5057

5158
Display display = new Display();
5259
Shell shell = new Shell(display);
53-
shell.setLayout(new GridLayout());
60+
shell.setLayout(new GridLayout(2, false));
5461
shell.setText("Code Mining demo");
5562

63+
Button toggleInLineButton = new Button(shell, SWT.PUSH);
64+
toggleInLineButton.setText(LINE_HEADER);
65+
GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.FILL).grab(false, false).applyTo(toggleInLineButton);
66+
5667
AtomicReference<String> endOfLineString = new AtomicReference<>("End of line");
5768
Text endOfLineText = new Text(shell, SWT.NONE);
5869
endOfLineText.setText(endOfLineString.get());
@@ -70,7 +81,9 @@ public static void main(String[] args) throws Exception {
7081
+ "// Name class with a number N to emulate Nms before resolving the references CodeMining\n"
7182
+ "// Empty lines show a header annotating they're empty.\n"
7283
+ "// The word `echo` is echoed.\n"
73-
+ "// Lines containing `end` get an annotation at their end\n\n"
84+
+ "// Lines containing `end` get an annotation at their end\n"
85+
+ "// Press the toggle button in the upper left corner to switch between\n"
86+
+ "// showing reference titles in-line and showing them in additional lines.\n\n"
7487
+ "class A\n" //
7588
+ "new A\n" //
7689
+ "new A\n\n" //
@@ -79,29 +92,39 @@ public static void main(String[] args) throws Exception {
7992
+ "class 5\n" //
8093
+ "new 5\n" //
8194
+ "new 5\n" //
82-
+ "new 5\n" //
95+
+ "new 5\n\n" //
96+
+ "Text with some references like [REF-X]\n" + "and [REF-Y] in it.\n\n"
8397
+ "multiline \n" //
8498
+ "multiline \n\n" //
8599
+ "suffix \n"),
86100
new AnnotationModel());
87-
GridDataFactory.fillDefaults().grab(true, true).applyTo(sourceViewer.getTextWidget());
101+
GridDataFactory.fillDefaults().span(2, 1).grab(true, true).applyTo(sourceViewer.getTextWidget());
88102
// Add AnnotationPainter (required by CodeMining)
89103
addAnnotationPainter(sourceViewer);
104+
105+
toggleInLineButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
106+
useInLineCodeMinings.set(!useInLineCodeMinings.get());
107+
toggleInLineButton.setText(useInLineCodeMinings.get() ? IN_LINE : LINE_HEADER);
108+
sourceViewer.updateCodeMinings();
109+
}));
110+
90111
// Initialize codemining providers
91-
((ISourceViewerExtension5) sourceViewer).setCodeMiningProviders(new ICodeMiningProvider[] {
112+
sourceViewer.setCodeMiningProviders(new ICodeMiningProvider[] {
92113
new ClassReferenceCodeMiningProvider(), //
93114
new ClassImplementationsCodeMiningProvider(), //
94115
new ToEchoWithHeaderAndInlineCodeMiningProvider("echo"), //
95116
new MultilineCodeMiningProvider(), //
96117
new EmptyLineCodeMiningProvider(), //
97118
new EchoAtEndOfLineCodeMiningProvider(endOfLineString), //
98-
new LineContentCodeMiningAfterPositionProvider() });
119+
new LineContentCodeMiningAfterPositionProvider(), //
120+
new ReferenceCodeMiningProvider(useInLineCodeMinings) });
121+
99122
// Execute codemining in a reconciler
100123
MonoReconciler reconciler = new MonoReconciler(new IReconcilingStrategy() {
101124

102125
@Override
103126
public void setDocument(IDocument document) {
104-
((ISourceViewerExtension5) sourceViewer).updateCodeMinings();
127+
sourceViewer.updateCodeMinings();
105128
}
106129

107130
@Override
@@ -111,14 +134,14 @@ public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
111134

112135
@Override
113136
public void reconcile(IRegion partition) {
114-
((ISourceViewerExtension5) sourceViewer).updateCodeMinings();
137+
sourceViewer.updateCodeMinings();
115138
}
116139
}, false);
117140
reconciler.install(sourceViewer);
118141

119142
endOfLineText.addModifyListener(event -> {
120143
endOfLineString.set(endOfLineText.getText());
121-
((ISourceViewerExtension5) sourceViewer).updateCodeMinings();
144+
sourceViewer.updateCodeMinings();
122145
});
123146

124147
shell.open();

examples/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/MultilineCodeMiningProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
import org.eclipse.jface.text.IDocument;
2020
import org.eclipse.jface.text.ITextViewer;
2121
import org.eclipse.jface.text.Position;
22-
import org.eclipse.jface.text.codemining.AbstractCodeMining;
2322
import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider;
2423
import org.eclipse.jface.text.codemining.ICodeMining;
24+
import org.eclipse.jface.text.codemining.LineContentCodeMining;
2525
import org.eclipse.jface.text.codemining.LineHeaderCodeMining;
2626

2727
public class MultilineCodeMiningProvider extends AbstractCodeMiningProvider {
@@ -35,7 +35,7 @@ public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextVi
3535
int index = 0;
3636
while ((index = document.get().indexOf(multiLineText, index)) != -1) {
3737
index += multiLineText.length();
38-
res.add(new AbstractCodeMining(new Position(index, 1), this, null) {
38+
res.add(new LineContentCodeMining(new Position(index, 1), this, null) {
3939
@Override
4040
public String getLabel() {
4141
return "multiline first part in same line";
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025, Advantest Europe GmbH
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+
* Contributors:
12+
* Dietrich Travkin <[email protected]> - Fix code mining redrawing - Issue 3405
13+
*
14+
*******************************************************************************/
15+
package org.eclipse.jface.text.examples.codemining;
16+
17+
import java.util.ArrayList;
18+
import java.util.Collections;
19+
import java.util.List;
20+
import java.util.concurrent.CompletableFuture;
21+
import java.util.concurrent.atomic.AtomicReference;
22+
import java.util.regex.Matcher;
23+
import java.util.regex.Pattern;
24+
25+
import org.eclipse.core.runtime.IProgressMonitor;
26+
import org.eclipse.jface.text.BadLocationException;
27+
import org.eclipse.jface.text.IDocument;
28+
import org.eclipse.jface.text.ITextViewer;
29+
import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider;
30+
import org.eclipse.jface.text.codemining.ICodeMining;
31+
32+
public class ReferenceCodeMiningProvider extends AbstractCodeMiningProvider {
33+
34+
private static final String REGEX_REF = "\\[REF-X\\]|\\[REF-Y\\]";
35+
private static final Pattern REGEX_PATTERN = Pattern.compile(REGEX_REF);
36+
37+
private AtomicReference<Boolean> useInLineCodeMinings;
38+
39+
public ReferenceCodeMiningProvider(AtomicReference<Boolean> useInLineCodeMinings) {
40+
this.useInLineCodeMinings = useInLineCodeMinings;
41+
}
42+
43+
@Override
44+
public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextViewer viewer,
45+
IProgressMonitor monitor) {
46+
return CompletableFuture.supplyAsync(() -> {
47+
IDocument document = viewer.getDocument();
48+
49+
if (document == null) {
50+
return Collections.emptyList();
51+
}
52+
53+
return createCodeMiningsFor(document);
54+
});
55+
}
56+
57+
List<ICodeMining> createCodeMiningsFor(IDocument document) {
58+
String documentContent = document.get();
59+
List<ICodeMining> minings = new ArrayList<>();
60+
61+
Matcher regexMatcher = REGEX_PATTERN.matcher(documentContent);
62+
while (regexMatcher.find()) {
63+
String matchedText = regexMatcher.group();
64+
int startIndex = regexMatcher.start();
65+
66+
String title = matchedText.endsWith("X]") ? "Plugging into Eclipse"
67+
: "Building commercial quality plug-ins";
68+
69+
if (useInLineCodeMinings.get()) {
70+
minings.add(new ReferenceInLineCodeMining(title + ": ", startIndex, document, this));
71+
} else {
72+
try {
73+
int offset = startIndex;
74+
int line = document.getLineOfOffset(offset);
75+
int lineOffset = document.getLineOffset(line);
76+
77+
minings.add(new ReferenceLineHeaderCodeMining(title, line, offset - lineOffset, title.length(),
78+
document, this));
79+
} catch (BadLocationException e) {
80+
e.printStackTrace();
81+
}
82+
}
83+
}
84+
85+
return minings;
86+
}
87+
88+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025, Advantest Europe GmbH
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+
* Contributors:
12+
* Dietrich Travkin <[email protected]> - Fix code mining redrawing - Issue 3405
13+
*
14+
*******************************************************************************/
15+
package org.eclipse.jface.text.examples.codemining;
16+
17+
import org.eclipse.jface.text.IDocument;
18+
import org.eclipse.jface.text.Position;
19+
import org.eclipse.jface.text.codemining.ICodeMiningProvider;
20+
import org.eclipse.jface.text.codemining.LineContentCodeMining;
21+
22+
public class ReferenceInLineCodeMining extends LineContentCodeMining {
23+
24+
public ReferenceInLineCodeMining(String label, int positionOffset, IDocument document,
25+
ICodeMiningProvider provider) {
26+
super(new Position(positionOffset, 1), provider);
27+
this.setLabel(label);
28+
}
29+
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025, Advantest Europe GmbH
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+
* Contributors:
12+
* Dietrich Travkin <[email protected]> - Fix code mining redrawing - Issue 3405
13+
*
14+
*******************************************************************************/
15+
package org.eclipse.jface.text.examples.codemining;
16+
17+
import org.eclipse.jface.text.BadLocationException;
18+
import org.eclipse.jface.text.IDocument;
19+
import org.eclipse.jface.text.Position;
20+
import org.eclipse.jface.text.codemining.ICodeMiningProvider;
21+
import org.eclipse.jface.text.codemining.LineHeaderCodeMining;
22+
import org.eclipse.jface.text.source.inlined.Positions;
23+
24+
public class ReferenceLineHeaderCodeMining extends LineHeaderCodeMining {
25+
26+
public ReferenceLineHeaderCodeMining(String label, int beforeLineNumber, int columnInLine, int length,
27+
IDocument document, ICodeMiningProvider provider) throws BadLocationException {
28+
super(calculatePosition(beforeLineNumber, columnInLine, document), provider, null);
29+
this.setLabel(label);
30+
}
31+
32+
private static Position calculatePosition(int beforeLineNumber, int columnInLine, IDocument document)
33+
throws BadLocationException {
34+
Position pos = Positions.of(beforeLineNumber, document, true);
35+
pos.setOffset(pos.offset + columnInLine);
36+
return pos;
37+
}
38+
39+
}

0 commit comments

Comments
 (0)