// Copyright (c) 2004-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; import java.io.IOException; import java.net.URL; import java.util.Iterator; import net.sf.eclipsefp.haskell.browser.BrowserPlugin; import net.sf.eclipsefp.haskell.browser.items.Documented; import net.sf.eclipsefp.haskell.buildwrapper.BWFacade; import net.sf.eclipsefp.haskell.buildwrapper.BuildWrapperPlugin; import net.sf.eclipsefp.haskell.buildwrapper.types.Location; import net.sf.eclipsefp.haskell.buildwrapper.types.ThingAtPoint; import net.sf.eclipsefp.haskell.ui.HaskellUIPlugin; import net.sf.eclipsefp.haskell.ui.internal.editors.haskell.imports.ImportsManager; import net.sf.eclipsefp.haskell.ui.internal.preferences.editor.IEditorPreferenceNames; import net.sf.eclipsefp.haskell.ui.internal.util.UITexts; import net.sf.eclipsefp.haskell.util.PlatformUtil; import org.apache.commons.lang3.StringEscapeUtils; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.FileLocator; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DefaultTextHover; import org.eclipse.jface.text.IInformationControl; import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextHoverExtension; import org.eclipse.jface.text.ITextHoverExtension2; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.IAnnotationAccessExtension; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess; public class HaskellTextHover extends DefaultTextHover implements ITextHoverExtension, ITextHoverExtension2 { private final HaskellEditor editor; private static final String ERROR_ANNOTATION_TYPE = "org.eclipse.ui.workbench.texteditor.error"; //$NON-NLS-1$ private static final String WARNING_ANNOTATION_TYPE = "org.eclipse.ui.workbench.texteditor.warning"; //$NON-NLS-1$ private static final String SPELLING_ANNOTATION_TYPE = "org.eclipse.ui.workbench.texteditor.spelling"; //$NON-NLS-1$ /** * when name + type is less than this constant in characters, with keep them on the same line */ private static final int SHORT_LENGTH=150; private final DefaultMarkerAnnotationAccess fMarkerAnnotationAccess; public HaskellTextHover( final HaskellEditor editor, final ISourceViewer sourceViewer ) { super( sourceViewer ); this.editor = editor; fMarkerAnnotationAccess = new DefaultMarkerAnnotationAccess(); } @Override public String getHoverInfo( final ITextViewer textViewer, final IRegion hoverRegion ) { // TODO TtC this method does not get called when hovering over a string literal // which leads to potential problem annotation hovers not appearing String hoverInfo = computeProblemInfo( textViewer, hoverRegion,fMarkerAnnotationAccess); if (hoverInfo != null) { return hoverInfo; } return computeThingAtPoint( textViewer, hoverRegion,false ); } /* (non-Javadoc) * @see org.eclipse.jface.text.ITextHoverExtension2#getHoverInfo2(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion) */ @Override public Object getHoverInfo2( final ITextViewer textViewer, final IRegion hoverRegion) { String hoverInfo = computeProblemInfo( textViewer, hoverRegion,fMarkerAnnotationAccess); String tap = computeThingAtPoint( textViewer, hoverRegion,true ); if (tap==null){ return hoverInfo; } else if (hoverInfo==null){ return tap; } // combine markers and thing at point return hoverInfo+"<hr>"+tap; } /* (non-Javadoc) * @see org.eclipse.jface.text.ITextHoverExtension#getHoverControlCreator() */ @Override public IInformationControlCreator getHoverControlCreator() { return new IInformationControlCreator() { @Override public IInformationControl createInformationControl( final Shell parent ) { return new HaskellInformationControl(parent); //return new DefaultInformationControl( parent, false ); // for bold-only tooltips } } ; } private static String toHTMLString(final String txt){ String txt2=StringEscapeUtils.escapeHtml4( txt ); txt2=txt2.replace( PlatformUtil.NL, "<br/>" ); txt2=txt2.replace( "\n", "<br/>" ); txt2=txt2.replace( "\r", "<br/>" ); txt2=txt2.replace( " ", " " ); txt2="<nobr>"+txt2.replace("<br/>","</nobr><br/><nobr>")+"</nobr>"; return txt2; } private static String toHTMLString(final String txt,final boolean html){ if (html){ return toHTMLString( txt ); } return txt; } @SuppressWarnings ( "unchecked" ) public static String computeProblemInfo( final ITextViewer textViewer, final IRegion hoverRegion,final IAnnotationAccessExtension fMarkerAnnotationAccess) { if (textViewer instanceof ISourceViewer) { IAnnotationModel annotationModel = ((ISourceViewer)textViewer).getAnnotationModel(); if (annotationModel!=null){ // collect all messages StringBuilder sb=new StringBuilder(); Iterator<Annotation> i = annotationModel.getAnnotationIterator(); while (i.hasNext()) { Annotation a = i.next(); String type = a.getType(); if (a.getText()!=null && (fMarkerAnnotationAccess.isSubtype( type, ERROR_ANNOTATION_TYPE ) || fMarkerAnnotationAccess.isSubtype( type, WARNING_ANNOTATION_TYPE )|| fMarkerAnnotationAccess.isSubtype( type, SPELLING_ANNOTATION_TYPE ))) { Position p = annotationModel.getPosition( a ); if (p.overlapsWith( hoverRegion.getOffset(), hoverRegion.getLength() )) { // add a nice icon String img=""; String txt=toHTMLString(a.getText()); try { URL url =FileLocator.toFileURL( HaskellUIPlugin.getDefault().getBundle().getResource( fMarkerAnnotationAccess.isSubtype( type, ERROR_ANNOTATION_TYPE )?"icons/obj16/error_obj.gif":"icons/obj16/warning_obj.gif")); img="<img src=\"" + url.toString()+"\" style=\"vertical-align:-4;\"/>"; } catch( IOException ioe){ HaskellUIPlugin.log( ioe ); } String div="<div style=\"font-family: verdana;padding:2px\">"+img+ txt + "</div>"; // put errors first if (fMarkerAnnotationAccess.isSubtype( type, ERROR_ANNOTATION_TYPE )){ if (sb.length()>0){ sb.insert(0,"<hr/>"); } sb.insert(0, div ); } else { if (sb.length()>0){ sb.append("<hr/>"); } sb.append( div ); } } } } if (sb.length()>0){ return sb.toString(); } } } return null; } protected String computeThingAtPoint( final ITextViewer textViewer, final IRegion hoverRegion,final boolean html ) { if (editor==null){ return null; } IFile file = editor.findFile(); if (file != null) { try { Location location = new Location(file.getLocation().toOSString(), textViewer.getDocument(), hoverRegion); //IDocument theDocument = textViewer.getDocument(); //ScionInstance scionInstance = ScionPlugin.getScionInstance( file ); BWFacade f=BuildWrapperPlugin.getFacade( file.getProject() ); if (f != null) { //long t0=System.currentTimeMillis(); //try { ThingAtPoint tap=f.getThingAtPoint(file,location); if (tap!=null){ StringBuilder sb=new StringBuilder(); sb.append(html ? "<div style='padding:2px'><nobr>" : ""); sb.append(toHTMLString( tap.getName(),html)); String moduleColor = HaskellUIPlugin.getDefault().getPreferenceStore().getString( IEditorPreferenceNames.EDITOR_CON_COLOR ); if (tap.getType()!=null){ String t=tap.getType(); // if name + type has less than SHORT_LENGTH characters, we keep it on one line boolean tshort=tap.getName().length()+t.length()<SHORT_LENGTH; sb.append(html && tshort ? "<nobr>" : ""); sb.append(" :: "); sb.append(html ? "<b>" : ""); sb.append(tap.getType()); sb.append(html ? "</b>" : ""); sb.append(html && tshort ? "</nobr>" : ""); } sb.append(html ? "</nobr>" : ""); if (tap.getModule()!=null){ sb.append(html ? "<br/>" : "\n"); sb.append(html ? "<span style='color: grey'>"+UITexts.editor_textHover_module+"</span> <span style='color:rgb("+moduleColor+"); font-weight: bold'><nobr>" : ""); sb.append(tap.getModule()); sb.append(html ? "</nobr></span>" : ""); } sb.append(html ? "</div>" : ""); ImportsManager im=editor.getImportsManager(); Documented d=im.getDeclarations().get( tap.getModule()+"."+tap.getName() ); if (d==null){ d=im.getDeclarations().get( tap.getName() ); } String doc=BrowserPlugin.getDoc( tap.getModule(), tap.getName(), d ); if (doc!=null && doc.length()>0){ sb.append(html ? "<hr/>" : "\n"); sb.append(html ? "<div style='padding:2px'>" : ""); sb.append(doc); sb.append(html ? "</div>" : ""); } return sb.toString(); } //} finally { // long t1=System.currentTimeMillis(); // HaskellUIPlugin.log( "computethingAtPoint: "+(t1-t0)+"ms", IStatus.INFO ); //} } } catch (BadLocationException ex) { HaskellUIPlugin.log( UITexts.editor_textHover_error, ex ); } } return null; } @Override protected boolean isIncluded( final Annotation annotation ) { return false; } // private class ComputeThingAtPoint extends FileCommandGroup { // private String thing; // private final ITextViewer textViewer; // private final IFile theFile; // private final IRegion hoverRegion; // // public ComputeThingAtPoint( final String jobName, final IFile theFile, final ITextViewer textviewer, final IRegion hoverRegion ) { // super( jobName, theFile, Job.SHORT ); // this.thing = null; // this.textViewer = textviewer; // this.theFile = theFile; // this.hoverRegion = hoverRegion; // } // // @Override // protected IStatus run( final IProgressMonitor monitor ) { // IDocument document = textViewer.getDocument(); // Location location; // try { // location = new Location(theFile.getLocation().toOSString(), document, hoverRegion); // ScionInstance scionInstance = ScionPlugin.getScionInstance( theFile ); // if (scionInstance != null) { // thing = scionInstance.thingAtPoint(document,location, false, true); // } // } catch (BadLocationException ex) { // HaskellUIPlugin.log( UITexts.editor_textHover_error, ex ); // } // // return Status.OK_STATUS; // } // // public String getThing() { // return thing; // } // } }