--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2000, 2012 IBM Corporation 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:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.text.correction;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Shell;
+
+import org.eclipse.core.runtime.Assert;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.DefaultInformationControl;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IInformationControl;
+import org.eclipse.jface.text.IInformationControlCreator;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.contentassist.ContentAssistEvent;
+import org.eclipse.jface.text.contentassist.ICompletionListener;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.quickassist.IQuickAssistAssistant;
+import org.eclipse.jface.text.quickassist.QuickAssistAssistant;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.IAnnotationModel;
+import org.eclipse.jface.text.source.ISourceViewer;
+
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPreferenceConstants;
+import org.eclipse.ui.PlatformUI;
+
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.eclipse.ui.texteditor.ITextEditor;
+
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaElement;
+
+import org.eclipse.jdt.ui.JavaUI;
+import org.eclipse.jdt.ui.SharedASTProvider;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+
+
+public class JavaCorrectionAssistant extends QuickAssistAssistant {
+
+ private ITextViewer fViewer;
+ private ITextEditor fEditor;
+ private Position fPosition;
+ private Annotation[] fCurrentAnnotations;
+
+ private QuickAssistLightBulbUpdater fLightBulbUpdater;
+ private boolean fIsCompletionActive;
+ private boolean fIsProblemLocationAvailable;
+
+
+ /**
+ * Constructor for JavaCorrectionAssistant.
+ * @param editor the editor
+ */
+ public JavaCorrectionAssistant(ITextEditor editor) {
+ super();
+ Assert.isNotNull(editor);
+ fEditor= editor;
+
+ JavaCorrectionProcessor processor= new JavaCorrectionProcessor(this);
+
+ setQuickAssistProcessor(processor);
+ enableColoredLabels(PlatformUI.getPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.USE_COLORED_LABELS));
+
+ setInformationControlCreator(getInformationControlCreator());
+
+ addCompletionListener(new ICompletionListener() {
+ public void assistSessionEnded(ContentAssistEvent event) {
+ fIsCompletionActive= false;
+ }
+
+ public void assistSessionStarted(ContentAssistEvent event) {
+ fIsCompletionActive= true;
+ }
+
+ public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) {
+ }
+ });
+ }
+
+ public IEditorPart getEditor() {
+ return fEditor;
+ }
+
+
+ private IInformationControlCreator getInformationControlCreator() {
+ return new IInformationControlCreator() {
+ public IInformationControl createInformationControl(Shell parent) {
+ return new DefaultInformationControl(parent, JavaPlugin.getAdditionalInfoAffordanceString());
+ }
+ };
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.text.contentassist.IContentAssistant#install(org.eclipse.jface.text.ITextViewer)
+ */
+ @Override
+ public void install(ISourceViewer sourceViewer) {
+ super.install(sourceViewer);
+ fViewer= sourceViewer;
+
+ fLightBulbUpdater= new QuickAssistLightBulbUpdater(fEditor, sourceViewer);
+ fLightBulbUpdater.install();
+ }
+
+
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.text.contentassist.ContentAssistant#uninstall()
+ */
+ @Override
+ public void uninstall() {
+ if (fLightBulbUpdater != null) {
+ fLightBulbUpdater.uninstall();
+ fLightBulbUpdater= null;
+ }
+ super.uninstall();
+ }
+
+ /*
+ * @see org.eclipse.jface.text.quickassist.QuickAssistAssistant#showPossibleQuickAssists()
+ * @since 3.2
+ */
+
+ /**
+ * Show completions at caret position. If current
+ * position does not contain quick fixes look for
+ * next quick fix on same line by moving from left
+ * to right and restarting at end of line if the
+ * beginning of the line is reached.
+ *
+ * @see IQuickAssistAssistant#showPossibleQuickAssists()
+ */
+ @Override
+ public String showPossibleQuickAssists() {
+ boolean isReinvoked= false;
+ fIsProblemLocationAvailable= false;
+
+ if (fIsCompletionActive) {
+ if (isUpdatedOffset()) {
+ isReinvoked= true;
+ restorePosition();
+ hide();
+ fIsProblemLocationAvailable= true;
+ }
+ }
+
+ fPosition= null;
+ fCurrentAnnotations= null;
+
+ if (fViewer == null || fViewer.getDocument() == null)
+ // Let superclass deal with this
+ return super.showPossibleQuickAssists();
+
+
+ ArrayList<Annotation> resultingAnnotations= new ArrayList<Annotation>(20);
+ try {
+ Point selectedRange= fViewer.getSelectedRange();
+ int currOffset= selectedRange.x;
+ int currLength= selectedRange.y;
+ boolean goToClosest= (currLength == 0) && !isReinvoked;
+
+ int newOffset= collectQuickFixableAnnotations(fEditor, currOffset, goToClosest, resultingAnnotations);
+ if (newOffset != currOffset) {
+ storePosition(currOffset, currLength);
+ fViewer.setSelectedRange(newOffset, 0);
+ fViewer.revealRange(newOffset, 0);
+ fIsProblemLocationAvailable= true;
+ if (fIsCompletionActive) {
+ hide();
+ }
+ }
+ } catch (BadLocationException e) {
+ JavaPlugin.log(e);
+ }
+ fCurrentAnnotations= resultingAnnotations.toArray(new Annotation[resultingAnnotations.size()]);
+
+ return super.showPossibleQuickAssists();
+ }
+
+
+ private static IRegion getRegionOfInterest(ITextEditor editor, int invocationLocation) throws BadLocationException {
+ IDocumentProvider documentProvider= editor.getDocumentProvider();
+ if (documentProvider == null) {
+ return null;
+ }
+ IDocument document= documentProvider.getDocument(editor.getEditorInput());
+ if (document == null) {
+ return null;
+ }
+ return document.getLineInformationOfOffset(invocationLocation);
+ }
+
+ public static int collectQuickFixableAnnotations(ITextEditor editor, int invocationLocation, boolean goToClosest, ArrayList<Annotation> resultingAnnotations) throws BadLocationException {
+ IAnnotationModel model= JavaUI.getDocumentProvider().getAnnotationModel(editor.getEditorInput());
+ if (model == null) {
+ return invocationLocation;
+ }
+
+ ensureUpdatedAnnotations(editor);
+
+ Iterator<Annotation> iter= model.getAnnotationIterator();
+ if (goToClosest) {
+ IRegion lineInfo= getRegionOfInterest(editor, invocationLocation);
+ if (lineInfo == null) {
+ return invocationLocation;
+ }
+ int rangeStart= lineInfo.getOffset();
+ int rangeEnd= rangeStart + lineInfo.getLength();
+
+ ArrayList<Annotation> allAnnotations= new ArrayList<Annotation>();
+ ArrayList<Position> allPositions= new ArrayList<Position>();
+ int bestOffset= Integer.MAX_VALUE;
+ while (iter.hasNext()) {
+ Annotation annot= iter.next();
+ if (JavaCorrectionProcessor.isQuickFixableType(annot)) {
+ Position pos= model.getPosition(annot);
+ if (pos != null && isInside(pos.offset, rangeStart, rangeEnd)) { // inside our range?
+ allAnnotations.add(annot);
+ allPositions.add(pos);
+ bestOffset= processAnnotation(annot, pos, invocationLocation, bestOffset);
+ }
+ }
+ }
+ if (bestOffset == Integer.MAX_VALUE) {
+ return invocationLocation;
+ }
+ for (int i= 0; i < allPositions.size(); i++) {
+ Position pos= allPositions.get(i);
+ if (isInside(bestOffset, pos.offset, pos.offset + pos.length)) {
+ resultingAnnotations.add(allAnnotations.get(i));
+ }
+ }
+ return bestOffset;
+ } else {
+ while (iter.hasNext()) {
+ Annotation annot= iter.next();
+ if (JavaCorrectionProcessor.isQuickFixableType(annot)) {
+ Position pos= model.getPosition(annot);
+ if (pos != null && isInside(invocationLocation, pos.offset, pos.offset + pos.length)) {
+ resultingAnnotations.add(annot);
+ }
+ }
+ }
+ return invocationLocation;
+ }
+ }
+
+ private static void ensureUpdatedAnnotations(ITextEditor editor) {
+ Object inputElement= editor.getEditorInput().getAdapter(IJavaElement.class);
+ if (inputElement instanceof ICompilationUnit) {
+ SharedASTProvider.getAST((ICompilationUnit) inputElement, SharedASTProvider.WAIT_ACTIVE_ONLY, null);
+ }
+ }
+
+ private static int processAnnotation(Annotation annot, Position pos, int invocationLocation, int bestOffset) {
+ int posBegin= pos.offset;
+ int posEnd= posBegin + pos.length;
+ if (isInside(invocationLocation, posBegin, posEnd) && JavaCorrectionProcessor.hasCorrections(annot)) { // covers invocation location?
+ return invocationLocation;
+ } else if (bestOffset != invocationLocation) {
+ int newClosestPosition= computeBestOffset(posBegin, invocationLocation, bestOffset);
+ if (newClosestPosition != -1) {
+ if (newClosestPosition != bestOffset) { // new best
+ if (JavaCorrectionProcessor.hasCorrections(annot)) { // only jump to it if there are proposals
+ return newClosestPosition;
+ }
+ }
+ }
+ }
+ return bestOffset;
+ }
+
+
+ private static boolean isInside(int offset, int start, int end) {
+ return offset == start || offset == end || (offset > start && offset < end); // make sure to handle 0-length ranges
+ }
+
+ /**
+ * Computes and returns the invocation offset given a new
+ * position, the initial offset and the best invocation offset
+ * found so far.
+ * <p>
+ * The closest offset to the left of the initial offset is the
+ * best. If there is no offset on the left, the closest on the
+ * right is the best.</p>
+ * @param newOffset the offset to llok at
+ * @param invocationLocation the invocation location
+ * @param bestOffset the current best offset
+ * @return -1 is returned if the given offset is not closer or the new best offset
+ */
+ private static int computeBestOffset(int newOffset, int invocationLocation, int bestOffset) {
+ if (newOffset <= invocationLocation) {
+ if (bestOffset > invocationLocation) {
+ return newOffset; // closest was on the right, prefer on the left
+ } else if (bestOffset <= newOffset) {
+ return newOffset; // we are closer or equal
+ }
+ return -1; // further away
+ }
+
+ if (newOffset <= bestOffset)
+ return newOffset; // we are closer or equal
+
+ return -1; // further away
+ }
+
+ /*
+ * @see org.eclipse.jface.text.contentassist.ContentAssistant#possibleCompletionsClosed()
+ */
+ @Override
+ protected void possibleCompletionsClosed() {
+ super.possibleCompletionsClosed();
+ restorePosition();
+ }
+
+ private void storePosition(int currOffset, int currLength) {
+ fPosition= new Position(currOffset, currLength);
+ }
+
+ private void restorePosition() {
+ if (fPosition != null && !fPosition.isDeleted() && fViewer.getDocument() != null) {
+ fViewer.setSelectedRange(fPosition.offset, fPosition.length);
+ fViewer.revealRange(fPosition.offset, fPosition.length);
+ }
+ fPosition= null;
+ }
+
+ /**
+ * Returns true if the last invoked completion was called with an updated offset.
+ * @return <code> true</code> if the last invoked completion was called with an updated offset.
+ */
+ public boolean isUpdatedOffset() {
+ return fPosition != null;
+ }
+
+ /**
+ * @return <code>true</code> if a problem exist on the current line and the completion was not invoked at the problem location
+ * @since 3.4
+ */
+ public boolean isProblemLocationAvailable() {
+ return fIsProblemLocationAvailable;
+ }
+
+ /**
+ * Returns the annotations at the current offset
+ * @return the annotations at the offset
+ */
+ public Annotation[] getAnnotationsAtOffset() {
+ return fCurrentAnnotations;
+ }
+}