Bug 570678 - [Generic Editor] misses quick fix if not at start of line

Change-Id: I517609250d123497abff3a94ba173da5868ee258
Signed-off-by: Christoph Läubrich <laeubi@laeubi-soft.de>
diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/TestQuickAssist.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/TestQuickAssist.java
index 89d93d8..e0a1ee2 100644
--- a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/TestQuickAssist.java
+++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/TestQuickAssist.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2016-2019 Red Hat Inc. and others
+ * Copyright (c) 2016-2021 Red Hat Inc. and others
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
  *
  * Contributors:
  *     Andrew Obuchowicz (Red Hat Inc.)
+ *     Christoph Läubrich - [Generic Editor] misses quick fix if not at start of line
  *******************************************************************************/
 package org.eclipse.ui.genericeditor.tests;
 
@@ -23,12 +24,17 @@
 import org.junit.After;
 import org.junit.Test;
 
+import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Table;
 import org.eclipse.swt.widgets.TableItem;
 
+import org.eclipse.core.resources.IMarker;
+
 import org.eclipse.jface.text.tests.util.DisplayHelper;
 
+import org.eclipse.ui.genericeditor.tests.contributions.MarkerResolutionGenerator;
+
 import org.eclipse.ui.texteditor.ITextEditorActionConstants;
 import org.eclipse.ui.texteditor.TextOperationAction;
 
@@ -37,6 +43,9 @@
  */
 public class TestQuickAssist extends AbstratGenericEditorTest {
 
+	private static final String FIXME_PROPOSAL= "org.eclipse.ui.genericeditor.tests.contributions.MarkerResolutionGenerator.fixme";
+
+	private static final String DEFAULT_PROPOSAL= "QUICK ASSIST PROPOSAL";
 	private Shell completionShell;
 
 	@Test
@@ -45,7 +54,53 @@
 		openQuickAssist();
 		this.completionShell= CompletionTest.findNewShell(beforeShells, editor.getSite().getShell().getDisplay());
 		final Table completionProposalList = CompletionTest.findCompletionSelectionControl(completionShell);
-		checkCompletionContent(completionProposalList);
+		checkCompletionContent(completionProposalList, new String[] { DEFAULT_PROPOSAL });
+	}
+
+	@Test
+	public void testMarkerQuickAssist() throws Exception {
+		final Set<Shell> beforeShells= Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
+		DisplayHelper.driveEventQueue(Display.getDefault());
+		IMarker marker= null;
+		try {
+			marker= this.file.createMarker(IMarker.PROBLEM);
+			marker.setAttribute(IMarker.LINE_NUMBER, 1);
+			marker.setAttribute(IMarker.CHAR_START, 0);
+			marker.setAttribute(IMarker.CHAR_END, 5);
+			marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
+			marker.setAttribute(IMarker.MESSAGE, "We have a problem");
+			marker.setAttribute(MarkerResolutionGenerator.FIXME, true);
+			openQuickAssist();
+			this.completionShell= CompletionTest.findNewShell(beforeShells, editor.getSite().getShell().getDisplay());
+			final Table completionProposalList= CompletionTest.findCompletionSelectionControl(completionShell);
+			checkCompletionContent(completionProposalList, new String[] { DEFAULT_PROPOSAL, FIXME_PROPOSAL });
+		} finally {
+			if (marker != null && marker.exists()) {
+				marker.delete();
+			}
+		}
+	}
+
+	@Test
+	public void testMarkerQuickAssistLineOnly() throws Exception {
+		final Set<Shell> beforeShells= Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
+		DisplayHelper.driveEventQueue(Display.getDefault());
+		IMarker marker= null;
+		try {
+			marker= this.file.createMarker(IMarker.PROBLEM);
+			marker.setAttribute(IMarker.LINE_NUMBER, 1);
+			marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
+			marker.setAttribute(IMarker.MESSAGE, "We have a problem");
+			marker.setAttribute(MarkerResolutionGenerator.FIXME, true);
+			openQuickAssist();
+			this.completionShell= CompletionTest.findNewShell(beforeShells, editor.getSite().getShell().getDisplay());
+			final Table completionProposalList= CompletionTest.findCompletionSelectionControl(completionShell);
+			checkCompletionContent(completionProposalList, new String[] { DEFAULT_PROPOSAL, FIXME_PROPOSAL });
+		} finally {
+			if (marker != null && marker.exists()) {
+				marker.delete();
+			}
+		}
 	}
 
 	private void openQuickAssist() {
@@ -58,20 +113,23 @@
 
 	/**
 	 * Checks that a mock quick assist proposal comes up
+	 * 
 	 * @param completionProposalList the quick assist proposal list
+	 * @param proposals expected proposals
 	 */
-	private void checkCompletionContent(final Table completionProposalList) {
+	private void checkCompletionContent(final Table completionProposalList, String[] proposals) {
 		// should be instantaneous, but happens to go asynchronous on CI so let's allow a wait
 		new DisplayHelper() {
 			@Override
 			protected boolean condition() {
-				return completionProposalList.getItemCount() == 1;
+				return completionProposalList.getItemCount() >= proposals.length;
 			}
 		}.waitForCondition(completionProposalList.getDisplay(), 200);
-		assertEquals(1, completionProposalList.getItemCount());
-		final TableItem quickAssistItem = completionProposalList.getItem(0);
-		assertTrue("Missing quick assist proposal", quickAssistItem.getText().contains("QUICK ASSIST PROPOSAL")); //$NON-NLS-1$ //$NON-NLS-2$
-
+		assertEquals(proposals.length, completionProposalList.getItemCount());
+		Set<String> existing= Arrays.stream(completionProposalList.getItems()).map(TableItem::getText).collect(Collectors.toSet());
+		for (String proposal : proposals) {
+			assertTrue("Missing quick assist proposal '" + proposal + "', found " + existing, existing.contains(proposal)); //$NON-NLS-1$ //$NON-NLS-2$
+		}
 	}
 
 
diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/markers/MarkerResoltionQuickAssistProcessor.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/markers/MarkerResoltionQuickAssistProcessor.java
index 50e0149..44494a1 100644
--- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/markers/MarkerResoltionQuickAssistProcessor.java
+++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/markers/MarkerResoltionQuickAssistProcessor.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2017 Red Hat Inc. and others.
+ * Copyright (c) 2017, 2021 Red Hat Inc. and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
  *
  * Contributors:
  *   Mickael Istria (Red Hat Inc.) - initial implementation
+ *   Christoph Läubrich - [Generic Editor] misses quick fix if not at start of line
  *******************************************************************************/
 package org.eclipse.ui.internal.genericeditor.markers;
 
@@ -18,6 +19,7 @@
 import java.util.HashSet;
 
 import org.eclipse.core.resources.IMarker;
+import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.Position;
 import org.eclipse.jface.text.contentassist.ICompletionProposal;
 import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
@@ -50,11 +52,32 @@
 		IAnnotationModel annotationModel = invocationContext.getSourceViewer().getAnnotationModel();
 		Collection<MarkerAnnotation> annotations = new HashSet<>();
 		annotationModel.getAnnotationIterator().forEachRemaining(annotation -> {
-			Position position = annotationModel.getPosition(annotation);
-			if (invocationContext.getOffset() >= position.getOffset() &&
-				invocationContext.getOffset() + Math.max(0, invocationContext.getLength()) <= position.getOffset() + position.getLength() &&
-				annotation instanceof MarkerAnnotation) {
-				annotations.add((MarkerAnnotation)annotation);
+			if (annotation instanceof MarkerAnnotation) {
+				MarkerAnnotation markerAnnotation = (MarkerAnnotation) annotation;
+				Position position = annotationModel.getPosition(annotation);
+				int documentOffset = invocationContext.getOffset();
+				int annotationOffset = position.getOffset();
+				int selectionLength = invocationContext.getLength();
+				int annotationLength = position.getLength();
+				if (annotationLength == 0) {
+					// Marker lines are 1-based
+					int markerLine = markerAnnotation.getMarker().getAttribute(IMarker.LINE_NUMBER, 0) - 1;
+					if (markerLine > -1) {
+						try {
+							int documentLine = invocationContext.getSourceViewer().getDocument()
+									.getLineOfOffset(documentOffset);
+							if (markerLine == documentLine) {
+								annotations.add((MarkerAnnotation) annotation);
+							}
+						} catch (BadLocationException e) {
+							// can't be used then...
+						}
+					}
+				}
+				if (documentOffset >= annotationOffset
+						&& documentOffset + Math.max(0, selectionLength) <= annotationOffset + annotationLength) {
+					annotations.add(markerAnnotation);
+				}
 			}
 		});
 		Collection<MarkerResolutionCompletionProposal> resolutions = new ArrayList<>();