/******************************************************************************* * Copyright (c) 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Zend Technologies *******************************************************************************/ package org.eclipse.php.internal.ui.editor; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.*; import org.eclipse.core.resources.IMarker; 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.ISourceViewerExtension2; import org.eclipse.jface.text.source.projection.AnnotationBag; import org.eclipse.php.internal.core.Logger; import org.eclipse.php.internal.core.ast.util.Util; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.texteditor.MarkerAnnotation; import org.eclipse.wst.sse.core.utils.StringUtils; import org.eclipse.wst.sse.ui.internal.ITemporaryAnnotation; import org.eclipse.wst.sse.ui.internal.SSEUIMessages; import org.eclipse.wst.sse.ui.internal.StructuredTextAnnotationHover; import org.eclipse.wst.sse.ui.internal.StructuredTextLineBreakingReader; /** * Override the StructuredTextAnnotationHover Reason: collect all the marker * messages instead of one message. All the parent methods are private so we * copied them. * * @author moshe, 2007 */ public class PHPStructuredTextAnnotationHover extends StructuredTextAnnotationHover { /** * Provides a set of convenience methods for creating HTML pages. Taken from * org.eclipse.jdt.internal.ui.text.HTMLPrinter */ class HTMLPrinter { HTMLPrinter() { } void addBullet(StringBuilder buffer, String bullet) { if (bullet != null) { buffer.append("<li>"); //$NON-NLS-1$ buffer.append(bullet); buffer.append("</li>"); //$NON-NLS-1$ } } void addPageEpilog(StringBuilder buffer) { buffer.append("</font></body></html>"); //$NON-NLS-1$ } void addPageProlog(StringBuilder buffer) { insertPageProlog(buffer, buffer.length()); } void addParagraph(StringBuilder buffer, Reader paragraphReader) { if (paragraphReader != null) addParagraph(buffer, read(paragraphReader)); } void addParagraph(StringBuilder buffer, String paragraph) { if (paragraph != null) { buffer.append("<p>"); //$NON-NLS-1$ buffer.append(paragraph); } } void addSmallHeader(StringBuilder buffer, String header) { if (header != null) { buffer.append("<h5>"); //$NON-NLS-1$ buffer.append(header); buffer.append("</h5>"); //$NON-NLS-1$ } } String convertToHTMLContent(String content) { content = replace(content, '<', "<"); //$NON-NLS-1$ return replace(content, '>', ">"); //$NON-NLS-1$ } void endBulletList(StringBuilder buffer) { buffer.append("</ul>"); //$NON-NLS-1$ } void insertPageProlog(StringBuilder buffer, int position) { buffer.insert(position, "<html><body text=\"#000000\" bgcolor=\"#FFFF88\"><font size=-1>"); //$NON-NLS-1$ } String read(Reader rd) { StringBuilder buffer = new StringBuilder(); char[] readBuffer = new char[2048]; try { int n = rd.read(readBuffer); while (n > 0) { buffer.append(readBuffer, 0, n); n = rd.read(readBuffer); } return buffer.toString(); } catch (IOException x) { } return null; } private String replace(String text, char c, String s) { int previous = 0; int current = text.indexOf(c, previous); if (current == -1) return text; StringBuilder buffer = new StringBuilder(); while (current > -1) { buffer.append(text.substring(previous, current)); buffer.append(s); previous = current + 1; current = text.indexOf(c, previous); } buffer.append(text.substring(previous)); return buffer.toString(); } void startBulletList(StringBuilder buffer) { buffer.append("<ul>"); //$NON-NLS-1$ } } private HTMLPrinter printer = new HTMLPrinter(); /** * Returns the distance to the ruler line. */ private int compareRulerLine(Position position, IDocument document, int line) { if (position.getOffset() > -1 && position.getLength() > -1) { try { int markerLine = document.getLineOfOffset(position.getOffset()); if (line == markerLine) return 1; if (markerLine <= line && line <= document.getLineOfOffset(position.getOffset() + position.getLength())) return 2; } catch (BadLocationException x) { } } return 0; } /* * Formats the message of this hover to fit onto the screen. */ private String formatHoverText(String text, ISourceViewer sourceViewer) { String result = null; String lineDelim = null; try { lineDelim = sourceViewer.getDocument().getLineDelimiter(0); } catch (org.eclipse.jface.text.BadLocationException exception) { // skip, just use default } if (lineDelim == null) { lineDelim = Util.getLineSeparator(text, null); } Display display = sourceViewer.getTextWidget().getDisplay(); // replace special characters in text with html entity (like <, >, & // to <, >, &&;) text = StringUtils.convertToHTMLContent(text); Reader textReader = new StringReader(text); GC gc = new GC(display); try { StringBuilder buf = new StringBuilder(); StructuredTextLineBreakingReader reader = new StructuredTextLineBreakingReader(textReader, gc, getHoverWidth(display)); String line = reader.readLine(); while (line != null) { if (buf.length() != 0) { buf.append(lineDelim); } buf.append(line); line = reader.readLine(); } result = buf.toString(); } catch (IOException exception) { Logger.logException(exception); } finally { gc.dispose(); } return result; } /* * Formats several message as HTML text. */ private String formatMultipleHoverText(List<String> messages) { StringBuilder buffer = new StringBuilder(); printer.addPageProlog(buffer); printer.addParagraph(buffer, SSEUIMessages.Multiple_errors); // $NON-NLS-1$ printer.startBulletList(buffer); Iterator<String> e = messages.iterator(); while (e.hasNext()) printer.addBullet(buffer, printer.convertToHTMLContent(e.next())); printer.endBulletList(buffer); printer.addPageEpilog(buffer); return buffer.toString(); } @Override public String getHoverInfo(ISourceViewer sourceViewer, int lineNumber) { // get all the marker messages List<String> messages = dropDuplicateMessages(getMarkerMessages(sourceViewer, lineNumber)); List<ITemporaryAnnotation> temporaryAnnotations = getTemporaryAnnotationsForLine(sourceViewer, lineNumber); for (int i = 0; i < temporaryAnnotations.size(); i++) { String message = ((Annotation) temporaryAnnotations.get(i)).getText(); if (message != null) { boolean duplicated = false; for (int j = 0; j < messages.size(); j++) duplicated = duplicated || messages.get(j).equals(message); if (!duplicated) { messages.add(message); } } else { messages.add(((ITemporaryAnnotation) temporaryAnnotations.get(i)).toString()); } } if (messages.size() > 1) return formatMultipleHoverText(messages); else if (messages.size() > 0) return formatHoverText(messages.get(0).toString(), sourceViewer); else return null; } private List<String> dropDuplicateMessages(List<String> messages) { Set<String> rt = new HashSet<>(); for (Iterator<String> i = messages.iterator(); i.hasNext();) { String message = i.next(); if (!rt.contains(message)) { rt.add(message); } } return new ArrayList<>(rt); } private int getHoverWidth(Display display) { Rectangle displayBounds = display.getBounds(); int hoverWidth = displayBounds.width - (display.getCursorLocation().x - displayBounds.x); hoverWidth -= 12; // XXX: Add some space to the border, Revisit if (hoverWidth < 200) { hoverWidth = 200; } return hoverWidth; } /** * Returns all marker messages which includes the ruler's line of activity. */ private List<String> getMarkerMessages(ISourceViewer viewer, int line) { IDocument document = viewer.getDocument(); IAnnotationModel model = getAnnotationModel(viewer); List<String> messages = new ArrayList<>(); if (model == null) { return messages; } Map messagesAtPosition = new HashMap(); Iterator<Annotation> e = model.getAnnotationIterator(); while (e.hasNext()) { Annotation a = e.next(); Position position = model.getPosition(a); if (position == null) { continue; } if (compareRulerLine(position, document, line) == 1) { if (a instanceof AnnotationBag) { AnnotationBag bag = (AnnotationBag) a; Iterator<Annotation> iterator = bag.iterator(); while (iterator.hasNext()) { Annotation annotation = (Annotation) iterator.next(); addText(model, annotation, messages, messagesAtPosition); } } else { addText(model, a, messages, messagesAtPosition); } } } return messages; } private void addText(IAnnotationModel model, Annotation annotation, List<String> messages, Map messagesAtPosition) { Position position = model.getPosition(annotation); if (position != null && includeAnnotation(annotation, position, messagesAtPosition)) { String text = getText(annotation); if (text != null) { messages.add(text); } } } private String getText(Annotation a) { String text = null; if (a instanceof MarkerAnnotation) { IMarker marker = ((MarkerAnnotation) a).getMarker(); text = marker.getAttribute(IMarker.MESSAGE, (String) null); } else { text = a.getText(); } return text; } /** * Returns one marker which includes the ruler's line of activity. */ private List<ITemporaryAnnotation> getTemporaryAnnotationsForLine(ISourceViewer viewer, int line) { IDocument document = viewer.getDocument(); IAnnotationModel model = viewer.getAnnotationModel(); if (model == null) return null; List<ITemporaryAnnotation> annotations = new ArrayList<>(); Iterator e = model.getAnnotationIterator(); while (e.hasNext()) { Object o = e.next(); if (o instanceof ITemporaryAnnotation) { ITemporaryAnnotation a = (ITemporaryAnnotation) o; Position position = model.getPosition((Annotation) a); if (position == null) continue; if (compareRulerLine(position, document, line) == 1) { annotations.add(a); } } } return annotations; } /** * Copy from DefaultAnnotationHover */ private boolean includeAnnotation(Annotation annotation, Position position, Map messagesAtPosition) { if (!isIncluded(annotation)) { return false; } String text = annotation.getText(); return (text != null && !isDuplicateAnnotation(messagesAtPosition, position, text)); } /** * Copy from DefaultAnnotationHover */ private boolean isDuplicateAnnotation(Map messagesAtPosition, Position position, String message) { if (messagesAtPosition.containsKey(position)) { Object value = messagesAtPosition.get(position); if (message.equals(value)) { return true; } if (value instanceof List) { List messages = (List) value; if (messages.contains(message)) { return true; } messages.add(message); } else { List<Object> messages = new ArrayList<>(); messages.add(value); messages.add(message); messagesAtPosition.put(position, messages); } } else { messagesAtPosition.put(position, message); } return false; } /** * Copy from DefaultAnnotationHover */ private IAnnotationModel getAnnotationModel(ISourceViewer viewer) { if (viewer instanceof ISourceViewerExtension2) { ISourceViewerExtension2 extension = (ISourceViewerExtension2) viewer; return extension.getVisualAnnotationModel(); } return viewer.getAnnotationModel(); } }