package com.redhat.ceylon.eclipse.util;
import static com.redhat.ceylon.eclipse.code.editor.MarkOccurrencesAction.ASSIGNMENT_ANNOTATION;
import static com.redhat.ceylon.eclipse.code.editor.MarkOccurrencesAction.DECLARATION_ANNOTATION;
import static com.redhat.ceylon.eclipse.code.editor.MarkOccurrencesAction.OCCURRENCE_ANNOTATION;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.PROBLEM_MARKER_ID;
import static com.redhat.ceylon.eclipse.core.builder.MarkerCreator.ERROR_CODE_KEY;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
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.ISourceViewer;
import org.eclipse.jface.text.source.projection.AnnotationBag;
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
import org.eclipse.search.ui.NewSearchUI;
import org.eclipse.ui.texteditor.MarkerAnnotation;
import com.redhat.ceylon.eclipse.code.editor.CeylonAnnotation;
public class AnnotationUtils {
public static final String SEARCH_ANNOTATION_TYPE =
NewSearchUI.PLUGIN_ID + ".results";
private static Set<String> sAnnotationTypesToFilter =
new HashSet<String>();
static {
String prefix = "org.eclipse.ui.workbench.texteditor.";
sAnnotationTypesToFilter.add(prefix + "quickdiffUnchanged");
sAnnotationTypesToFilter.add(prefix + "quickdiffChange");
sAnnotationTypesToFilter.add(prefix + "quickdiffAddition");
sAnnotationTypesToFilter.add(prefix + "quickdiffDeletion");
sAnnotationTypesToFilter.add("org.eclipse.debug.core.breakpoint");
sAnnotationTypesToFilter.add(DECLARATION_ANNOTATION);
sAnnotationTypesToFilter.add(OCCURRENCE_ANNOTATION);
sAnnotationTypesToFilter.add(ASSIGNMENT_ANNOTATION);
sAnnotationTypesToFilter.add(ProjectionAnnotation.TYPE);
}
/**
* @return true, if the given Annotation and Position are redundant,
* given the annotation information in the given Map
*/
public static boolean addAndCheckDuplicateAnnotation(Map<Integer,
List<Object>> map, Annotation annotation, Position position) {
List<Object> annotationsAtPosition;
if (!map.containsKey(position.offset)) {
annotationsAtPosition = new ArrayList<Object>();
map.put(position.offset, annotationsAtPosition);
}
else {
annotationsAtPosition = map.get(position.offset);
}
// TODO this should call out to a language extension point first
// to see if the language can resolve duplicates
// Check to see if an error code is present on the marker / annotation
Integer errorCode = -1;
if (annotation instanceof CeylonAnnotation) {
CeylonAnnotation ceylonAnnotation =
(CeylonAnnotation) annotation;
errorCode = ceylonAnnotation.getId();
}
else if (annotation instanceof MarkerAnnotation) {
MarkerAnnotation markerAnnotation =
(MarkerAnnotation) annotation;
errorCode =
markerAnnotation.getMarker()
.getAttribute(ERROR_CODE_KEY, -1);
}
// Fall back to comparing the text associated with this annotation
if (errorCode == -1) {
if (!annotationsAtPosition.contains(annotation.getText())) {
annotationsAtPosition.add(annotation.getText());
return false;
}
}
else if (!annotationsAtPosition.contains(errorCode)) {
annotationsAtPosition.add(errorCode);
return false;
}
return true;
}
/**
* @return the list of Annotations that reside at the given
* line for the given ISourceViewer
*/
public static List<Annotation> getAnnotationsForLine(final ISourceViewer viewer,
final int line) {
return getAnnotations(viewer, new IPositionPredicate() {
IDocument document = viewer.getDocument();
public boolean matchPosition(Position p) {
return positionIsAtLine(p, document, line);
}
});
}
/**
* @return the list of Annotations that reside at the given
* offset for the given ISourceViewer
*/
public static List<Annotation> getAnnotationsForOffset(ISourceViewer viewer,
final int offset) {
return getAnnotations(viewer, new IPositionPredicate() {
public boolean matchPosition(Position p) {
return offset >= p.offset &&
offset < p.offset + p.length;
}
});
}
/**
* @return true, if the given Position resides at the given
* line of the given IDocument
*/
public static boolean positionIsAtLine(Position position,
IDocument document, int line) {
int offset = position.getOffset();
int length = position.getLength();
if (offset > -1 && length > -1) {
try {
int startLine= document.getLineOfOffset(offset);
int endLine = document.getLineOfOffset(offset+length);
return line >= startLine && line <= endLine;
}
catch (BadLocationException x) {}
}
return false;
}
/**
* @return the list of Annotations on the given ISourceViewer
* that satisfy the given IPositionPredicate and that are worth
* showing to the user as text (e.g., ignoring debugger breakpoint
* annotations and source folding annotations)
*/
public static List<Annotation> getAnnotations(ISourceViewer viewer,
IPositionPredicate posPred) {
IAnnotationModel model = viewer.getAnnotationModel();
if (model == null) {
return null;
}
List<Annotation> annotations = new ArrayList<Annotation>();
Iterator<?> iterator = model.getAnnotationIterator();
Map<Integer,List<Object>> map = new HashMap<Integer,List<Object>>();
while (iterator.hasNext()) {
Annotation annotation = (Annotation) iterator.next();
Position position = model.getPosition(annotation);
if (annotation instanceof MarkerAnnotation) {
try {
if (((MarkerAnnotation) annotation).getMarker().getType()
.equals(PROBLEM_MARKER_ID)) {
continue;
}
}
catch (CoreException e) {
e.printStackTrace();
continue;
}
}
if (position == null ||
!posPred.matchPosition(position)) {
continue;
}
if (annotation instanceof AnnotationBag) {
AnnotationBag bag = (AnnotationBag) annotation;
for (Iterator<?> e = bag.iterator(); e.hasNext(); ) {
Annotation bagAnnotation = (Annotation) e.next();
position = model.getPosition(bagAnnotation);
if (position != null &&
includeAnnotation(bagAnnotation, position) &&
!addAndCheckDuplicateAnnotation(map, bagAnnotation, position))
annotations.add(bagAnnotation);
}
}
else {
if (includeAnnotation(annotation, position) &&
!addAndCheckDuplicateAnnotation(map, annotation, position)) {
annotations.add(annotation);
}
}
}
return annotations;
}
/**
* Check preferences, etc., to determine whether this
* annotation is actually showing. (Don't want to show a
* hover for a non-visible annotation.)
*/
private static boolean includeAnnotation(Annotation annotation,
Position position) {
return !sAnnotationTypesToFilter.contains(annotation.getType());
}
}
/**
* Interface that represents a single-argument predicate taking
* a textual Position. Used by AnnotationUtils to detect annotations
* associated with a particular range or location in source text.
* @author rfuhrer
*/
interface IPositionPredicate {
boolean matchPosition(Position p);
}