package org.jbehave.eclipse.editor.story; import static org.apache.commons.lang.StringUtils.removeStart; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.commons.lang.StringEscapeUtils; import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.ui.PreferenceConstants; import org.eclipse.jface.internal.text.html.BrowserInformationControl; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DefaultInformationControl; 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.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.ISourceViewerExtension2; import org.eclipse.jface.text.source.LineRange; import org.eclipse.jface.text.source.projection.AnnotationBag; import org.eclipse.swt.browser.LocationEvent; import org.eclipse.swt.browser.LocationListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.PartInitException; import org.eclipse.ui.texteditor.MarkerAnnotation; import org.jbehave.eclipse.JBehaveProject; import org.jbehave.eclipse.editor.step.StepJumper; import org.jbehave.eclipse.editor.story.validator.MarkingStoryValidator; import org.jbehave.eclipse.util.New; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SuppressWarnings("restriction") public class StoryAnnotationHover implements IAnnotationHover, IAnnotationHoverExtension { private static Logger log = LoggerFactory.getLogger(StoryAnnotationHover.class); private StoryEditor storyEditor; /** * Creates a new default annotation hover. * @param storyEditor * */ public StoryAnnotationHover(StoryEditor storyEditor) { this.storyEditor = storyEditor; } /* * @see org.eclipse.jface.text.source.IAnnotationHover#getHoverInfo(org.eclipse.jface.text.source.ISourceViewer, int) */ public String getHoverInfo(ISourceViewer sourceViewer, int lineNumber) { String nfo = hoverInfo(sourceViewer, lineNumber); System.out.println("StoryAnnotationHover.enclosing_method(" + nfo + ")"); return nfo; } private String hoverInfo(ISourceViewer sourceViewer, int lineNumber) { List<Annotation> javaAnnotations= getAnnotationsForLine(sourceViewer, lineNumber); if (javaAnnotations != null) { if (javaAnnotations.size() == 1) { // optimization Annotation annotation = javaAnnotations.get(0); String message = getMessageFrom(annotation); if (message != null && message.trim().length() > 0) return formatSingleMessage(message); } else { List<String> messages= new ArrayList<String>(); for(Annotation annotation : javaAnnotations) { String message = getMessageFrom(annotation); if (message != null && message.trim().length() > 0) messages.add(message.trim()); } if (messages.size() == 1) return formatSingleMessage((String)messages.get(0)); if (messages.size() > 1) return formatMultipleMessages(messages); } } return null; } public IInformationControlCreator getHoverControlCreator() { return new IInformationControlCreator() { public IInformationControl createInformationControl( final Shell parent) { if (BrowserInformationControl.isAvailable(parent)) { return newInformationControl(parent); } else { return new DefaultInformationControl(parent, true); } } }; } public boolean canHandleMouseCursor() { return false; } public Object getHoverInfo(ISourceViewer sourceViewer, ILineRange lineRange, int visibleNumberOfLines) { return getHoverInfo(sourceViewer, lineRange.getStartLine()); } public ILineRange getHoverLineRange(ISourceViewer viewer, int lineNumber) { return new LineRange(lineNumber, 1); } private String getMessageFrom(Annotation annotation) { if(annotation instanceof MarkerAnnotation) { String annotationMessage = getMessageFrom((MarkerAnnotation)annotation); if(annotationMessage!=null) return annotationMessage; } //return annotation.getText(); return null; } private String getMessageFrom(MarkerAnnotation annotation) { IMarker marker = annotation.getMarker(); try { if(MarkingStoryValidator.MARKER_ID.equals(marker.getType())) { int intCode = marker.getAttribute(Marks.ERROR_CODE, -1); Marks.Code errorCode = Marks.Code.lookup(intCode, null); if(errorCode==null) return null; switch(errorCode) { case MultipleMatchingSteps: case MultipleMatchingSteps_PrioritySelection: { String html = (String)marker.getAttribute(Marks.STEPS_HTML); String message = (String) marker.getAttribute(Marks.MESSAGE); if (message == null) { message = "<b>Multiple steps matching:</b>"; } message = StringEscapeUtils.escapeHtml(message); return String.format("%s<br><br>%s", message, html); } default: break; } return annotation.getText(); } } catch (CoreException e) { log.warn("Unable to retrieve information from marker (" + annotation.getText() + ")", e); } return null; } /** * Tells whether the annotation should be included in * the computation. * * @param annotation the annotation to test * @return <code>true</code> if the annotation is included in the computation */ protected boolean isIncluded(Annotation annotation) { return true; } /** * Hook method to format the given single message. * <p> * Subclasses can change this to create a different * format like HTML. * </p> * * @param message the message to format * @return the formatted message */ protected String formatSingleMessage(String message) { return message; } /** * Hook method to formats the given messages. * <p> * Subclasses can change this to create a different * format like HTML. * </p> * * @param messages the messages to format * @return the formatted message */ protected String formatMultipleMessages(List<String> messages) { StringBuffer buffer= new StringBuffer(); Iterator<String> e= messages.iterator(); while (e.hasNext()) { buffer.append('\n'); String listItemText= (String) e.next(); buffer.append(listItemText); //$NON-NLS-1$ } return buffer.toString(); } private boolean isRulerLine(Position position, IDocument document, int line) { if (position.getOffset() > -1 && position.getLength() > -1) { try { return line == document.getLineOfOffset(position.getOffset()); } catch (BadLocationException x) { } } return false; } private IAnnotationModel getAnnotationModel(ISourceViewer viewer) { if (viewer instanceof ISourceViewerExtension2) { ISourceViewerExtension2 extension= (ISourceViewerExtension2) viewer; return extension.getVisualAnnotationModel(); } return viewer.getAnnotationModel(); } private boolean includeAnnotation(Annotation annotation, Position position) { if (!isIncluded(annotation)) return false; return true; } @SuppressWarnings("unchecked") private List<Annotation> getAnnotationsForLine(ISourceViewer viewer, int line) { IAnnotationModel model= getAnnotationModel(viewer); if (model == null) return null; IDocument document= viewer.getDocument(); List<Annotation> javaAnnotations= New.arrayList(); Iterator<Annotation> iterator = model.getAnnotationIterator(); while (iterator.hasNext()) { Annotation annotation= (Annotation) iterator.next(); Position position= model.getPosition(annotation); if (position == null) continue; if (!isRulerLine(position, document, line)) continue; if (annotation instanceof AnnotationBag) { AnnotationBag bag= (AnnotationBag) annotation; Iterator<Annotation> e= bag.iterator(); while (e.hasNext()) { annotation= (Annotation) e.next(); position= model.getPosition(annotation); if (position != null && includeAnnotation(annotation, position)) javaAnnotations.add(annotation); } continue; } if (includeAnnotation(annotation, position)) javaAnnotations.add(annotation); } return javaAnnotations; } protected BrowserInformationControl newInformationControl(final Shell parent) { String font= PreferenceConstants.APPEARANCE_JAVADOC_FONT; BrowserInformationControl iControl= new BrowserInformationControl(parent, font, "Press 'F2' for focus") { @Override public Point computeSizeHint() { Point size = super.computeSizeHint(); int x = size.x; if(x<120) x = 120; int y = size.y; if(y<100) y = 100; return new Point(x,y); } }; iControl.addLocationListener(new LocationListener() { public void changing(LocationEvent event) { boolean initial = "about:blank".equals(event.location); if(!initial) { try { String qname = removeStart(event.location, "file:///"); new StepJumper(getProject()).jumpToMethod(qname); } catch (PartInitException e) { log.error("Failed to jump to <" + event.location + ">", e); } catch (JavaModelException e) { log.error("Failed to jump to <" + event.location + ">", e); } } event.doit = initial; } private JBehaveProject getProject() { return storyEditor.getJBehaveProject(); } public void changed(LocationEvent event) { } }); return iControl; } }