Bug 540448 - [code mining] create code minings at the end of the line

Adding LineContentAnnotation on last offset of the line (usually on \n
or with an offset that's superior to last char offset) will now draw the
annotation after the line.

For that goal, a new LineEndCodeMinging API is introduced.

Change-Id: Ibb636a54e11981692a92dba653fb8013d5a3c00a
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.text/+/190503
Tested-by: Platform Bot <platform-bot@eclipse.org>
Reviewed-by: Mickael Istria <mistria@redhat.com>
diff --git a/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/CodeMiningDemo.java b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/CodeMiningDemo.java
index a1bc647..ef74891 100644
--- a/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/CodeMiningDemo.java
+++ b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/CodeMiningDemo.java
@@ -49,14 +49,21 @@
 		ISourceViewer sourceViewer = new SourceViewer(shell, null, SWT.V_SCROLL | SWT.BORDER);
 		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\n"
+						+ "// Name class with a number N to emulate Nms before resolving the references CodeMining\n"
+						+ "// Empty lines show a header annotating they're empty.\n"
+						+ "// The word `echo` is echoed.\n"
+						+ "// Lines containing `end` get an annotation at their end\n\n"
 						+ "class A\n" + "new A\n" + "new A\n\n" + "class 5\n" + "new 5\n" + "new 5\n" + "new 5"),
 				new AnnotationModel());
 		// Add AnnotationPainter (required by CodeMining)
 		addAnnotationPainter(sourceViewer);
 		// Initialize codemining providers
 		((ISourceViewerExtension5) sourceViewer).setCodeMiningProviders(new ICodeMiningProvider[] {
-				new ClassReferenceCodeMiningProvider(), new ClassImplementationsCodeMiningProvider(), new ToEchoWithHeaderAndInlineCodeMiningProvider("class") });
+				new ClassReferenceCodeMiningProvider(), //
+				new ClassImplementationsCodeMiningProvider(), //
+				new ToEchoWithHeaderAndInlineCodeMiningProvider("echo"), //
+				new EmptyLineCodeMiningProvider(), //
+				new EchoAtEndOfLineCodeMiningProvider()});
 		// Execute codemining in a reconciler
 		MonoReconciler reconciler = new MonoReconciler(new IReconcilingStrategy() {
 
diff --git a/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EchoAtEndOfLineCodeMiningProvider.java b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EchoAtEndOfLineCodeMiningProvider.java
new file mode 100644
index 0000000..732881b
--- /dev/null
+++ b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EchoAtEndOfLineCodeMiningProvider.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ *  Copyright (c) 2022, Red Hat Inc. and others.
+ *
+ *  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 java.util.function.Consumer;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider;
+import org.eclipse.jface.text.codemining.ICodeMining;
+import org.eclipse.jface.text.codemining.LineEndCodeMining;
+import org.eclipse.swt.events.MouseEvent;
+
+public class EchoAtEndOfLineCodeMiningProvider extends AbstractCodeMiningProvider {
+
+	@Override
+	public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextViewer viewer,
+			IProgressMonitor monitor) {
+		IDocument document = viewer.getDocument();
+		List<ICodeMining> res = new ArrayList<>();
+		for (int i = 0; i < document.getNumberOfLines(); i++) {
+			try {
+				if (document.get(document.getLineOffset(i), document.getLineLength(i)).contains("end")) {
+					res.add(new LineEndCodeMining(document, i, this) {
+						@Override
+						public String getLabel() {
+							return "End of line";
+						}
+						@Override
+						public boolean isResolved() {
+							return true;
+						}
+						@Override
+						public Consumer<MouseEvent> getAction() {
+							return e -> System.err.println(getLabel() + getPosition());
+						}
+					});
+				}
+			} catch (BadLocationException ex) {
+				ex.printStackTrace();
+			}
+		}
+		return CompletableFuture.completedFuture(res);
+	}
+
+	@Override
+	public void dispose() {
+
+	}
+
+}
diff --git a/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EmptyLineCodeMiningProvider.java b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EmptyLineCodeMiningProvider.java
new file mode 100644
index 0000000..0645d44
--- /dev/null
+++ b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EmptyLineCodeMiningProvider.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ *  Copyright (c) 2022, Red Hat Inc. and others.
+ *
+ *  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.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider;
+import org.eclipse.jface.text.codemining.ICodeMining;
+import org.eclipse.jface.text.codemining.LineHeaderCodeMining;
+
+public class EmptyLineCodeMiningProvider extends AbstractCodeMiningProvider {
+
+	@Override
+	public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextViewer viewer,
+			IProgressMonitor monitor) {
+		IDocument document = viewer.getDocument();
+		List<LineHeaderCodeMining> emptyLineHeaders = new ArrayList<>();
+		for (int line = 0; line < document.getNumberOfLines(); line++) {
+			try {
+				if (document.getLineLength(line) == 1) {
+					emptyLineHeaders.add(new LineHeaderCodeMining(line, document, this) {
+						@Override
+						public String getLabel() {
+							return "Next line is empty";
+						}
+					});
+				}
+			} catch (BadLocationException ex) {
+				ex.printStackTrace();
+			}
+		}
+		return CompletableFuture.completedFuture(emptyLineHeaders);
+	}
+
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/LineEndCodeMining.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/LineEndCodeMining.java
new file mode 100644
index 0000000..2386173
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/LineEndCodeMining.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ *  Copyright (c) 2022, Red Hat Inc. and others.
+ *
+ *  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.codemining;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.Position;
+
+/**
+ * A code mining that is positioned on end of a line.
+ *
+ * @since 3.20
+ */
+public abstract class LineEndCodeMining extends LineContentCodeMining {
+
+	protected LineEndCodeMining(IDocument document, int line, ICodeMiningProvider provider) throws BadLocationException {
+		super(getLineEndPosition(document, line), provider);
+	}
+
+	private static Position getLineEndPosition(IDocument document, int line) throws BadLocationException {
+		int lastCharOffset= document.getLineOffset(line) + document.getLineLength(line);
+		if (lastCharOffset < document.getLength()) {
+			lastCharOffset--; // place on the newline \n char, not on 1st char of next line
+		}
+		return new Position(lastCharOffset);
+	}
+
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java
index e86801b..409b1e8 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java
@@ -126,13 +126,31 @@
 	 */
 	private static void draw(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length,
 			Color color) {
-		if (annotation.drawRightToPreviousChar(widgetOffset)) {
+		if (annotation.isEndOfLine(widgetOffset)) {
+			drawAfterLine(annotation, gc, textWidget, widgetOffset, length, color);
+		} else if (annotation.drawRightToPreviousChar(widgetOffset)) {
 			drawAsRightOfPreviousCharacter(annotation, gc, textWidget, widgetOffset, length, color);
 		} else {
 			drawAsLeftOf1stCharacter(annotation, gc, textWidget, widgetOffset, length, color);
 		}
 	}
 
+	private static void drawAfterLine(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) {
+		if (gc == null) {
+			return;
+		}
+		if (textWidget.getCharCount() == 0) {
+			annotation.draw(gc, textWidget, widgetOffset, length, color, 0, 0);
+		} else {
+			int line= textWidget.getLineAtOffset(widgetOffset);
+			int lineEndOffset= widgetOffset < textWidget.getCharCount() ? widgetOffset : textWidget.getCharCount() - 1;
+			Rectangle bounds= textWidget.getTextBounds(lineEndOffset, lineEndOffset);
+			int lineEndX= bounds.x + bounds.width + gc.stringExtent(" ").x; //$NON-NLS-1$
+			annotation.setLocation(lineEndX, textWidget.getLinePixel(line) + textWidget.getLineVerticalIndent(line));
+			annotation.draw(gc, textWidget, widgetOffset, length, color, lineEndX, textWidget.getLinePixel(line) + textWidget.getLineVerticalIndent(line));
+		}
+	}
+
 	protected static void drawAsLeftOf1stCharacter(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) {
 		StyleRange style= null;
 		try {
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java
index 0e5df94..62a1752 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java
@@ -158,4 +158,9 @@
 				getTextWidget().getLineAtOffset(widgetOffset) == getTextWidget().getLineAtOffset(widgetOffset - 1);
 	}
 
+	boolean isEndOfLine(int widgetOffset) {
+		return getTextWidget().getCharCount() == 0 || getTextWidget().getCharCount() <= widgetOffset ||
+				"\n".equals(getTextWidget().getText(widgetOffset, widgetOffset)); //$NON-NLS-1$
+	}
+
 }