/******************************************************************************* * Copyright (c) 2000, 2008 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.wst.jsdt.internal.ui.text.correction; import java.util.ArrayList; import java.util.Iterator; import org.eclipse.core.runtime.Assert; import org.eclipse.wst.jsdt.internal.ui.text.html.HTMLTextPresenter; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.preference.PreferenceConverter; 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.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.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.wst.jsdt.core.IJavaScriptUnit; import org.eclipse.wst.jsdt.core.IJavaScriptElement; import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin; import org.eclipse.wst.jsdt.internal.ui.javaeditor.ASTProvider; import org.eclipse.wst.jsdt.ui.JavaScriptUI; import org.eclipse.wst.jsdt.ui.PreferenceConstants; import org.eclipse.wst.jsdt.ui.text.IColorManager; import org.eclipse.wst.jsdt.ui.text.JavaScriptTextTools; public class JavaCorrectionAssistant extends QuickAssistAssistant { private ITextViewer fViewer; private ITextEditor fEditor; private Position fPosition; private Annotation[] fCurrentAnnotations; private QuickAssistLightBulbUpdater fLightBulbUpdater; /** * Constructor for JavaCorrectionAssistant. */ public JavaCorrectionAssistant(ITextEditor editor) { super(); Assert.isNotNull(editor); fEditor= editor; JavaCorrectionProcessor processor= new JavaCorrectionProcessor(this); setQuickAssistProcessor(processor); setInformationControlCreator(getInformationControlCreator()); JavaScriptTextTools textTools= JavaScriptPlugin.getDefault().getJavaTextTools(); IColorManager manager= textTools.getColorManager(); IPreferenceStore store= JavaScriptPlugin.getDefault().getPreferenceStore(); Color c= getColor(store, PreferenceConstants.CODEASSIST_PROPOSALS_FOREGROUND, manager); setProposalSelectorForeground(c); c= getColor(store, PreferenceConstants.CODEASSIST_PROPOSALS_BACKGROUND, manager); setProposalSelectorBackground(c); } public IEditorPart getEditor() { return fEditor; } private IInformationControlCreator getInformationControlCreator() { return new IInformationControlCreator() { public IInformationControl createInformationControl(Shell parent) { return new DefaultInformationControl(parent, new HTMLTextPresenter()); } }; } private static Color getColor(IPreferenceStore store, String key, IColorManager manager) { RGB rgb= PreferenceConverter.getColor(store, key); return manager.getColor(rgb); } /* (non-Javadoc) * @see org.eclipse.jface.text.contentassist.IContentAssistant#install(org.eclipse.jface.text.ITextViewer) */ 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() */ public void uninstall() { if (fLightBulbUpdater != null) { fLightBulbUpdater.uninstall(); fLightBulbUpdater= null; } super.uninstall(); } /* * @see org.eclipse.jface.text.quickassist.QuickAssistAssistant#showPossibleQuickAssists() * */ /** * 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 org.eclipse.jface.text.quickassist.IQuickAssistAssistant#showPossibleQuickAssists() */ public String showPossibleQuickAssists() { fPosition= null; fCurrentAnnotations= null; if (fViewer == null || fViewer.getDocument() == null) // Let superclass deal with this return super.showPossibleQuickAssists(); ArrayList resultingAnnotations= new ArrayList(20); try { Point selectedRange= fViewer.getSelectedRange(); int currOffset= selectedRange.x; int currLength= selectedRange.y; boolean goToClosest= (currLength == 0); int newOffset= collectQuickFixableAnnotations(fEditor, currOffset, goToClosest, resultingAnnotations); if (newOffset != currOffset) { storePosition(currOffset, currLength); fViewer.setSelectedRange(newOffset, 0); fViewer.revealRange(newOffset, 0); } } catch (BadLocationException e) { JavaScriptPlugin.log(e); } fCurrentAnnotations= (Annotation[]) 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 resultingAnnotations) throws BadLocationException { IAnnotationModel model= JavaScriptUI.getDocumentProvider().getAnnotationModel(editor.getEditorInput()); if (model == null) { return invocationLocation; } ensureUpdatedAnnotations(editor); Iterator 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 allAnnotations= new ArrayList(); ArrayList allPositions= new ArrayList(); int bestOffset= Integer.MAX_VALUE; while (iter.hasNext()) { Annotation annot= (Annotation) 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= (Position) 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= (Annotation) 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(IJavaScriptElement.class); if (inputElement instanceof IJavaScriptUnit) { JavaScriptPlugin.getDefault().getASTProvider().getAST((IJavaScriptUnit) inputElement, ASTProvider.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)) { // 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> * @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() */ 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. */ public boolean isUpdatedOffset() { return fPosition != null; } /** * Returns the annotations at the current offset */ public Annotation[] getAnnotationsAtOffset() { return fCurrentAnnotations; } }