blob: e9b84a791166a7ad1b4143f4b25be9acd0a4154e [file] [log] [blame]
/*=============================================================================#
# 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;
}
}
}