package org.erlide.ui.editors.erl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ISelectionValidator;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.ui.IWindowListener;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.erlide.engine.ErlangEngine;
import org.erlide.engine.model.ErlModelException;
import org.erlide.engine.model.root.IErlModule;
import org.erlide.engine.services.search.ErlSearchScope;
import org.erlide.engine.services.search.ErlangSearchPattern;
import org.erlide.engine.services.search.LimitTo;
import org.erlide.engine.services.search.ModuleLineFunctionArityRef;
import org.erlide.engine.services.search.OpenResult;
import org.erlide.engine.services.search.OpenService;
import org.erlide.runtime.rpc.RpcException;
import org.erlide.runtime.rpc.RpcTimeoutException;
import org.erlide.ui.internal.ErlideUIPlugin;
import org.erlide.ui.internal.search.ErlangSearchElement;
import org.erlide.ui.internal.search.SearchUtil;
import org.erlide.util.ErlLogger;
import org.erlide.util.IDisposable;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangRangeException;
import com.google.common.collect.Lists;
public class MarkOccurencesSupport implements IDisposable {
private final ErlangEditor editor;
/**
* Tells whether the occurrence annotations are sticky i.e. whether they
* stay even if there's no valid erlang element at the current caret
* position. Only valid if {@link #markOccurencesHandler.fMarkOccurrenceAnnotations}
* is
* <code>true</code>.
*/
public boolean fStickyOccurrenceAnnotations;
public Annotation[] fOccurrenceAnnotations;
/**
* Tells whether all occurrences of the element at the current caret
* location are automatically marked in this editor.
*/
public boolean fMarkOccurrenceAnnotations;
/**
* The selection used when forcing occurrence marking through code.
*/
public ISelection fForcedMarkOccurrencesSelection;
public long fMarkOccurrenceModificationStamp;
/**
* The region of the word under the caret used to when computing the current
* occurrence markings.
*/
public IRegion fMarkOccurrenceTargetRegion;
public ActivationListener fActivationListener;
public ISelectionChangedListener fPostSelectionListener;
public OccurrencesFinderJob fOccurrencesFinderJob;
public OccurrencesFinderJobCanceler fOccurrencesFinderJobCanceler;
public MarkOccurencesSupport(final ErlangEditor editor,
final Annotation[] fOccurrenceAnnotations,
final long fMarkOccurrenceModificationStamp) {
this.editor = editor;
this.fOccurrenceAnnotations = fOccurrenceAnnotations;
this.fMarkOccurrenceModificationStamp = fMarkOccurrenceModificationStamp;
fActivationListener = new ActivationListener();
}
protected void installOccurrencesFinder(final boolean forceUpdate) {
fMarkOccurrenceAnnotations = true;
fPostSelectionListener = new ISelectionChangedListener() {
@Override
public void selectionChanged(final SelectionChangedEvent event) {
final ISelection selection = event.getSelection();
editor.markOccurencesHandler.updateOccurrenceAnnotations(
(ITextSelection) selection, editor.getModule());
}
};
final ISelectionProvider selectionProvider = editor.getSelectionProvider();
if (selectionProvider != null) {
((IPostSelectionProvider) selectionProvider)
.addPostSelectionChangedListener(fPostSelectionListener);
if (forceUpdate) {
fForcedMarkOccurrencesSelection = selectionProvider.getSelection();
final IErlModule module = editor.getModule();
if (module != null) {
editor.markOccurencesHandler.updateOccurrenceAnnotations(
(ITextSelection) fForcedMarkOccurrencesSelection, module);
}
}
}
if (fOccurrencesFinderJobCanceler == null) {
fOccurrencesFinderJobCanceler = new OccurrencesFinderJobCanceler();
fOccurrencesFinderJobCanceler.install();
}
}
protected void uninstallOccurrencesFinder() {
fMarkOccurrenceAnnotations = false;
if (fOccurrencesFinderJob != null) {
fOccurrencesFinderJob.cancel();
fOccurrencesFinderJob = null;
}
if (fOccurrencesFinderJobCanceler != null) {
fOccurrencesFinderJobCanceler.uninstall();
fOccurrencesFinderJobCanceler = null;
}
if (fPostSelectionListener != null) {
((IPostSelectionProvider) editor.getSelectionProvider())
.removePostSelectionChangedListener(fPostSelectionListener);
fPostSelectionListener = null;
}
editor.markOccurencesHandler.removeOccurrenceAnnotations();
}
protected boolean isMarkingOccurrences() {
final IEclipsePreferences prefsNode = ErlideUIPlugin.getPrefsNode();
return prefsNode.getBoolean("markingOccurences", false);
}
/**
* Updates the occurrences annotations based on the current selection.
*
* @param selection
* the text selection
* @param module
* @param astRoot
* the compilation unit AST
*
* @since 3.0
*/
protected void updateOccurrenceAnnotations(final ITextSelection selection,
final IErlModule module) {
if (fOccurrencesFinderJob != null) {
fOccurrencesFinderJob.cancel();
}
if (!fMarkOccurrenceAnnotations) {
return;
}
if (module == null || selection == null) {
return;
}
final IDocument document = editor.getViewer().getDocument();
if (document == null) {
return;
}
boolean hasChanged = false;
final int offset = selection.getOffset();
if (document instanceof IDocumentExtension4) {
final long currentModificationStamp = ((IDocumentExtension4) document)
.getModificationStamp();
final IRegion markOccurrenceTargetRegion = fMarkOccurrenceTargetRegion;
hasChanged = currentModificationStamp != fMarkOccurrenceModificationStamp;
if (markOccurrenceTargetRegion != null && !hasChanged) {
if (markOccurrenceTargetRegion.getOffset() <= offset
&& offset <= markOccurrenceTargetRegion.getOffset()
+ markOccurrenceTargetRegion.getLength()) {
return;
}
}
fMarkOccurrenceTargetRegion = ErlangWordFinder.findWord(module, editor,
offset);
fMarkOccurrenceModificationStamp = currentModificationStamp;
}
if (fOccurrencesFinderJob != null) {
fOccurrencesFinderJob.cancel();
}
fOccurrencesFinderJob = new OccurrencesFinderJob(document, module, selection,
hasChanged);
fOccurrencesFinderJob.setPriority(Job.DECORATE);
fOccurrencesFinderJob.setSystem(true);
fOccurrencesFinderJob.schedule();
// fOccurrencesFinderJob.run(new NullProgressMonitor());
}
void removeOccurrenceAnnotations() {
fMarkOccurrenceModificationStamp = IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
fMarkOccurrenceTargetRegion = null;
final IDocumentProvider documentProvider = editor.getDocumentProvider();
if (documentProvider == null) {
return;
}
final IAnnotationModel annotationModel = documentProvider
.getAnnotationModel(editor.getEditorInput());
if (annotationModel == null || fOccurrenceAnnotations == null) {
return;
}
synchronized (editor.getLockObject(annotationModel)) {
if (annotationModel instanceof IAnnotationModelExtension) {
((IAnnotationModelExtension) annotationModel)
.replaceAnnotations(fOccurrenceAnnotations, null);
} else {
for (int i = 0, length = fOccurrenceAnnotations.length; i < length; i++) {
annotationModel.removeAnnotation(fOccurrenceAnnotations[i]);
}
}
fOccurrenceAnnotations = null;
}
}
List<MarkOccurencesSupport.ErlangRef> getErlangRefs(final IErlModule module,
final List<ModuleLineFunctionArityRef> findRefs) {
final List<MarkOccurencesSupport.ErlangRef> result = new ArrayList<>(
findRefs.size());
for (final ModuleLineFunctionArityRef ref : findRefs) {
result.add(new MarkOccurencesSupport.ErlangRef(
SearchUtil.createSearchElement(ref, module), ref.getOffset(),
ref.getLength(), ref.isDef()));
}
return result;
}
@Override
public void dispose() {
fMarkOccurrenceAnnotations = false;
uninstallOccurrencesFinder();
if (fActivationListener != null) {
PlatformUI.getWorkbench().removeWindowListener(fActivationListener);
fActivationListener = null;
}
}
public void setEnabled(final boolean newBooleanValue) {
if (newBooleanValue != fMarkOccurrenceAnnotations) {
fMarkOccurrenceAnnotations = newBooleanValue;
if (!fMarkOccurrenceAnnotations) {
uninstallOccurrencesFinder();
} else {
installOccurrencesFinder(true);
}
}
}
class OccurrencesFinderJob extends Job {
private final IDocument fDocument;
private final ITextSelection selection;
private final ISelectionValidator fPostSelectionValidator;
private boolean fCanceled = false;
private List<ErlangRef> fRefs;
private final boolean fHasChanged;
private final IErlModule module;
public OccurrencesFinderJob(final IDocument document, final IErlModule module,
final ITextSelection selection, final boolean hasChanged) {
super("OccurrencesFinderJob");
fDocument = document;
this.selection = selection;
if (editor.getSelectionProvider() instanceof ISelectionValidator) {
fPostSelectionValidator = (ISelectionValidator) editor
.getSelectionProvider();
} else {
fPostSelectionValidator = null;
}
this.module = module;
fHasChanged = hasChanged;
}
private void findRefs(final IErlModule theModule, final ITextSelection aSelection,
final boolean hasChanged) {
fRefs = null;
if (fCanceled) {
return;
}
try {
final int offset = aSelection.getOffset();
final OpenResult res = ErlangEngine.getInstance()
.getService(OpenService.class).open(theModule.getScannerName(),
offset,
ErlangEngine.getInstance().getModelUtilService()
.getImportsAsList(theModule),
"", ErlangEngine.getInstance().getModel().getPathVars());
final ErlangSearchPattern pattern = SearchUtil
.getSearchPatternFromOpenResultAndLimitTo(theModule, offset, res,
LimitTo.ALL_OCCURRENCES, false);
if (fCanceled) {
return;
}
if (pattern != null) {
final ErlSearchScope scope = new ErlSearchScope();
scope.addModule(theModule);
final List<ModuleLineFunctionArityRef> findRefs = Lists
.newArrayList();
// TODO: should run in background
final OtpErlangObject refs = ErlangEngine.getInstance()
.getSearchServerService().findRefs(pattern, scope,
ErlangEngine.getInstance().getStateDir(), true);
if (refs != null) {
SearchUtil.addSearchResult(findRefs, refs);
fRefs = editor.markOccurencesHandler.getErlangRefs(theModule,
findRefs);
}
}
} catch (final RpcTimeoutException e) {
ErlLogger.warn(e);
} catch (final RpcException e) {
ErlLogger.warn(e);
} catch (final ErlModelException e) {
ErlLogger.warn(e);
} catch (final OtpErlangRangeException e) {
ErlLogger.warn(e);
}
if (fRefs == null) {
if (!editor.markOccurencesHandler.fStickyOccurrenceAnnotations) {
editor.markOccurencesHandler.removeOccurrenceAnnotations();
} else if (hasChanged) {
editor.markOccurencesHandler.removeOccurrenceAnnotations();
}
}
}
// cannot use cancel() because it is declared final
void doCancel() {
fCanceled = true;
cancel();
}
private boolean isCanceled(final IProgressMonitor progressMonitor) {
return fCanceled || progressMonitor.isCanceled()
|| fPostSelectionValidator != null
&& !(fPostSelectionValidator.isValid(selection)
|| editor.markOccurencesHandler.fForcedMarkOccurrencesSelection == selection)
|| LinkedModeModel.hasInstalledModel(fDocument);
}
/*
* @see Job#run(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public IStatus run(final IProgressMonitor progressMonitor) {
findRefs(module, selection, fHasChanged);
if (fRefs == null) {
return Status.CANCEL_STATUS;
}
if (isCanceled(progressMonitor)) {
return Status.CANCEL_STATUS;
}
final ITextViewer textViewer = editor.getViewer();
if (textViewer == null) {
return Status.CANCEL_STATUS;
}
final IDocument document = textViewer.getDocument();
if (document == null) {
return Status.CANCEL_STATUS;
}
final IDocumentProvider documentProvider = editor.getDocumentProvider();
if (documentProvider == null) {
return Status.CANCEL_STATUS;
}
final IAnnotationModel annotationModel = documentProvider
.getAnnotationModel(editor.getEditorInput());
if (annotationModel == null) {
return Status.CANCEL_STATUS;
}
// Add occurrence annotations
final HashMap<Annotation, Position> annotationMap = new HashMap<>(
fRefs.size());
for (final MarkOccurencesSupport.ErlangRef ref : fRefs) {
if (isCanceled(progressMonitor)) {
return Status.CANCEL_STATUS;
}
final Position position = new Position(ref.getOffset(), ref.getLength());
final String description = ref.getDescription();
final String annotationType = ref.isDef()
? "org.erlide.ui.occurrences.definition" //$NON-NLS-1$
: "org.erlide.ui.occurrences";
annotationMap.put(new Annotation(annotationType, false, description),
position);
}
if (isCanceled(progressMonitor)) {
return Status.CANCEL_STATUS;
}
synchronized (editor.getLockObject(annotationModel)) {
if (annotationModel instanceof IAnnotationModelExtension) {
((IAnnotationModelExtension) annotationModel).replaceAnnotations(
editor.markOccurencesHandler.fOccurrenceAnnotations,
annotationMap);
} else {
editor.markOccurencesHandler.removeOccurrenceAnnotations();
for (final Map.Entry<Annotation, Position> mapEntry : annotationMap
.entrySet()) {
annotationModel.addAnnotation(mapEntry.getKey(),
mapEntry.getValue());
}
}
editor.markOccurencesHandler.fOccurrenceAnnotations = annotationMap
.keySet().toArray(new Annotation[annotationMap.keySet().size()]);
}
return Status.OK_STATUS;
}
}
class OccurrencesFinderJobCanceler implements IDocumentListener, ITextInputListener {
public void install() {
final ISourceViewer sourceViewer = editor.getViewer();
if (sourceViewer == null) {
return;
}
final StyledText text = sourceViewer.getTextWidget();
if (text == null || text.isDisposed()) {
return;
}
sourceViewer.addTextInputListener(this);
final IDocument document = sourceViewer.getDocument();
if (document != null) {
document.addDocumentListener(this);
}
}
public void uninstall() {
final ISourceViewer sourceViewer = editor.getViewer();
if (sourceViewer != null) {
sourceViewer.removeTextInputListener(this);
}
final IDocumentProvider documentProvider = editor.getDocumentProvider();
if (documentProvider != null) {
final IDocument document = documentProvider
.getDocument(editor.getEditorInput());
if (document != null) {
document.removeDocumentListener(this);
}
}
}
@Override
public void documentAboutToBeChanged(final DocumentEvent event) {
if (editor.markOccurencesHandler.fOccurrencesFinderJob != null) {
editor.markOccurencesHandler.fOccurrencesFinderJob.doCancel();
}
}
@Override
public void documentChanged(final DocumentEvent event) {
}
@Override
public void inputDocumentAboutToBeChanged(final IDocument oldInput,
final IDocument newInput) {
if (oldInput == null) {
return;
}
oldInput.removeDocumentListener(this);
}
@Override
public void inputDocumentChanged(final IDocument oldInput,
final IDocument newInput) {
if (newInput == null) {
return;
}
newInput.addDocumentListener(this);
}
}
private static class ErlangRef {
final private ErlangSearchElement element;
final private int offset;
final private int length;
final private boolean def;
public ErlangRef(final ErlangSearchElement element, final int offset,
final int length, final boolean def) {
super();
this.element = element;
this.offset = offset;
this.length = length;
this.def = def;
}
public int getOffset() {
return offset;
}
public int getLength() {
return length;
}
public boolean isDef() {
return def;
}
public String getDescription() {
return element.toString();
}
}
private class ActivationListener implements IWindowListener {
@Override
public void windowActivated(final IWorkbenchWindow window) {
if (window == editor.getEditorSite().getWorkbenchWindow()
&& fMarkOccurrenceAnnotations && editor.isActivePart()) {
fForcedMarkOccurrencesSelection = editor.getSelectionProvider()
.getSelection();
updateOccurrenceAnnotations(
(ITextSelection) fForcedMarkOccurrencesSelection,
editor.getModule());
}
}
@Override
public void windowDeactivated(final IWorkbenchWindow window) {
if (window == editor.getEditorSite().getWorkbenchWindow()
&& fMarkOccurrenceAnnotations && editor.isActivePart()) {
removeOccurrenceAnnotations();
}
}
@Override
public void windowClosed(final IWorkbenchWindow window) {
}
@Override
public void windowOpened(final IWorkbenchWindow window) {
}
}
}