// Copyright (c) 2003-2008 by Leif Frenzel - see http://leiffrenzel.de
// This code is made available under the terms of the Eclipse Public License,
// version 1.0 (EPL). See http://www.eclipse.org/legal/epl-v10.html
package net.sf.eclipsefp.haskell.ui.internal.editors.haskell.text;
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 net.sf.eclipsefp.haskell.ui.HaskellUIPlugin;
import net.sf.eclipsefp.haskell.ui.internal.editors.haskell.HaskellInformationControl;
import net.sf.eclipsefp.haskell.ui.internal.editors.haskell.HaskellTextHover;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationHover;
import org.eclipse.jface.text.source.IAnnotationHoverExtension;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.ILineRange;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.LineRange;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess;
/** <p>Determines all markers for the given line and collects, concatenates,
* and formats their messages.</p>
*
* @author Leif Frenzel
*/
public class AnnotationHover implements IAnnotationHover,IAnnotationHoverExtension {
private final DefaultMarkerAnnotationAccess fMarkerAnnotationAccess;
/**
*
*/
public AnnotationHover() {
fMarkerAnnotationAccess = new DefaultMarkerAnnotationAccess();
}
// interface methods of IAnnotationHover
////////////////////////////////////////
@Override
public String getHoverInfo( final ISourceViewer sv, final int line ) {
String result = null;
List<Annotation> annotations = getAnnotations( sv, line );
if( annotations != null ) {
if( annotations.size() == 1 ) {
Annotation annotation = annotations.get( 0 );
result = formatAnnotation( annotation );
} else {
List<String> messages = collectMessages( annotations );
if( messages.size() == 1 ) {
result = messages.get( 0 );
} else {
result = formatMultipleMessages( messages );
}
}
}
return result;
}
/* (non-Javadoc)
* @see org.eclipse.jface.text.source.IAnnotationHoverExtension#canHandleMouseCursor()
*/
@Override
public boolean canHandleMouseCursor() {
return true;
}
/* (non-Javadoc)
* @see org.eclipse.jface.text.source.IAnnotationHoverExtension#getHoverControlCreator()
*/
@Override
public IInformationControlCreator getHoverControlCreator() {
return new IInformationControlCreator() {
@Override
public IInformationControl createInformationControl( final Shell parent ) {
return new HaskellInformationControl(parent);
}
} ;
}
/* (non-Javadoc)
* @see org.eclipse.jface.text.source.IAnnotationHoverExtension#getHoverInfo(org.eclipse.jface.text.source.ISourceViewer, org.eclipse.jface.text.source.ILineRange, int)
*/
@Override
public Object getHoverInfo( final ISourceViewer paramISourceViewer,
final ILineRange paramILineRange, final int paramInt ) {
try {
int offset=paramISourceViewer.getDocument().getLineOffset( paramILineRange.getStartLine() );
int length=paramISourceViewer.getDocument().getLineLength( paramILineRange.getStartLine());
return HaskellTextHover.computeProblemInfo( paramISourceViewer, new Region( offset, length ), fMarkerAnnotationAccess );
} catch (BadLocationException ble){
HaskellUIPlugin.log( ble );
return null;
}
}
/* (non-Javadoc)
* @see org.eclipse.jface.text.source.IAnnotationHoverExtension#getHoverLineRange(org.eclipse.jface.text.source.ISourceViewer, int)
*/
@Override
public ILineRange getHoverLineRange( final ISourceViewer paramISourceViewer,
final int paramInt ) {
return new LineRange( paramInt, 1 );
}
// helping methods
//////////////////
private String formatAnnotation( final Annotation annotation ) {
String result = null;
String message = annotation.getText();
if( message != null && message.trim().length() > 0 ) {
result = message.trim();
}
return result;
}
private List<String> collectMessages( final List<Annotation> annotations ) {
List<String> result = new ArrayList<>();
Iterator<Annotation> it = annotations.iterator();
while( it.hasNext() ) {
result.add( formatAnnotation( it.next() ) );
}
return result;
}
private String formatMultipleMessages( final List<String> messages ) {
StringBuffer sb = new StringBuffer();
sb.append( "Multiple markers at this line:\n" );
Iterator<String> iter = messages.iterator();
while( iter.hasNext() ) {
sb.append( "\n - " );
sb.append( iter.next() );
}
return sb.toString();
}
private int compareRulerLine( final Position position,
final IDocument document,
final int line ) {
int result = 0;
if( position.getOffset() > -1 && position.getLength() > -1 ) {
try {
int annotationLine = document.getLineOfOffset( position.getOffset() );
int posEnd = position.getOffset() + position.getLength();
if( line == annotationLine ) {
result = 1;
} else if( annotationLine < line
&& line <= document.getLineOfOffset( posEnd ) ) {
result = 2;
}
} catch( BadLocationException badlox ) {
// ignored
}
}
return result;
}
private List<Annotation> getAnnotations( final ISourceViewer viewer,
final int line ) {
List<Annotation> result = null;
IAnnotationModel model = viewer.getAnnotationModel();
if( model != null ) {
result = new ArrayList<>();
IDocument document = viewer.getDocument();
Iterator<?> it = model.getAnnotationIterator();
Map<Position, Set<String>> msgs = new HashMap<>();
while( it.hasNext() ) {
Object obj = it.next();
if( obj instanceof Annotation ) {
Annotation ann = (Annotation)obj;
// Don't add ocurrences annotations
if (ann.getType().equals("net.sf.eclipsefp.haskell.ui.occurrences")) {
continue;
}
Position position = model.getPosition( ( Annotation )obj );
if( position == null ) {
continue;
}
String text = ( ( Annotation )obj ).getText();
if( isDuplicate( msgs, position, text ) ) {
continue;
}
switch( compareRulerLine( position, document, line ) ) {
case 1:
result.add( ( Annotation ) obj );
break;
}
}
}
}
return result;
}
// side-effect! this doesn't just check, it also adds new msgs by default
private boolean isDuplicate(
final Map<Position, Set<String>> messagesAtPosition,
final Position position,
final String message ) {
boolean result = false;
if( messagesAtPosition.containsKey( position ) ) {
Set<String> msgs = messagesAtPosition.get( position );
result = msgs.contains( message );
msgs.add( message );
} else {
Set<String> msgs = new HashSet<>();
msgs.add( message );
messagesAtPosition.put( position, msgs );
}
return result;
}
}