/*******************************************************************************
* Copyright (c) 2000, 2017 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.dltk.tcl.internal.ui.text;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.tcl.core.TclNature;
import org.eclipse.dltk.tcl.internal.ui.TclUI;
import org.eclipse.dltk.ui.DLTKPluginImages;
import org.eclipse.dltk.ui.DLTKUIPlugin;
import org.eclipse.dltk.ui.text.ScriptAnnotationUtils;
import org.eclipse.dltk.ui.text.ScriptCorrectionProcessorManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationAccessExtension;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationPresentation;
import org.eclipse.jface.text.source.ImageUtilities;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.texteditor.AnnotationPreference;
import org.eclipse.ui.texteditor.ITextEditor;
public class TclQuickAssistLightBulbUpdater {
public static class AssistAnnotation extends Annotation
implements IAnnotationPresentation {
// XXX: To be fully correct this should be a non-static fields in
// QuickAssistLightBulbUpdater
private static final int LAYER;
static {
Annotation annotation = new Annotation(
"org.eclipse.dltk.ui.warning", false, null); //$NON-NLS-1$
AnnotationPreference preference = EditorsUI
.getAnnotationPreferenceLookup()
.getAnnotationPreference(annotation);
if (preference != null)
LAYER = preference.getPresentationLayer() - 1;
else
LAYER = IAnnotationAccessExtension.DEFAULT_LAYER;
}
private Image fImage;
public AssistAnnotation() {
}
@Override
public int getLayer() {
return LAYER;
}
private Image getImage() {
if (fImage == null) {
fImage = DLTKPluginImages
.get(DLTKPluginImages.IMG_OBJS_QUICK_ASSIST);
}
return fImage;
}
@Override
public void paint(GC gc, Canvas canvas, Rectangle r) {
ImageUtilities.drawImage(getImage(), gc, canvas, r, SWT.CENTER,
SWT.TOP);
}
}
private final Annotation fAnnotation;
private boolean fIsAnnotationShown;
private ITextEditor fEditor;
private ISelectionChangedListener fListener;
private IPropertyChangeListener fPropertyChangeListener;
public TclQuickAssistLightBulbUpdater(ITextEditor part,
ITextViewer viewer) {
fEditor = part;
fAnnotation = new AssistAnnotation();
fIsAnnotationShown = false;
fPropertyChangeListener = null;
}
public boolean isSetInPreferences() {
return true;
}
private void installSelectionListener() {
fListener = event -> {
ISelection selection = event.getSelection();
if (selection instanceof ITextSelection) {
ITextSelection textSelection = (ITextSelection) selection;
doSelectionChanged(textSelection.getOffset(),
textSelection.getLength());
}
};
fEditor.getSelectionProvider().addSelectionChangedListener(fListener);
}
private void uninstallSelectionListener() {
if (fListener != null) {
fEditor.getSelectionProvider()
.removeSelectionChangedListener(this.fListener);
fListener = null;
}
IAnnotationModel model = getAnnotationModel();
if (model != null) {
removeLightBulb(model);
}
}
public void install() {
if (isSetInPreferences()) {
installSelectionListener();
}
if (fPropertyChangeListener == null) {
fPropertyChangeListener = event -> doPropertyChanged(
event.getProperty());
TclUI.getDefault().getPreferenceStore()
.addPropertyChangeListener(fPropertyChangeListener);
}
}
public void uninstall() {
uninstallSelectionListener();
if (fPropertyChangeListener != null) {
TclUI.getDefault().getPreferenceStore()
.removePropertyChangeListener(fPropertyChangeListener);
fPropertyChangeListener = null;
}
}
protected void doPropertyChanged(String property) {
// if
// (property.equals(PreferenceConstants.EDITOR_QUICKASSIST_LIGHTBULB)) {
// if (isSetInPreferences()) {
// ISourceModule cu = getSourceModule();
// if (cu != null) {
// installSelectionListener();
// Point point = fViewer.getSelectedRange();
// CompilationUnit astRoot = SharedASTProvider.getAST(cu,
// SharedASTProvider.WAIT_ACTIVE_ONLY, null);
// if (astRoot != null) {
// doSelectionChanged(point.x, point.y, astRoot);
// }
// }
// } else {
// uninstallSelectionListener();
// }
// }
}
private ISourceModule getSourceModule() {
IModelElement elem = DLTKUIPlugin
.getEditorInputModelElement(fEditor.getEditorInput());
if (elem instanceof ISourceModule) {
return (ISourceModule) elem;
}
return null;
}
private IAnnotationModel getAnnotationModel() {
return DLTKUIPlugin.getDocumentProvider()
.getAnnotationModel(fEditor.getEditorInput());
}
private IDocument getDocument() {
return DLTKUIPlugin.getDocumentProvider()
.getDocument(fEditor.getEditorInput());
}
private void doSelectionChanged(int offset, int length) {
final IAnnotationModel model = getAnnotationModel();
final ISourceModule cu = getSourceModule();
if (model == null || cu == null) {
return;
}
boolean hasQuickFix = hasQuickFixLightBulb(model, offset);
if (hasQuickFix) {
removeLightBulb(model);
return; // there is already a quick fix light bulb at the new
// location
}
calculateLightBulb(model, offset, length);
}
/*
* Needs to be called synchronized
*/
private void calculateLightBulb(IAnnotationModel model, int offset,
int length) {
boolean needsAnnotation = true;
if (fIsAnnotationShown) {
model.removeAnnotation(fAnnotation);
}
if (needsAnnotation) {
model.addAnnotation(fAnnotation, new Position(offset, length));
}
fIsAnnotationShown = needsAnnotation;
}
private void removeLightBulb(IAnnotationModel model) {
synchronized (this) {
if (fIsAnnotationShown) {
model.removeAnnotation(fAnnotation);
fIsAnnotationShown = false;
}
}
}
/*
* Tests if there is already a quick fix light bulb on the current line
*/
private boolean hasQuickFixLightBulb(IAnnotationModel model, int offset) {
try {
IDocument document = getDocument();
if (document == null) {
return false;
}
// we access a document and annotation model from within a job
// since these are only read accesses, we won't hurt anyone else if
// this goes boink
// may throw an IndexOutOfBoundsException upon concurrent document
// modification
int currLine = document.getLineOfOffset(offset);
// this iterator is not protected, it may throw
// ConcurrentModificationExceptions
Iterator<Annotation> iter = model.getAnnotationIterator();
while (iter.hasNext()) {
Annotation annot = iter.next();
if (ScriptAnnotationUtils.isQuickFixableType(annot)) {
// may throw an IndexOutOfBoundsException upon concurrent
// annotation model changes
Position pos = model.getPosition(annot);
if (pos != null) {
// may throw an IndexOutOfBoundsException upon
// concurrent document modification
int startLine = document
.getLineOfOffset(pos.getOffset());
if (startLine == currLine && hasCorrections(annot)) {
return true;
}
}
}
}
} catch (BadLocationException e) {
// ignore
} catch (IndexOutOfBoundsException e) {
// concurrent modification - too bad, ignore
} catch (ConcurrentModificationException e) {
// concurrent modification - too bad, ignore
}
return false;
}
private boolean hasCorrections(Annotation annot) {
return ScriptCorrectionProcessorManager.canFix(TclNature.NATURE_ID,
annot);
}
}