/*=============================================================================# # Copyright (c) 2005-2016 Stephan Wahlbrink (WalWare.de) 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: # Stephan Wahlbrink - initial API and implementation #=============================================================================*/ package de.walware.ecommons.ltk.ui.sourceediting; import java.util.Map; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.text.AbstractDocument; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPartitioningException; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.Position; 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.viewers.ISelection; import org.eclipse.swt.graphics.Point; import org.eclipse.ui.texteditor.IDocumentProvider; import de.walware.ecommons.text.core.IPartitionConstraint; import de.walware.ecommons.ltk.IModelManager; import de.walware.ecommons.ltk.ast.AstSelection; import de.walware.ecommons.ltk.core.ISourceModelStamp; import de.walware.ecommons.ltk.core.model.ISourceUnit; import de.walware.ecommons.ltk.core.model.ISourceUnitModelInfo; import de.walware.ecommons.ltk.ui.ISelectionWithElementInfoListener; import de.walware.ecommons.ltk.ui.LTKInputData; public abstract class AbstractMarkOccurrencesProvider implements ISourceEditorAddon, ISelectionWithElementInfoListener { private static final int CLEAR = -1; private static final int KEEP = 1; private static final int UPDATE = 2; public final class RunData { public final AbstractDocument doc; public ISourceModelStamp stamp; private Annotation[] annotations; private Point range; private int set = 0; private Map<Annotation, Position> todo; RunData(final AbstractDocument doc, final ISourceModelStamp stamp) { this.doc= doc; this.stamp= stamp; } public boolean isValid() { final Point currentSelection = fEditor.fCurrentSelection; return (this.range != null && currentSelection.x >= this.range.x && currentSelection.x+currentSelection.y <= this.range.y && this.doc.getModificationStamp() == this.stamp.getSourceStamp() ); } public boolean accept(final Point range) { this.range = range; if (isValid()) { return true; } this.range = null; return false; } public void set(final Map<Annotation, Position> annotations) { this.set = UPDATE; this.todo = annotations; } public void keep() { this.set = KEEP; } public void clear() { this.set = CLEAR; } } private final SourceEditor1 fEditor; private final String fPartitioning; private final IPartitionConstraint fToleratePartitions; private boolean fIsMarkEnabled; private RunData fLastRun; public AbstractMarkOccurrencesProvider(final SourceEditor1 editor, final IPartitionConstraint toleratePartitions) { if (editor == null) { throw new NullPointerException("editor"); } if (toleratePartitions == null) { throw new NullPointerException("validPartitions"); } fEditor = editor; fPartitioning= fEditor.getDocumentContentInfo().getPartitioning(); fToleratePartitions = toleratePartitions; } @Override public void install(final ISourceEditor editor) { fIsMarkEnabled = true; fEditor.addPostSelectionWithElementInfoListener(this); } @Override public void uninstall() { fIsMarkEnabled = false; fEditor.removePostSelectionWithElementInfoListener(this); removeAnnotations(); } @Override public void inputChanged() { fLastRun = null; } @Override public void stateChanged(final LTKInputData state) { final ISelection selection = state.getSelection(); final boolean ok = update((ISourceUnit) state.getInputElement(), state.getAstSelection(), (selection instanceof ITextSelection) ? (ITextSelection) selection : null ); if (!ok && state.isStillValid()) { removeAnnotations(); } } /** * Updates the occurrences annotations based on the current selection. * * @return <code>true</code> if the annotation is ok (still valid or updated), * otherwise <code>false</code> */ protected boolean update(final ISourceUnit inputElement, final AstSelection astSelection, final ITextSelection orgSelection) { if (!fIsMarkEnabled) { return false; } try { final ISourceUnitModelInfo info = inputElement.getModelInfo(fEditor.getModelTypeId(), IModelManager.NONE, new NullProgressMonitor() ); if (fEditor.getSourceUnit() != inputElement || info == null || astSelection == null) { return false; } final RunData run = new RunData(inputElement.getDocument(null), info.getStamp()); if (run.doc == null) { return false; } if (fLastRun != null && fLastRun.isValid() && fLastRun.stamp.equals(run.stamp)) { return true; } doUpdate(run, info, astSelection, orgSelection); if (!fIsMarkEnabled) { return false; } if (run.set == 0) { checkKeep(run, orgSelection); } switch (run.set) { case KEEP: return true; case UPDATE: updateAnnotations(run); return true; default: removeAnnotations(); return true; } } catch (final BadLocationException e) { } catch (final BadPartitioningException e) { } catch (final UnsupportedOperationException e) { } return false; } protected abstract void doUpdate(RunData run, ISourceUnitModelInfo info, AstSelection astSelection, ITextSelection orgSelection) throws BadLocationException, BadPartitioningException, UnsupportedOperationException; protected void checkKeep(final RunData run, final ITextSelection selection) throws BadLocationException, BadPartitioningException { if (fLastRun == null || !fLastRun.stamp.equals(run.stamp)) { run.clear(); return; } if (selection instanceof ITextSelection) { final ITextSelection textSelection = selection; final Point currentSelection = fEditor.fCurrentSelection; final int offset = textSelection.getOffset(); final int docLength = run.doc.getLength(); final ITypedRegion partition = run.doc.getPartition(fPartitioning, offset, false); if (docLength > 0 && ( (currentSelection.y > 0) || (offset != currentSelection.x) || (textSelection.getLength() == 0 && partition != null && fToleratePartitions.matches(partition.getType()) && (offset <= 0 || !Character.isLetterOrDigit(run.doc.getChar(offset-1)) ) && (offset >= docLength || Character.isWhitespace(run.doc.getChar(offset)) ) ) )) { run.keep(); return; } } return; } protected IAnnotationModel getAnnotationModel() { final IDocumentProvider documentProvider = fEditor.getDocumentProvider(); if (documentProvider == null) { throw new UnsupportedOperationException(); } final IAnnotationModel annotationModel = documentProvider.getAnnotationModel( fEditor.getEditorInput()); if (annotationModel == null || !(annotationModel instanceof IAnnotationModelExtension)) { throw new UnsupportedOperationException(); } return annotationModel; } protected void updateAnnotations(final RunData run) throws BadLocationException { if (!run.isValid()) { return; } // Add occurrence annotations final IAnnotationModel annotationModel = getAnnotationModel(); // create diff ? // if (fLastRun != null && Arrays.equals(run.name, fLastRun.name)) { // } final Annotation[] lastAnnotations = (fLastRun != null) ? fLastRun.annotations : null; synchronized (SourceEditor1.getLockObject(annotationModel)) { if (!run.isValid()) { return; } ((IAnnotationModelExtension) annotationModel).replaceAnnotations(lastAnnotations, run.todo); run.annotations = run.todo.keySet().toArray(new Annotation[run.todo.keySet().size()]); run.todo = null; fLastRun = run; } } protected void removeAnnotations() { final IAnnotationModel annotationModel = getAnnotationModel(); synchronized (SourceEditor1.getLockObject(annotationModel)) { if (fLastRun == null) { return; } ((IAnnotationModelExtension) annotationModel).replaceAnnotations(fLastRun.annotations, null); fLastRun = null; } } }