| /*=============================================================================# |
| # Copyright (c) 2005, 2019 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.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 org.eclipse.statet.ecommons.text.core.PartitionConstraint; |
| |
| import org.eclipse.statet.ltk.ast.core.util.AstSelection; |
| import org.eclipse.statet.ltk.core.ISourceModelStamp; |
| import org.eclipse.statet.ltk.model.core.IModelManager; |
| import org.eclipse.statet.ltk.model.core.elements.ISourceUnit; |
| import org.eclipse.statet.ltk.model.core.elements.ISourceUnitModelInfo; |
| import org.eclipse.statet.ltk.ui.ISelectionWithElementInfoListener; |
| import org.eclipse.statet.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= AbstractMarkOccurrencesProvider.this.editor.currentSelection; |
| 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 editor; |
| |
| private final String partitioning; |
| private final PartitionConstraint toleratePartitions; |
| |
| private boolean isMarkEnabled; |
| private RunData lastRun; |
| |
| |
| public AbstractMarkOccurrencesProvider(final SourceEditor1 editor, |
| final PartitionConstraint toleratePartitions) { |
| if (editor == null) { |
| throw new NullPointerException("editor"); |
| } |
| if (toleratePartitions == null) { |
| throw new NullPointerException("validPartitions"); |
| } |
| this.editor= editor; |
| this.partitioning= this.editor.getDocumentContentInfo().getPartitioning(); |
| this.toleratePartitions= toleratePartitions; |
| } |
| |
| @Override |
| public void install(final ISourceEditor editor) { |
| this.isMarkEnabled= true; |
| this.editor.addPostSelectionWithElementInfoListener(this); |
| } |
| |
| @Override |
| public void uninstall() { |
| this.isMarkEnabled= false; |
| this.editor.removePostSelectionWithElementInfoListener(this); |
| removeAnnotations(); |
| } |
| |
| |
| @Override |
| public void inputChanged() { |
| this.lastRun= 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 (!this.isMarkEnabled) { |
| return false; |
| } |
| try { |
| final ISourceUnitModelInfo info= inputElement.getModelInfo(this.editor.getModelTypeId(), |
| IModelManager.NONE, new NullProgressMonitor() ); |
| if (this.editor.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 (this.lastRun != null && this.lastRun.isValid() && this.lastRun.stamp.equals(run.stamp)) { |
| return true; |
| } |
| |
| doUpdate(run, info, astSelection, orgSelection); |
| if (!this.isMarkEnabled) { |
| 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 (this.lastRun == null || !this.lastRun.stamp.equals(run.stamp)) { |
| run.clear(); |
| return; |
| } |
| if (selection instanceof ITextSelection) { |
| final ITextSelection textSelection= selection; |
| final Point currentSelection= this.editor.currentSelection; |
| final int offset= textSelection.getOffset(); |
| final int docLength= run.doc.getLength(); |
| final ITypedRegion partition= run.doc.getPartition(this.partitioning, offset, false); |
| if (docLength > 0 && |
| ( (currentSelection.y > 0) |
| || (offset != currentSelection.x) |
| || (textSelection.getLength() == 0 |
| && partition != null && this.toleratePartitions.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= this.editor.getDocumentProvider(); |
| if (documentProvider == null) { |
| throw new UnsupportedOperationException(); |
| } |
| final IAnnotationModel annotationModel= documentProvider.getAnnotationModel( |
| this.editor.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 (this.lastRun != null && Arrays.equals(run.name, this.lastRun.name)) { |
| // } |
| final Annotation[] lastAnnotations= (this.lastRun != null) ? this.lastRun.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; |
| this.lastRun= run; |
| } |
| } |
| |
| protected void removeAnnotations() { |
| final IAnnotationModel annotationModel= getAnnotationModel(); |
| synchronized (SourceEditor1.getLockObject(annotationModel)) { |
| if (this.lastRun == null) { |
| return; |
| } |
| ((IAnnotationModelExtension) annotationModel).replaceAnnotations(this.lastRun.annotations, null); |
| this.lastRun= null; |
| } |
| } |
| |
| } |