Bug 513314 - Example enabling spellcheck on generic editor

+ Enable quick fix for spelling issues by default

Change-Id: I32973aa0e064547f1c5800e2503f43ef3c319078
Signed-off-by: Mickael Istria <mistria@redhat.com>
diff --git a/org.eclipse.ui.genericeditor.examples/META-INF/MANIFEST.MF b/org.eclipse.ui.genericeditor.examples/META-INF/MANIFEST.MF
index a7f228c..a7dd0ef 100644
--- a/org.eclipse.ui.genericeditor.examples/META-INF/MANIFEST.MF
+++ b/org.eclipse.ui.genericeditor.examples/META-INF/MANIFEST.MF
@@ -10,4 +10,5 @@
  org.eclipse.jface.text;bundle-version="3.11.0",
  org.eclipse.core.resources;bundle-version="3.11.0",
  org.eclipse.ui;bundle-version="3.108.0",
- org.eclipse.core.runtime;bundle-version="3.11.0"
+ org.eclipse.core.runtime;bundle-version="3.11.0",
+ org.eclipse.ui.workbench.texteditor;bundle-version="3.10.100"
diff --git a/org.eclipse.ui.genericeditor.examples/plugin.xml b/org.eclipse.ui.genericeditor.examples/plugin.xml
index f03f4b6..d455262 100644
--- a/org.eclipse.ui.genericeditor.examples/plugin.xml
+++ b/org.eclipse.ui.genericeditor.examples/plugin.xml
@@ -50,5 +50,12 @@
             editorId="org.eclipse.ui.genericeditor.GenericEditor">
       </editorContentTypeBinding>
    </extension>
+   <extension
+         point="org.eclipse.core.filebuffers.documentSetup">
+      <participant
+            class="org.eclipse.ui.genericeditor.examples.dotproject.ProjectDocumentParticipant"
+            contentTypeId="org.eclipse.ui.genericeditor.examples.dotproject">
+      </participant>
+   </extension>
 
 </plugin>
diff --git a/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/ProjectDocumentParticipant.java b/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/ProjectDocumentParticipant.java
new file mode 100644
index 0000000..d65b8ca
--- /dev/null
+++ b/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/ProjectDocumentParticipant.java
@@ -0,0 +1,14 @@
+package org.eclipse.ui.genericeditor.examples.dotproject;
+
+import org.eclipse.core.filebuffers.IDocumentSetupParticipant;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentExtension3;
+
+public class ProjectDocumentParticipant implements IDocumentSetupParticipant {
+
+	@Override
+	public void setup(IDocument document) {
+		document.addDocumentListener(new SpellCheckDocumentListener());
+	}
+
+}
diff --git a/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/SpellCheckDocumentListener.java b/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/SpellCheckDocumentListener.java
new file mode 100644
index 0000000..099b891
--- /dev/null
+++ b/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/SpellCheckDocumentListener.java
@@ -0,0 +1,95 @@
+package org.eclipse.ui.genericeditor.examples.dotproject;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.filebuffers.ITextFileBufferManager;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocumentListener;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.source.IAnnotationModel;
+import org.eclipse.jface.text.source.IAnnotationModelExtension;
+import org.eclipse.ui.editors.text.EditorsUI;
+import org.eclipse.ui.texteditor.spelling.ISpellingProblemCollector;
+import org.eclipse.ui.texteditor.spelling.SpellingAnnotation;
+import org.eclipse.ui.texteditor.spelling.SpellingContext;
+import org.eclipse.ui.texteditor.spelling.SpellingProblem;
+import org.eclipse.ui.texteditor.spelling.SpellingService;
+
+public class SpellCheckDocumentListener implements IDocumentListener {
+	
+	Job lastJob = null;
+	SpellingService service = EditorsUI.getSpellingService();
+
+	@Override
+	public void documentAboutToBeChanged(DocumentEvent event) {
+	}
+
+	@Override
+	public void documentChanged(final DocumentEvent event) {
+		if (this.lastJob != null) {
+			this.lastJob.cancel();
+		}
+		this.lastJob = new Job("Spellcheck") {
+			@Override
+			protected IStatus run(IProgressMonitor monitor) {
+				IAnnotationModel model = ITextFileBufferManager.DEFAULT.getTextFileBuffer(event.getDocument()).getAnnotationModel();
+				String text = event.getDocument().get();
+				int commentStart = text.indexOf("<comment>");
+				if (commentStart < 0) {
+					return Status.OK_STATUS;
+				}
+				commentStart += "<comment>".length();
+				int commentEnd = text.indexOf("</comment>", commentStart);
+				if (commentEnd <= commentStart) {
+					return Status.OK_STATUS;
+				}
+				Region region = new Region(commentStart, commentEnd - commentStart);
+				service.check(event.getDocument(), new Region[] { region }, new SpellingContext(), new ISpellingProblemCollector() {
+					private Map<SpellingAnnotation, Position> annotations = new HashMap<>();
+				
+					@Override
+					public void endCollecting() {
+						Set<SpellingAnnotation> previous = new HashSet<>();
+						model.getAnnotationIterator().forEachRemaining(annotation -> {
+							if (annotation instanceof SpellingAnnotation) {
+								previous.add((SpellingAnnotation)annotation);
+							}
+						});
+						if (model instanceof IAnnotationModelExtension) {
+							((IAnnotationModelExtension)model).replaceAnnotations(
+									previous.toArray(new SpellingAnnotation[previous.size()]),
+									annotations);
+							
+						} else {
+							previous.forEach(model::removeAnnotation);
+							annotations.forEach(model::addAnnotation);
+						}
+					}
+					
+					@Override
+					public void beginCollecting() {
+					}
+					
+					@Override
+					public void accept(SpellingProblem problem) {
+						this.annotations.put(new SpellingAnnotation(problem), new Position(problem.getOffset(), problem.getLength()));
+					}
+				}, monitor);
+				return Status.OK_STATUS;
+			}
+		};
+		this.lastJob.setUser(false);
+		this.lastJob.setPriority(Job.DECORATE);
+		// set a delay before reacting to user action to handle continuous typing
+		this.lastJob.schedule(500);
+	}
+
+}
diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeQuickAssistProcessor.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeQuickAssistProcessor.java
new file mode 100644
index 0000000..0e11b3c
--- /dev/null
+++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeQuickAssistProcessor.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Red Hat Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * - Mickael Istria (Red Hat Inc.)
+ *******************************************************************************/
+package org.eclipse.ui.internal.genericeditor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
+import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
+import org.eclipse.jface.text.source.Annotation;
+
+/**
+ * A quick assist processor that delegates all content assist
+ * operations to children provided in constructor and aggregates
+ * the results.
+ * 
+ * @since 1.0
+ */
+public class CompositeQuickAssistProcessor implements IQuickAssistProcessor {
+
+	private List<IQuickAssistProcessor> fProcessors;
+
+	public CompositeQuickAssistProcessor(List<IQuickAssistProcessor> processors) {
+		this.fProcessors = processors;
+	}
+
+	@Override
+	public String getErrorMessage() {
+		StringBuilder res = new StringBuilder();
+		for (IQuickAssistProcessor processor : this.fProcessors) {
+			String errorMessage = processor.getErrorMessage();
+			if (errorMessage != null) {
+				res.append(errorMessage);
+				res.append('\n');
+			}
+		}
+		if (res.length() == 0) {
+			return null;
+		}
+		return res.toString();
+	}
+
+	@Override
+	public boolean canFix(Annotation annotation) {
+		for (IQuickAssistProcessor processor : this.fProcessors) {
+			if (processor.canFix(annotation)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
+		for (IQuickAssistProcessor processor : this.fProcessors) {
+			if (processor.canAssist(invocationContext)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public ICompletionProposal[] computeQuickAssistProposals(IQuickAssistInvocationContext invocationContext) {
+		List<ICompletionProposal> res = new ArrayList<>();
+		for (IQuickAssistProcessor processor : this.fProcessors) {
+			ICompletionProposal[] proposals = processor.computeQuickAssistProposals(invocationContext);
+			if (proposals != null) {
+				res.addAll(Arrays.asList(proposals));
+			}
+		}
+		return res.toArray(new ICompletionProposal[res.size()]);
+	}
+
+}
diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java
index d24f71f..1fe651b 100644
--- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java
+++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java
@@ -42,6 +42,7 @@
 import org.eclipse.ui.internal.editors.text.EditorsPlugin;
 import org.eclipse.ui.internal.genericeditor.markers.MarkerResoltionQuickAssistProcessor;
 import org.eclipse.ui.texteditor.ITextEditor;
+import org.eclipse.ui.texteditor.spelling.SpellingCorrectionProcessor;
 
 /**
  * The configuration of the {@link ExtensionBasedTextEditor}. It registers the proxy composite
@@ -165,7 +166,11 @@
 	@Override
 	public IQuickAssistAssistant getQuickAssistAssistant(ISourceViewer sourceViewer) {
 		QuickAssistAssistant quickAssistAssistant = new QuickAssistAssistant();
-		quickAssistAssistant.setQuickAssistProcessor(new MarkerResoltionQuickAssistProcessor());
+		CompositeQuickAssistProcessor processor = new CompositeQuickAssistProcessor(Arrays.asList(
+				new MarkerResoltionQuickAssistProcessor(),
+				new SpellingCorrectionProcessor()
+				));
+		quickAssistAssistant.setQuickAssistProcessor(processor);
 		quickAssistAssistant.setRestoreCompletionProposalSize(EditorsPlugin.getDefault().getDialogSettingsSection("quick_assist_proposal_size")); //$NON-NLS-1$
 		quickAssistAssistant.setInformationControlCreator(new IInformationControlCreator() {
 			@Override