Java tutorial
package com.redhat.ceylon.eclipse.code.hover; /******************************************************************************* * Copyright (c) 2000, 2012 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 * Genady Beryozkin <eclipse@genady.org> - [hovering] tooltip for constant string does not show constant value - https://bugs.eclipse.org/bugs/show_bug.cgi?id=85382 *******************************************************************************/ import static com.redhat.ceylon.eclipse.code.browser.BrowserInformationControl.isAvailable; import static com.redhat.ceylon.eclipse.code.complete.CodeCompletions.getDocDescriptionFor; import static com.redhat.ceylon.eclipse.code.complete.CodeCompletions.isVariable; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.getInitialValueDescription; import static com.redhat.ceylon.eclipse.code.editor.Navigation.gotoDeclaration; import static com.redhat.ceylon.eclipse.code.html.HTMLPrinter.addPageEpilog; import static com.redhat.ceylon.eclipse.code.html.HTMLPrinter.convertToHTMLContent; import static com.redhat.ceylon.eclipse.code.html.HTMLPrinter.insertPageProlog; import static com.redhat.ceylon.eclipse.code.html.HTMLPrinter.toHex; import static com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider.getLabel; import static com.redhat.ceylon.eclipse.code.resolve.JavaHyperlinkDetector.getJavaElement; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getModelLoader; import static com.redhat.ceylon.eclipse.util.Highlights.ANNOTATIONS; import static com.redhat.ceylon.eclipse.util.Highlights.CHARS; import static com.redhat.ceylon.eclipse.util.Highlights.NUMBERS; import static com.redhat.ceylon.eclipse.util.Highlights.STRINGS; import static com.redhat.ceylon.eclipse.util.Highlights.getCurrentThemeColor; import static com.redhat.ceylon.eclipse.util.Nodes.findNode; import static com.redhat.ceylon.eclipse.util.Nodes.getReferencedDeclaration; import static com.redhat.ceylon.eclipse.util.Nodes.getReferencedNode; import static java.lang.Character.codePointCount; import static java.lang.Float.parseFloat; import static java.lang.Integer.parseInt; import static org.eclipse.jdt.internal.ui.JavaPluginImages.setLocalImageDescriptors; import static org.eclipse.jdt.ui.PreferenceConstants.APPEARANCE_JAVADOC_FONT; import static org.eclipse.ui.ISharedImages.IMG_TOOL_BACK; import static org.eclipse.ui.ISharedImages.IMG_TOOL_BACK_DISABLED; import static org.eclipse.ui.ISharedImages.IMG_TOOL_FORWARD; import static org.eclipse.ui.ISharedImages.IMG_TOOL_FORWARD_DISABLED; import static org.eclipse.ui.PlatformUI.getWorkbench; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IPath; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2; import org.eclipse.jdt.ui.JavaUI; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.ToolBarManager; import org.eclipse.jface.text.AbstractReusableInformationControlCreator; 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.IInputChangedListener; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.ITextViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.browser.LocationEvent; import org.eclipse.swt.browser.LocationListener; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; import com.github.rjeschke.txtmark.Configuration; import com.github.rjeschke.txtmark.Configuration.Builder; import com.github.rjeschke.txtmark.Processor; import com.redhat.ceylon.cmr.api.JDKUtils; import com.redhat.ceylon.cmr.api.ModuleSearchResult.ModuleDetails; import com.redhat.ceylon.compiler.typechecker.TypeChecker; import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit; import com.redhat.ceylon.compiler.typechecker.model.Class; import com.redhat.ceylon.compiler.typechecker.model.ClassOrInterface; import com.redhat.ceylon.compiler.typechecker.model.Constructor; import com.redhat.ceylon.compiler.typechecker.model.Declaration; import com.redhat.ceylon.compiler.typechecker.model.Functional; import com.redhat.ceylon.compiler.typechecker.model.Interface; import com.redhat.ceylon.compiler.typechecker.model.Method; import com.redhat.ceylon.compiler.typechecker.model.MethodOrValue; import com.redhat.ceylon.compiler.typechecker.model.Module; import com.redhat.ceylon.compiler.typechecker.model.NothingType; import com.redhat.ceylon.compiler.typechecker.model.Package; import com.redhat.ceylon.compiler.typechecker.model.Parameter; import com.redhat.ceylon.compiler.typechecker.model.ParameterList; import com.redhat.ceylon.compiler.typechecker.model.ProducedReference; import com.redhat.ceylon.compiler.typechecker.model.ProducedType; import com.redhat.ceylon.compiler.typechecker.model.ProducedTypedReference; import com.redhat.ceylon.compiler.typechecker.model.Referenceable; import com.redhat.ceylon.compiler.typechecker.model.Scope; import com.redhat.ceylon.compiler.typechecker.model.TypeAlias; import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration; import com.redhat.ceylon.compiler.typechecker.model.TypeParameter; import com.redhat.ceylon.compiler.typechecker.model.TypedDeclaration; import com.redhat.ceylon.compiler.typechecker.model.Unit; import com.redhat.ceylon.compiler.typechecker.model.Value; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.compiler.typechecker.tree.Tree.AnonymousAnnotation; import com.redhat.ceylon.compiler.typechecker.tree.Tree.CompilationUnit; import com.redhat.ceylon.compiler.typechecker.util.ProducedTypeNamePrinter; import com.redhat.ceylon.eclipse.code.browser.BrowserInformationControl; import com.redhat.ceylon.eclipse.code.browser.BrowserInput; import com.redhat.ceylon.eclipse.code.correct.ExtractFunctionProposal; import com.redhat.ceylon.eclipse.code.correct.ExtractValueProposal; import com.redhat.ceylon.eclipse.code.correct.SpecifyTypeProposal; import com.redhat.ceylon.eclipse.code.editor.CeylonEditor; import com.redhat.ceylon.eclipse.code.html.HTML; import com.redhat.ceylon.eclipse.code.html.HTMLPrinter; import com.redhat.ceylon.eclipse.code.parse.CeylonParseController; import com.redhat.ceylon.eclipse.code.search.FindAssignmentsAction; import com.redhat.ceylon.eclipse.code.search.FindReferencesAction; import com.redhat.ceylon.eclipse.code.search.FindRefinementsAction; import com.redhat.ceylon.eclipse.code.search.FindSubtypesAction; import com.redhat.ceylon.eclipse.core.model.CeylonUnit; import com.redhat.ceylon.eclipse.core.model.JDTModelLoader; import com.redhat.ceylon.eclipse.util.Nodes; import com.redhat.ceylon.eclipse.util.UnlinkedSpanEmitter; public class DocumentationHover extends SourceInfoHover { private static final String smallerSize = "90%"; private static final String annotationSize = "85%"; private static final String largerSize = "103%"; public DocumentationHover(CeylonEditor editor) { super(editor); } final class CeylonLocationListener implements LocationListener { private final BrowserInformationControl control; CeylonLocationListener(BrowserInformationControl control) { this.control = control; } @Override public void changing(LocationEvent event) { String location = event.location; //necessary for windows environment (fix for blank page) //somehow related to this: https://bugs.eclipse.org/bugs/show_bug.cgi?id=129236 if (!"about:blank".equals(location)) { event.doit = false; } handleLink(location); /*else if (location.startsWith("javadoc:")) { final DocBrowserInformationControlInput input = (DocBrowserInformationControlInput) control.getInput(); int beginIndex = input.getHtml().indexOf("javadoc:")+8; final String handle = input.getHtml().substring(beginIndex, input.getHtml().indexOf("\"",beginIndex)); new Job("Fetching Javadoc") { @Override protected IStatus run(IProgressMonitor monitor) { final IJavaElement elem = JavaCore.create(handle); try { final String javadoc = JavadocContentAccess2.getHTMLContent((IMember) elem, true); if (javadoc!=null) { PlatformUI.getWorkbench().getProgressService() .runInUI(editor.getSite().getWorkbenchWindow(), new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { StringBuilder sb = new StringBuilder(); HTMLPrinter.insertPageProlog(sb, 0, getStyleSheet()); appendJavadoc(elem, javadoc, sb); HTMLPrinter.addPageEpilog(sb); control.setInput(new DocBrowserInformationControlInput(input, null, sb.toString(), 0)); } }, null); } } catch (Exception e) { e.printStackTrace(); } return Status.OK_STATUS; } }.schedule(); }*/ } private void handleLink(String location) { if (location.startsWith("dec:")) { Referenceable target = getLinkedModel(editor, location); if (target != null) { close(control); //FIXME: should have protocol to hide, rather than dispose gotoDeclaration(target, editor); } } else if (location.startsWith("doc:")) { Referenceable target = getLinkedModel(editor, location); if (target != null) { control.setInput(getHoverInfo(target, control.getInput(), editor, null)); } } else if (location.startsWith("ref:")) { Referenceable target = getLinkedModel(editor, location); close(control); new FindReferencesAction(editor, (Declaration) target).run(); } else if (location.startsWith("sub:")) { Referenceable target = getLinkedModel(editor, location); close(control); new FindSubtypesAction(editor, (Declaration) target).run(); } else if (location.startsWith("act:")) { Referenceable target = getLinkedModel(editor, location); close(control); new FindRefinementsAction(editor, (Declaration) target).run(); } else if (location.startsWith("ass:")) { Referenceable target = getLinkedModel(editor, location); close(control); new FindAssignmentsAction(editor, (Declaration) target).run(); } else if (location.startsWith("stp:")) { close(control); CompilationUnit rn = editor.getParseController().getRootNode(); Node node = Nodes.findNode(rn, Integer.parseInt(location.substring(4))); SpecifyTypeProposal.createProposal(rn, node, editor) .apply(editor.getParseController().getDocument()); } else if (location.startsWith("exv:")) { close(control); new ExtractValueProposal(editor).apply(editor.getParseController().getDocument()); } else if (location.startsWith("exf:")) { close(control); new ExtractFunctionProposal(editor).apply(editor.getParseController().getDocument()); } } @Override public void changed(LocationEvent event) { } } /** * Action to go back to the previous input in the hover control. */ static final class BackAction extends Action { private final BrowserInformationControl fInfoControl; public BackAction(BrowserInformationControl infoControl) { fInfoControl = infoControl; setText("Back"); ISharedImages images = getWorkbench().getSharedImages(); setImageDescriptor(images.getImageDescriptor(IMG_TOOL_BACK)); setDisabledImageDescriptor(images.getImageDescriptor(IMG_TOOL_BACK_DISABLED)); update(); } @Override public void run() { BrowserInput previous = (BrowserInput) fInfoControl.getInput().getPrevious(); if (previous != null) { fInfoControl.setInput(previous); } } public void update() { BrowserInput current = fInfoControl.getInput(); if (current != null && current.getPrevious() != null) { BrowserInput previous = current.getPrevious(); setToolTipText("Back to " + previous.getInputName()); setEnabled(true); } else { setToolTipText("Back"); setEnabled(false); } } } /** * Action to go forward to the next input in the hover control. */ static final class ForwardAction extends Action { private final BrowserInformationControl fInfoControl; public ForwardAction(BrowserInformationControl infoControl) { fInfoControl = infoControl; setText("Forward"); ISharedImages images = getWorkbench().getSharedImages(); setImageDescriptor(images.getImageDescriptor(IMG_TOOL_FORWARD)); setDisabledImageDescriptor(images.getImageDescriptor(IMG_TOOL_FORWARD_DISABLED)); update(); } @Override public void run() { BrowserInput next = (BrowserInput) fInfoControl.getInput().getNext(); if (next != null) { fInfoControl.setInput(next); } } public void update() { BrowserInput current = fInfoControl.getInput(); if (current != null && current.getNext() != null) { setToolTipText("Forward to " + current.getNext().getInputName()); setEnabled(true); } else { setToolTipText("Forward"); setEnabled(false); } } } /** * Action that shows the current hover contents in the Javadoc view. */ /*private static final class ShowInDocViewAction extends Action { private final BrowserInformationControl fInfoControl; public ShowInJavadocViewAction(BrowserInformationControl infoControl) { fInfoControl= infoControl; setText("Show in Ceylondoc View"); setImageDescriptor(JavaPluginImages.DESC_OBJS_JAVADOCTAG); //TODO: better image } @Override public void run() { DocBrowserInformationControlInput infoInput= (DocBrowserInformationControlInput) fInfoControl.getInput(); //TODO: check cast fInfoControl.notifyDelayedInputChange(null); fInfoControl.dispose(); //FIXME: should have protocol to hide, rather than dispose try { JavadocView view= (JavadocView) JavaPlugin.getActivePage().showView(JavaUI.ID_JAVADOC_VIEW); view.setInput(infoInput); } catch (PartInitException e) { JavaPlugin.log(e); } } }*/ /** * Action that opens the current hover input element. */ final class OpenDeclarationAction extends Action { private final BrowserInformationControl fInfoControl; public OpenDeclarationAction(BrowserInformationControl infoControl) { fInfoControl = infoControl; setText("Open Declaration"); setLocalImageDescriptors(this, "goto_input.gif"); } @Override public void run() { close(fInfoControl); //FIXME: should have protocol to hide, rather than dispose CeylonBrowserInput input = (CeylonBrowserInput) fInfoControl.getInput(); gotoDeclaration(getLinkedModel(editor, input.getAddress()), editor); } } private static void close(BrowserInformationControl control) { control.notifyDelayedInputChange(null); control.dispose(); } /** * The hover control creator. */ private IInformationControlCreator fHoverControlCreator; /** * The presentation control creator. */ private IInformationControlCreator fPresenterControlCreator; private IInformationControlCreator getInformationPresenterControlCreator() { if (fPresenterControlCreator == null) fPresenterControlCreator = new PresenterControlCreator(this); return fPresenterControlCreator; } @Override public IInformationControlCreator getHoverControlCreator() { return getHoverControlCreator("F2 for focus"); } public IInformationControlCreator getHoverControlCreator(String statusLineMessage) { if (fHoverControlCreator == null) { fHoverControlCreator = new HoverControlCreator(this, getInformationPresenterControlCreator(), statusLineMessage); } return fHoverControlCreator; } void addLinkListener(final BrowserInformationControl control) { control.addLocationListener(new CeylonLocationListener(control)); } public static Referenceable getLinkedModel(CeylonEditor editor, String location) { if (location == null) { return null; } else if (location.equals("doc:ceylon.language:ceylon.language:Nothing")) { return editor.getParseController().getRootNode().getUnit().getNothingDeclaration(); } TypeChecker tc = editor.getParseController().getTypeChecker(); String[] bits = location.split(":"); JDTModelLoader modelLoader = getModelLoader(tc); String moduleName = bits[1]; Module module = modelLoader.getLoadedModule(moduleName); if (module == null || bits.length == 2) { return module; } Referenceable target = module.getPackage(bits[2]); for (int i = 3; i < bits.length; i++) { Scope scope; if (target instanceof Scope) { scope = (Scope) target; } else if (target instanceof TypedDeclaration) { scope = ((TypedDeclaration) target).getType().getDeclaration(); } else { return null; } if (scope instanceof Value) { TypeDeclaration val = ((Value) scope).getTypeDeclaration(); if (val.isAnonymous()) { scope = val; } } target = scope.getDirectMember(bits[i], null, false); } return target; } public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { CeylonBrowserInput info = (CeylonBrowserInput) getHoverInfo2(textViewer, hoverRegion); return info != null ? info.getHtml() : null; } @Override public CeylonBrowserInput getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) { return internalGetHoverInfo(editor, hoverRegion); } static CeylonBrowserInput internalGetHoverInfo(CeylonEditor editor, IRegion hoverRegion) { if (editor == null || editor.getSelectionProvider() == null) { return null; } CeylonBrowserInput result = getExpressionHover(editor, hoverRegion); if (result == null) { result = getDeclarationHover(editor, hoverRegion); } return result; } static CeylonBrowserInput getExpressionHover(CeylonEditor editor, IRegion hoverRegion) { CeylonParseController parseController = editor.getParseController(); if (parseController == null) { return null; } Tree.CompilationUnit rn = parseController.getRootNode(); if (rn != null) { int hoffset = hoverRegion.getOffset(); ITextSelection selection = editor.getSelectionFromThread(); if (selection != null && selection.getOffset() <= hoffset && selection.getOffset() + selection.getLength() >= hoffset) { Node node = findNode(rn, selection.getOffset(), selection.getOffset() + selection.getLength() - 1); if (node instanceof Tree.Type) { return getTypeHoverInfo(node, selection.getText(), editor.getCeylonSourceViewer().getDocument(), editor.getParseController().getProject()); } if (node instanceof Tree.Expression) { node = ((Tree.Expression) node).getTerm(); } if (node instanceof Tree.Term) { return getTermTypeHoverInfo(node, selection.getText(), editor.getCeylonSourceViewer().getDocument(), editor.getParseController().getProject()); } else { return null; } } else { return null; } } else { return null; } } static CeylonBrowserInput getDeclarationHover(CeylonEditor editor, IRegion hoverRegion) { CeylonParseController parseController = editor.getParseController(); Node node = getHoverNode(hoverRegion, parseController); if (node != null) { if (node instanceof Tree.ImportPath) { Referenceable r = ((Tree.ImportPath) node).getModel(); if (r != null) { return getHoverInfo(r, null, editor, node); } else { return null; } } else if (node instanceof Tree.LocalModifier) { return getInferredTypeHoverInfo(node, parseController.getProject()); } else if (node instanceof Tree.Literal) { return getTermTypeHoverInfo(node, null, editor.getCeylonSourceViewer().getDocument(), parseController.getProject()); } else { return getHoverInfo(getReferencedDeclaration(node), null, editor, node); } } else { return null; } } private static CeylonBrowserInput getInferredTypeHoverInfo(Node node, IProject project) { ProducedType t = ((Tree.LocalModifier) node).getTypeModel(); if (t == null) return null; StringBuilder buffer = new StringBuilder(); HTMLPrinter.insertPageProlog(buffer, 0, HTML.getStyleSheet()); HTML.addImageAndLabel(buffer, null, HTML.fileUrl("types.gif").toExternalForm(), 16, 16, "<tt>" + producedTypeLink(t, node.getUnit()) + "</tt>", 20, 4); buffer.append("<br/>"); if (!t.containsUnknowns()) { buffer.append("One quick assist available:<br/>"); HTML.addImageAndLabel(buffer, null, HTML.fileUrl("correction_change.gif").toExternalForm(), 16, 16, "<a href=\"stp:" + node.getStartIndex() + "\">Specify explicit type</a>", 20, 4); } //buffer.append(getDocumentationFor(editor.getParseController(), t.getDeclaration())); HTMLPrinter.addPageEpilog(buffer); return new CeylonBrowserInput(null, null, buffer.toString()); } private static CeylonBrowserInput getTypeHoverInfo(Node node, String selectedText, IDocument doc, IProject project) { ProducedType t = ((Tree.Type) node).getTypeModel(); if (t == null) return null; // String expr = ""; // try { // expr = doc.get(node.getStartIndex(), node.getStopIndex()-node.getStartIndex()+1); // } // catch (BadLocationException e) { // e.printStackTrace(); // } String abbreviated = PRINTER.getProducedTypeName(t, node.getUnit()); String unabbreviated = VERBOSE_PRINTER.getProducedTypeName(t, node.getUnit()); StringBuilder buffer = new StringBuilder(); HTMLPrinter.insertPageProlog(buffer, 0, HTML.getStyleSheet()); HTML.addImageAndLabel(buffer, null, HTML.fileUrl("types.gif").toExternalForm(), 16, 16, "<tt>" + producedTypeLink(t, node.getUnit()) + "</tt> ", 20, 4); if (!abbreviated.equals(unabbreviated)) { buffer.append("<br/>").append("Abbreviation of: ").append(unabbreviated); } HTMLPrinter.addPageEpilog(buffer); return new CeylonBrowserInput(null, null, buffer.toString()); } private static String escape(String string) { return string.replace("\0", "\\{NULL}").replace("\b", "\\b").replace("\t", "\\t").replace("\n", "\\n") .replace("\r", "\\r").replace("\f", "\\f"); } private static CeylonBrowserInput getTermTypeHoverInfo(Node node, String selectedText, IDocument doc, IProject project) { ProducedType t = ((Tree.Term) node).getTypeModel(); if (t == null) return null; // String expr = ""; // try { // expr = doc.get(node.getStartIndex(), node.getStopIndex()-node.getStartIndex()+1); // } // catch (BadLocationException e) { // e.printStackTrace(); // } StringBuilder buffer = new StringBuilder(); HTMLPrinter.insertPageProlog(buffer, 0, HTML.getStyleSheet()); String desc = node instanceof Tree.Literal ? "literal" : "expression"; HTML.addImageAndLabel(buffer, null, HTML.fileUrl("types.gif").toExternalForm(), 16, 16, "<tt>" + producedTypeLink(t, node.getUnit()) + "</tt> " + desc, 20, 4); if (node instanceof Tree.StringLiteral) { String escaped = escape(node.getText()); if (escaped.length() > 250) { escaped = escaped.substring(0, 250) + "..."; } buffer.append("<br/>").append("<code style='color:").append(toHex(getCurrentThemeColor(STRINGS))) .append("'><pre>").append('\"').append(convertToHTMLContent(escaped).replace("\\n", "<br/>")) .append('\"').append("</pre></code>"); // If a single char selection, then append info on that character too if (selectedText != null && codePointCount(selectedText, 0, selectedText.length()) == 1) { appendCharacterHoverInfo(buffer, selectedText); } } else if (node instanceof Tree.CharLiteral) { String character = node.getText(); if (character.length() > 2) { appendCharacterHoverInfo(buffer, character.substring(1, character.length() - 1)); } } else if (node instanceof Tree.NaturalLiteral) { buffer.append("<br/>").append("<code style='color:").append(toHex(getCurrentThemeColor(NUMBERS))) .append("'>"); String text = node.getText().replace("_", ""); switch (text.charAt(0)) { case '#': buffer.append(parseInt(text.substring(1), 16)); break; case '$': buffer.append(parseInt(text.substring(1), 2)); break; default: buffer.append(parseInt(text)); } buffer.append("</code>"); } else if (node instanceof Tree.FloatLiteral) { buffer.append("<br/>").append("<code style='color:").append(toHex(getCurrentThemeColor(NUMBERS))) .append("'>").append(parseFloat(node.getText().replace("_", ""))).append("</code>"); } if (selectedText != null) { buffer.append("<br/>").append("Two quick assists available:<br/>"); HTML.addImageAndLabel(buffer, null, HTML.fileUrl("change.png").toExternalForm(), 16, 16, "<a href=\"exv:\">Extract value</a>", 20, 4); HTML.addImageAndLabel(buffer, null, HTML.fileUrl("change.png").toExternalForm(), 16, 16, "<a href=\"exf:\">Extract function</a>", 20, 4); buffer.append("<br/>"); } HTMLPrinter.addPageEpilog(buffer); return new CeylonBrowserInput(null, null, buffer.toString()); } private static void appendCharacterHoverInfo(StringBuilder buffer, String character) { buffer.append("<br/>").append("<code style='color:").append(toHex(getCurrentThemeColor(CHARS))).append("'>") .append('\'').append(convertToHTMLContent(escape(character))).append('\'').append("</code>"); int codepoint = Character.codePointAt(character, 0); String name = Character.getName(codepoint); buffer.append("<br/>Unicode Name: <code>").append(name).append("</code>"); String hex = Integer.toHexString(codepoint).toUpperCase(); while (hex.length() < 4) { hex = "0" + hex; } buffer.append("<br/>Codepoint: <code>").append("U+").append(hex).append("</code>"); buffer.append("<br/>General Category: <code>").append(getCodepointGeneralCategoryName(codepoint)) .append("</code>"); Character.UnicodeScript script = Character.UnicodeScript.of(codepoint); buffer.append("<br/>Script: <code>").append(script.name()).append("</code>"); Character.UnicodeBlock block = Character.UnicodeBlock.of(codepoint); buffer.append("<br/>Block: <code>").append(block).append("</code><br/>"); } private static String getCodepointGeneralCategoryName(int codepoint) { String gc; switch (Character.getType(codepoint)) { case Character.COMBINING_SPACING_MARK: gc = "Mark, combining spacing"; break; case Character.CONNECTOR_PUNCTUATION: gc = "Punctuation, connector"; break; case Character.CONTROL: gc = "Other, control"; break; case Character.CURRENCY_SYMBOL: gc = "Symbol, currency"; break; case Character.DASH_PUNCTUATION: gc = "Punctuation, dash"; break; case Character.DECIMAL_DIGIT_NUMBER: gc = "Number, decimal digit"; break; case Character.ENCLOSING_MARK: gc = "Mark, enclosing"; break; case Character.END_PUNCTUATION: gc = "Punctuation, close"; break; case Character.FINAL_QUOTE_PUNCTUATION: gc = "Punctuation, final quote"; break; case Character.FORMAT: gc = "Other, format"; break; case Character.INITIAL_QUOTE_PUNCTUATION: gc = "Punctuation, initial quote"; break; case Character.LETTER_NUMBER: gc = "Number, letter"; break; case Character.LINE_SEPARATOR: gc = "Separator, line"; break; case Character.LOWERCASE_LETTER: gc = "Letter, lowercase"; break; case Character.MATH_SYMBOL: gc = "Symbol, math"; break; case Character.MODIFIER_LETTER: gc = "Letter, modifier"; break; case Character.MODIFIER_SYMBOL: gc = "Symbol, modifier"; break; case Character.NON_SPACING_MARK: gc = "Mark, nonspacing"; break; case Character.OTHER_LETTER: gc = "Letter, other"; break; case Character.OTHER_NUMBER: gc = "Number, other"; break; case Character.OTHER_PUNCTUATION: gc = "Punctuation, other"; break; case Character.OTHER_SYMBOL: gc = "Symbol, other"; break; case Character.PARAGRAPH_SEPARATOR: gc = "Separator, paragraph"; break; case Character.PRIVATE_USE: gc = "Other, private use"; break; case Character.SPACE_SEPARATOR: gc = "Separator, space"; break; case Character.START_PUNCTUATION: gc = "Punctuation, open"; break; case Character.SURROGATE: gc = "Other, surrogate"; break; case Character.TITLECASE_LETTER: gc = "Letter, titlecase"; break; case Character.UNASSIGNED: gc = "Other, unassigned"; break; case Character.UPPERCASE_LETTER: gc = "Letter, uppercase"; break; default: gc = "<Unknown>"; } return gc; } private static String getIcon(Object obj) { if (obj instanceof Module) { return "jar_l_obj.gif"; } else if (obj instanceof Package) { return "package_obj.gif"; } else if (obj instanceof Declaration) { Declaration dec = (Declaration) obj; if (dec instanceof Class || dec instanceof Constructor) { String icon = dec.isShared() ? "class_obj.gif" : "innerclass_private_obj.gif"; return decorateTypeIcon(dec, icon); } else if (dec instanceof Interface) { String icon = dec.isShared() ? "int_obj.gif" : "innerinterface_private_obj.gif"; return decorateTypeIcon(dec, icon); } else if (dec instanceof TypeAlias || dec instanceof NothingType) { return "type_alias.gif"; } else if (dec.isParameter()) { if (dec instanceof Method) { return "methpro_obj.gif"; } else { return "field_protected_obj.gif"; } } else if (dec instanceof Method) { String icon = dec.isShared() ? "public_co.gif" : "private_co.gif"; return decorateFunctionIcon(dec, icon); } else if (dec instanceof MethodOrValue) { return dec.isShared() ? "field_public_obj.gif" : "field_private_obj.gif"; } else if (dec instanceof TypeParameter) { return "typevariable_obj.gif"; } } return null; } private static String decorateFunctionIcon(Declaration dec, String icon) { if (dec.isAnnotation()) { return icon.replace("co", "ann"); } else { return icon; } } private static String decorateTypeIcon(Declaration dec, String icon) { if (((TypeDeclaration) dec).getCaseTypes() != null) { return icon.replace("obj", "enum"); } else if (dec.isAnnotation()) { return icon.replace("obj", "ann"); } else if (((TypeDeclaration) dec).isAlias()) { return icon.replace("obj", "alias"); } else { return icon; } } /** * Computes the hover info. * @param previousInput the previous input, or <code>null</code> * @param node * @param elements the resolved elements * @param editorInputElement the editor input, or <code>null</code> * * @return the HTML hover info for the given element(s) or <code>null</code> * if no information is available * @since 3.4 */ static CeylonBrowserInput getHoverInfo(Referenceable model, BrowserInput previousInput, CeylonEditor editor, Node node) { if (model instanceof Declaration) { Declaration dec = (Declaration) model; return new CeylonBrowserInput(previousInput, dec, getDocumentationFor(editor.getParseController(), dec, node, null)); } else if (model instanceof Package) { Package dec = (Package) model; return new CeylonBrowserInput(previousInput, dec, getDocumentationFor(editor.getParseController(), dec)); } else if (model instanceof Module) { Module dec = (Module) model; return new CeylonBrowserInput(previousInput, dec, getDocumentationFor(editor.getParseController(), dec)); } else { return null; } } private static void appendJavadoc(IJavaElement elem, StringBuilder sb) { if (elem instanceof IMember) { try { //TODO: Javadoc @ icon? IMember mem = (IMember) elem; String jd = JavadocContentAccess2.getHTMLContent(mem, true); if (jd != null) { sb.append("<br/>").append(jd); String base = getBaseURL(mem, mem.isBinary()); int endHeadIdx = sb.indexOf("</head>"); sb.insert(endHeadIdx, "\n<base href='" + base + "'>\n"); } } catch (JavaModelException e) { e.printStackTrace(); } } } private static String getBaseURL(IJavaElement element, boolean isBinary) throws JavaModelException { if (isBinary) { // Source attachment usually does not include Javadoc resources // => Always use the Javadoc location as base: URL baseURL = JavaUI.getJavadocLocation(element, false); if (baseURL != null) { if (baseURL.getProtocol().equals("jar")) { // It's a JarURLConnection, which is not known to the browser widget. // Let's start the help web server: URL baseURL2 = PlatformUI.getWorkbench().getHelpSystem().resolve(baseURL.toExternalForm(), true); if (baseURL2 != null) { // can be null if org.eclipse.help.ui is not available baseURL = baseURL2; } } return baseURL.toExternalForm(); } } else { IResource resource = element.getResource(); if (resource != null) { /* * Too bad: Browser widget knows nothing about EFS and custom URL handlers, * so IResource#getLocationURI() does not work in all cases. * We only support the local file system for now. * A solution could be https://bugs.eclipse.org/bugs/show_bug.cgi?id=149022 . */ IPath location = resource.getLocation(); if (location != null) { return location.toFile().toURI().toString(); } } } return null; } public static String getDocumentationFor(CeylonParseController cpc, Package pack) { StringBuilder buffer = new StringBuilder(); addMainPackageDescription(pack, buffer); addPackageDocumentation(cpc, pack, buffer); addAdditionalPackageInfo(buffer, pack); addPackageMembers(buffer, pack); addPackageModuleInfo(pack, buffer); insertPageProlog(buffer, 0, HTML.getStyleSheet()); addPageEpilog(buffer); return buffer.toString(); } private static void addPackageMembers(StringBuilder buffer, Package pack) { boolean first = true; for (Declaration dec : pack.getMembers()) { if (dec instanceof Class && ((Class) dec).isOverloaded()) { continue; } if (dec.isShared() && !dec.isAnonymous()) { if (first) { buffer.append("<p>Contains: "); first = false; } else { buffer.append(", "); } /*addImageAndLabel(buffer, null, fileUrl(getIcon(dec)).toExternalForm(), 16, 16, "<tt><a " + link(dec) + ">" + dec.getName() + "</a></tt>", 20, 2);*/ appendLink(buffer, dec); } } if (!first) { buffer.append(".</p>"); } } private static void appendLink(StringBuilder buffer, Referenceable dec) { buffer.append("<tt><a ").append(HTML.link(dec)).append(">"); if (dec instanceof Declaration) { buffer.append(((Declaration) dec).getName()); } else if (dec instanceof Package) { buffer.append(getLabel((Package) dec)); } else if (dec instanceof Module) { buffer.append(getLabel((Module) dec)); } buffer.append("</a></tt>"); } private static String link(Referenceable dec) { StringBuilder builder = new StringBuilder(); appendLink(builder, dec); return builder.toString(); } private static void addAdditionalPackageInfo(StringBuilder buffer, Package pack) { Module mod = pack.getModule(); if (mod.isJava()) { buffer.append("<p>This package is implemented in Java.</p>"); } if (JDKUtils.isJDKModule(mod.getNameAsString())) { buffer.append("<p>This package forms part of the Java SDK.</p>"); } } private static void addMainPackageDescription(Package pack, StringBuilder buffer) { if (pack.isShared()) { String ann = toHex(getCurrentThemeColor(ANNOTATIONS)); HTML.addImageAndLabel(buffer, null, HTML.fileUrl("annotation_obj.gif").toExternalForm(), 16, 16, "<tt><span style='font-size:" + annotationSize + ";color:" + ann + "'>shared</span></tt>", 20, 4); } HTML.addImageAndLabel(buffer, pack, HTML.fileUrl(getIcon(pack)).toExternalForm(), 16, 16, "<tt><span style='font-size:" + largerSize + "'>" + HTML.highlightLine(description(pack)) + "</span></tt>", 20, 4); } private static void addPackageModuleInfo(Package pack, StringBuilder buffer) { Module mod = pack.getModule(); HTML.addImageAndLabel(buffer, mod, HTML.fileUrl(getIcon(mod)).toExternalForm(), 16, 16, "<span style='font-size:" + smallerSize + "'>in module " + link(mod) + "</span>", 20, 2); } private static String description(Package pack) { return "package " + getLabel(pack); } public static String getDocumentationFor(ModuleDetails mod, String version, Scope scope, Unit unit) { return getDocumentationForModule(mod.getName(), version, mod.getDoc(), scope, unit); } public static String getDocumentationForModule(String name, String version, String doc, Scope scope, Unit unit) { StringBuilder buffer = new StringBuilder(); HTML.addImageAndLabel( buffer, null, HTML.fileUrl("jar_l_obj.gif").toExternalForm(), 16, 16, "<tt><span style='font-size:" + largerSize + "'>" + HTML.highlightLine(description(name, version)) + "</span></tt></b>", 20, 4); if (doc != null) { buffer.append(markdown(doc, scope, unit)); } insertPageProlog(buffer, 0, HTML.getStyleSheet()); addPageEpilog(buffer); return buffer.toString(); } private static String description(String name, String version) { return "module " + name + " \"" + version + "\""; } private static String getDocumentationFor(CeylonParseController cpc, Module mod) { StringBuilder buffer = new StringBuilder(); addMainModuleDescription(mod, buffer); addAdditionalModuleInfo(buffer, mod); addModuleDocumentation(cpc, mod, buffer); addModuleMembers(buffer, mod); insertPageProlog(buffer, 0, HTML.getStyleSheet()); addPageEpilog(buffer); return buffer.toString(); } private static void addAdditionalModuleInfo(StringBuilder buffer, Module mod) { if (mod.isJava()) { buffer.append("<p>This module is implemented in Java.</p>"); } if (mod.isDefault()) { buffer.append("<p>The default module for packages which do not belong to explicit module.</p>"); } if (JDKUtils.isJDKModule(mod.getNameAsString())) { buffer.append("<p>This module forms part of the Java SDK.</p>"); } } private static void addMainModuleDescription(Module mod, StringBuilder buffer) { HTML.addImageAndLabel(buffer, mod, HTML.fileUrl(getIcon(mod)).toExternalForm(), 16, 16, "<tt><span style='font-size:" + largerSize + "'>" + HTML.highlightLine(description(mod)) + "</span></tt>", 20, 4); } private static void addModuleDocumentation(CeylonParseController cpc, Module mod, StringBuilder buffer) { Unit unit = mod.getUnit(); PhasedUnit pu = null; if (unit instanceof CeylonUnit) { pu = ((CeylonUnit) unit).getPhasedUnit(); } if (pu != null) { List<Tree.ModuleDescriptor> moduleDescriptors = pu.getCompilationUnit().getModuleDescriptors(); if (!moduleDescriptors.isEmpty()) { Tree.ModuleDescriptor refnode = moduleDescriptors.get(0); if (refnode != null) { Scope linkScope = mod.getPackage(mod.getNameAsString()); appendDocAnnotationContent(refnode.getAnnotationList(), buffer, linkScope); appendThrowAnnotationContent(refnode.getAnnotationList(), buffer, linkScope); appendSeeAnnotationContent(refnode.getAnnotationList(), buffer); } } } } private static void addPackageDocumentation(CeylonParseController cpc, Package pack, StringBuilder buffer) { Unit unit = pack.getUnit(); PhasedUnit pu = null; if (unit instanceof CeylonUnit) { pu = ((CeylonUnit) unit).getPhasedUnit(); } if (pu != null) { List<Tree.PackageDescriptor> packageDescriptors = pu.getCompilationUnit().getPackageDescriptors(); if (!packageDescriptors.isEmpty()) { Tree.PackageDescriptor refnode = packageDescriptors.get(0); if (refnode != null) { Scope linkScope = pack; appendDocAnnotationContent(refnode.getAnnotationList(), buffer, linkScope); appendThrowAnnotationContent(refnode.getAnnotationList(), buffer, linkScope); appendSeeAnnotationContent(refnode.getAnnotationList(), buffer); } } } } private static void addModuleMembers(StringBuilder buffer, Module mod) { boolean first = true; for (Package pack : mod.getPackages()) { if (pack.isShared()) { if (first) { buffer.append("<p>Contains: "); first = false; } else { buffer.append(", "); } /*addImageAndLabel(buffer, null, fileUrl(getIcon(dec)).toExternalForm(), 16, 16, "<tt><a " + link(dec) + ">" + dec.getName() + "</a></tt>", 20, 2);*/ appendLink(buffer, pack); } } if (!first) { buffer.append(".</p>"); } } private static String description(Module mod) { return "module " + getLabel(mod) + " \"" + mod.getVersion() + "\""; } public static String getDocumentationFor(CeylonParseController cpc, Declaration dec) { return getDocumentationFor(cpc, dec, null, null); } public static String getDocumentationFor(CeylonParseController cpc, Declaration dec, ProducedReference pr) { return getDocumentationFor(cpc, dec, null, pr); } private static String getDocumentationFor(CeylonParseController cpc, Declaration dec, Node node, ProducedReference pr) { if (dec == null) return null; if (dec instanceof Value) { TypeDeclaration val = ((Value) dec).getTypeDeclaration(); if (val != null && val.isAnonymous()) { dec = val; } } Unit unit = cpc.getRootNode().getUnit(); StringBuilder buffer = new StringBuilder(); insertPageProlog(buffer, 0, HTML.getStyleSheet()); addMainDescription(buffer, dec, node, pr, cpc, unit); boolean obj = addInheritanceInfo(dec, node, pr, buffer, unit); addContainerInfo(dec, node, buffer); //TODO: use the pr to get the qualifying type?? boolean hasDoc = addDoc(cpc, dec, node, buffer); addRefinementInfo(cpc, dec, node, buffer, hasDoc, unit); //TODO: use the pr to get the qualifying type?? addReturnType(dec, buffer, node, pr, obj, unit); addParameters(cpc, dec, node, pr, buffer, unit); addClassMembersInfo(dec, buffer); addUnitInfo(dec, buffer); addPackageInfo(dec, buffer); if (dec instanceof NothingType) { addNothingTypeInfo(buffer); } else { appendExtraActions(dec, buffer); } addPageEpilog(buffer); return buffer.toString(); } private static void addMainDescription(StringBuilder buffer, Declaration dec, Node node, ProducedReference pr, CeylonParseController cpc, Unit unit) { StringBuilder buf = new StringBuilder(); if (dec.isShared()) buf.append("shared "); if (dec.isActual()) buf.append("actual "); if (dec.isDefault()) buf.append("default "); if (dec.isFormal()) buf.append("formal "); if (dec instanceof Value && ((Value) dec).isLate()) buf.append("late "); if (isVariable(dec)) buf.append("variable "); if (dec.isNative()) buf.append("native "); if (dec instanceof TypeDeclaration) { TypeDeclaration td = (TypeDeclaration) dec; if (td.isSealed()) buf.append("sealed "); if (td.isFinal()) buf.append("final "); if (td instanceof Class && ((Class) td).isAbstract()) buf.append("abstract "); } if (dec.isAnnotation()) buf.append("annotation "); if (buf.length() != 0) { String ann = toHex(getCurrentThemeColor(ANNOTATIONS)); HTML.addImageAndLabel(buffer, null, HTML.fileUrl("annotation_obj.gif").toExternalForm(), 16, 16, "<tt><span style='font-size:" + annotationSize + ";color:" + ann + "'>" + buf + "</span></tt>", 20, 4); } HTML.addImageAndLabel(buffer, dec, HTML.fileUrl(getIcon(dec)).toExternalForm(), 16, 16, "<tt><span style='font-size:" + largerSize + "'>" + (dec.isDeprecated() ? "<s>" : "") + description(dec, node, pr, cpc, unit) + (dec.isDeprecated() ? "</s>" : "") + "</span></tt>", 20, 4); } private static void addClassMembersInfo(Declaration dec, StringBuilder buffer) { if (dec instanceof ClassOrInterface) { if (!dec.getMembers().isEmpty()) { boolean first = true; for (Declaration mem : dec.getMembers()) { if (mem instanceof Method && ((Method) mem).isOverloaded()) { continue; } if (mem.isShared()) { if (first) { buffer.append("<p>Members: "); first = false; } else { buffer.append(", "); } appendLink(buffer, mem); } } if (!first) { buffer.append(".</p>"); //extraBreak = true; } } } } private static void addNothingTypeInfo(StringBuilder buffer) { buffer.append("Special bottom type defined by the language. " + "<code>Nothing</code> is assignable to all types, but has no value. " + "A function or value of type <code>Nothing</code> either throws " + "an exception, or never returns."); } private static boolean addInheritanceInfo(Declaration dec, Node node, ProducedReference pr, StringBuilder buffer, Unit unit) { buffer.append("<p><div style='padding-left:20px'>"); boolean obj = false; if (dec instanceof TypedDeclaration) { TypeDeclaration td = ((TypedDeclaration) dec).getTypeDeclaration(); if (td != null && td.isAnonymous()) { obj = true; documentInheritance(td, node, pr, buffer, unit); } } else if (dec instanceof TypeDeclaration) { documentInheritance((TypeDeclaration) dec, node, pr, buffer, unit); } buffer.append("</div></p>"); documentTypeParameters(dec, node, pr, buffer, unit); buffer.append("</p>"); return obj; } private static void addRefinementInfo(CeylonParseController cpc, Declaration dec, Node node, StringBuilder buffer, boolean hasDoc, Unit unit) { Declaration rd = dec.getRefinedDeclaration(); if (dec != rd && rd != null) { buffer.append("<p>"); TypeDeclaration superclass = (TypeDeclaration) rd.getContainer(); ClassOrInterface outer = (ClassOrInterface) dec.getContainer(); ProducedType sup = getQualifyingType(node, outer).getSupertype(superclass); HTML.addImageAndLabel(buffer, rd, HTML.fileUrl(rd.isFormal() ? "implm_co.gif" : "over_co.gif").toExternalForm(), 16, 16, "refines " + link(rd) + " declared by <tt>" + producedTypeLink(sup, unit) + "</tt>", 20, 2); buffer.append("</p>"); if (!hasDoc) { Tree.Declaration refnode2 = (Tree.Declaration) getReferencedNode(rd, cpc); if (refnode2 != null) { appendDocAnnotationContent(refnode2.getAnnotationList(), buffer, resolveScope(rd)); } } } } private static void appendParameters(Declaration d, ProducedReference pr, Unit unit, StringBuilder result/*, CeylonParseController cpc*/) { if (d instanceof Functional) { List<ParameterList> plists = ((Functional) d).getParameterLists(); if (plists != null) { for (ParameterList params : plists) { if (params.getParameters().isEmpty()) { result.append("()"); } else { result.append("("); for (Parameter p : params.getParameters()) { appendParameter(result, pr, p, unit); // if (cpc!=null) { // result.append(getDefaultValueDescription(p, cpc)); // } result.append(", "); } result.setLength(result.length() - 2); result.append(")"); } } } } } private static void appendParameter(StringBuilder result, ProducedReference pr, Parameter p, Unit unit) { if (p.getModel() == null) { result.append(p.getName()); } else { ProducedTypedReference ppr = pr == null ? null : pr.getTypedParameter(p); if (p.isDeclaredVoid()) { result.append(HTML.keyword("void")); } else { if (ppr != null) { ProducedType pt = ppr.getType(); if (p.isSequenced() && pt != null) { pt = p.getDeclaration().getUnit().getSequentialElementType(pt); } result.append(producedTypeLink(pt, unit)); if (p.isSequenced()) { result.append(p.isAtLeastOne() ? '+' : '*'); } } else if (p.getModel() instanceof Method) { result.append(HTML.keyword("function")); } else { result.append(HTML.keyword("value")); } } result.append(" "); appendLink(result, p.getModel()); appendParameters(p.getModel(), ppr, unit, result); } } private static void addParameters(CeylonParseController cpc, Declaration dec, Node node, ProducedReference pr, StringBuilder buffer, Unit unit) { if (dec instanceof Functional) { if (pr == null) { pr = getProducedReference(dec, node); } if (pr == null) return; for (ParameterList pl : ((Functional) dec).getParameterLists()) { if (!pl.getParameters().isEmpty()) { buffer.append("<p>"); for (Parameter p : pl.getParameters()) { MethodOrValue model = p.getModel(); if (model != null) { StringBuilder param = new StringBuilder(); param.append("<span style='font-size:" + smallerSize + "'>accepts <tt>"); appendParameter(param, pr, p, unit); param.append(HTML.highlightLine(getInitialValueDescription(model, cpc))) .append("</tt>"); Tree.Declaration refNode = (Tree.Declaration) getReferencedNode(model, cpc); if (refNode != null) { appendDocAnnotationContent(refNode.getAnnotationList(), param, resolveScope(dec)); } param.append("</span>"); HTML.addImageAndLabel(buffer, model, HTML.fileUrl("methpro_obj.gif").toExternalForm(), 16, 16, param.toString(), 20, 2); } } buffer.append("</p>"); } } } } private static void addReturnType(Declaration dec, StringBuilder buffer, Node node, ProducedReference pr, boolean obj, Unit unit) { if (dec instanceof TypedDeclaration && !obj) { if (pr == null) { pr = getProducedReference(dec, node); } if (pr == null) return; ProducedType ret = pr.getType(); if (ret != null) { buffer.append("<p>"); StringBuilder buf = new StringBuilder("returns <tt>"); buf.append(producedTypeLink(ret, unit)).append("|"); buf.setLength(buf.length() - 1); buf.append("</tt>"); HTML.addImageAndLabel(buffer, ret.getDeclaration(), HTML.fileUrl("stepreturn_co.gif").toExternalForm(), 16, 16, buf.toString(), 20, 2); buffer.append("</p>"); } } } private static ProducedTypeNamePrinter printer(boolean abbreviate) { return new ProducedTypeNamePrinter(abbreviate, true, false, true) { @Override protected String getSimpleDeclarationName(Declaration declaration, Unit unit) { return "<a " + HTML.link(declaration) + ">" + super.getSimpleDeclarationName(declaration, unit) + "</a>"; } @Override protected String amp() { return "&"; } @Override protected String lt() { return "<"; } @Override protected String gt() { return ">"; } }; } private static ProducedTypeNamePrinter PRINTER = printer(true); private static ProducedTypeNamePrinter VERBOSE_PRINTER = printer(false); private static String producedTypeLink(ProducedType pt, Unit unit) { return PRINTER.getProducedTypeName(pt, unit); } private static List<ProducedType> getTypeParameters(Declaration dec) { if (dec instanceof Functional) { List<TypeParameter> typeParameters = ((Functional) dec).getTypeParameters(); if (typeParameters.isEmpty()) { return Collections.<ProducedType>emptyList(); } else { List<ProducedType> list = new ArrayList<ProducedType>(); for (TypeParameter p : typeParameters) { list.add(p.getType()); } return list; } } else { return Collections.<ProducedType>emptyList(); } } private static ProducedReference getProducedReference(Declaration dec, Node node) { if (node instanceof Tree.TypeDeclaration) { return ((TypeDeclaration) dec).getType(); } else if (node instanceof Tree.MemberOrTypeExpression) { return ((Tree.MemberOrTypeExpression) node).getTarget(); } else if (node instanceof Tree.Type) { return ((Tree.Type) node).getTypeModel(); } else { //a member declaration - unfortunately there is //nothing matching TypeDeclaration.getType() for //TypedDeclarations! ClassOrInterface outer = dec.isClassOrInterfaceMember() ? (ClassOrInterface) dec.getContainer() : null; return dec.getProducedReference(outer == null ? null : outer.getType(), getTypeParameters(dec)); } } private static boolean addDoc(CeylonParseController cpc, Declaration dec, Node node, StringBuilder buffer) { boolean hasDoc = false; Node rn = getReferencedNode(dec, cpc); if (rn instanceof Tree.Declaration) { Tree.Declaration refnode = (Tree.Declaration) rn; appendDeprecatedAnnotationContent(refnode.getAnnotationList(), buffer, resolveScope(dec)); int len = buffer.length(); appendDocAnnotationContent(refnode.getAnnotationList(), buffer, resolveScope(dec)); hasDoc = buffer.length() != len; appendThrowAnnotationContent(refnode.getAnnotationList(), buffer, resolveScope(dec)); appendSeeAnnotationContent(refnode.getAnnotationList(), buffer); } else { appendJavadoc(dec, cpc.getProject(), buffer, node); } return hasDoc; } private static void addContainerInfo(Declaration dec, Node node, StringBuilder buffer) { buffer.append("<p>"); if (dec.isParameter()) { Declaration pd = ((MethodOrValue) dec).getInitializerParameter().getDeclaration(); if (pd.getName().startsWith("anonymous#")) { buffer.append("Parameter of anonymous function."); } else { buffer.append("Parameter of "); appendLink(buffer, pd); buffer.append("."); } // HTML.addImageAndLabel(buffer, pd, // HTML.fileUrl(getIcon(pd)).toExternalForm(), // 16, 16, // "<span style='font-size:" + smallerSize + "'>parameter of <tt><a " + HTML.link(pd) + ">" + // pd.getName() +"</a></tt><span>", 20, 2); } else if (dec instanceof TypeParameter) { Declaration pd = ((TypeParameter) dec).getDeclaration(); buffer.append("Type parameter of "); appendLink(buffer, pd); buffer.append("."); // HTML.addImageAndLabel(buffer, pd, // HTML.fileUrl(getIcon(pd)).toExternalForm(), // 16, 16, // "<span style='font-size:" + smallerSize + "'>type parameter of <tt><a " + HTML.link(pd) + ">" + // pd.getName() +"</a></tt></span>", // 20, 2); } else { if (dec.isClassOrInterfaceMember()) { ClassOrInterface outer = (ClassOrInterface) dec.getContainer(); ProducedType qt = getQualifyingType(node, outer); if (qt != null) { Unit unit = node == null ? null : node.getUnit(); buffer.append("Member of <tt>" + producedTypeLink(qt, unit) + "</tt>."); // HTML.addImageAndLabel(buffer, outer, // HTML.fileUrl(getIcon(outer)).toExternalForm(), // 16, 16, // "<span style='font-size:" + smallerSize + "'>member of <tt>" + // producedTypeLink(qt, unit) + "</tt></span>", // 20, 2); } } } buffer.append("</p>"); } private static void addPackageInfo(Declaration dec, StringBuilder buffer) { buffer.append("<p>"); Package pack = dec.getUnit().getPackage(); if ((dec.isShared() || dec.isToplevel()) && !(dec instanceof NothingType)) { String label; if (pack.getNameAsString().isEmpty()) { label = "<span style='font-size:" + smallerSize + "'>in default package</span>"; } else { label = "<span style='font-size:" + smallerSize + "'>in package " + link(pack) + "</span>"; } HTML.addImageAndLabel(buffer, pack, HTML.fileUrl(getIcon(pack)).toExternalForm(), 16, 16, label, 20, 2); Module mod = pack.getModule(); HTML.addImageAndLabel(buffer, mod, HTML.fileUrl(getIcon(mod)).toExternalForm(), 16, 16, "<span style='font-size:" + smallerSize + "'>in module " + link(mod) + "</span>", 20, 2); } buffer.append("</p>"); } private static ProducedType getQualifyingType(Node node, ClassOrInterface outer) { if (outer == null) { return null; } if (node instanceof Tree.MemberOrTypeExpression) { ProducedReference pr = ((Tree.MemberOrTypeExpression) node).getTarget(); if (pr != null) { return pr.getQualifyingType(); } } if (node instanceof Tree.QualifiedType) { return ((Tree.QualifiedType) node).getOuterType().getTypeModel(); } return outer.getType(); } private static void addUnitInfo(Declaration dec, StringBuilder buffer) { buffer.append("<p>"); String unitName = null; if (dec.getUnit() instanceof CeylonUnit) { // Manage the case of CeylonBinaryUnit : getFileName() would return the class file name. // but getCeylonFileName() will return the ceylon source file name if any. unitName = ((CeylonUnit) dec.getUnit()).getCeylonFileName(); } if (unitName == null) { unitName = dec.getUnit().getFilename(); } HTML.addImageAndLabel(buffer, null, HTML.fileUrl("unit.gif").toExternalForm(), 16, 16, "<span style='font-size:" + smallerSize + "'>declared in <tt><a href='dec:" + HTML.declink(dec) + "'>" + unitName + "</a></tt></span>", 20, 2); //} buffer.append("</p>"); } private static void appendExtraActions(Declaration dec, StringBuilder buffer) { buffer.append("<p>"); HTML.addImageAndLabel(buffer, null, HTML.fileUrl("search_ref_obj.png").toExternalForm(), 16, 16, "<span style='font-size:" + smallerSize + "'><a href='ref:" + HTML.declink(dec) + "'>find references</a> to <tt>" + dec.getName() + "</tt></span>", 20, 2); if (dec instanceof ClassOrInterface) { HTML.addImageAndLabel(buffer, null, HTML.fileUrl("search_decl_obj.png").toExternalForm(), 16, 16, "<span style='font-size:" + smallerSize + "'><a href='sub:" + HTML.declink(dec) + "'>find subtypes</a> of <tt>" + dec.getName() + "</tt></span>", 20, 2); } if (dec instanceof MethodOrValue || dec instanceof TypeParameter) { HTML.addImageAndLabel(buffer, null, HTML.fileUrl("search_ref_obj.png").toExternalForm(), 16, 16, "<span style='font-size:" + smallerSize + "'><a href='ass:" + HTML.declink(dec) + "'>find assignments</a> to <tt>" + dec.getName() + "</tt></span>", 20, 2); } if (dec.isFormal() || dec.isDefault()) { HTML.addImageAndLabel(buffer, null, HTML.fileUrl("search_decl_obj.png").toExternalForm(), 16, 16, "<span style='font-size:" + smallerSize + "'><a href='act:" + HTML.declink(dec) + "'>find refinements</a> of <tt>" + dec.getName() + "</tt></span>", 20, 2); } buffer.append("</p>"); } private static void documentInheritance(TypeDeclaration dec, Node node, ProducedReference pr, StringBuilder buffer, Unit unit) { if (pr == null) { pr = getProducedReference(dec, node); } ProducedType type; if (pr instanceof ProducedType) { type = (ProducedType) pr; } else { type = dec.getType(); } List<ProducedType> cts = type.getCaseTypes(); if (cts != null) { StringBuilder cases = new StringBuilder(); for (ProducedType ct : cts) { if (cases.length() > 0) { cases.append(" | "); } cases.append(producedTypeLink(ct, unit)); } if (dec.getSelfType() != null) { cases.append(" (self type)"); } HTML.addImageAndLabel(buffer, null, HTML.fileUrl("sub.gif").toExternalForm(), 16, 16, " <tt><span style='font-size:" + smallerSize + "'>of " + cases + "</span></tt>", 20, 2); } if (dec instanceof Class) { ProducedType sup = type.getExtendedType(); if (sup != null) { HTML.addImageAndLabel(buffer, sup.getDeclaration(), HTML.fileUrl("superclass.gif").toExternalForm(), 16, 16, "<tt><span style='font-size:" + smallerSize + "'>extends " + producedTypeLink(sup, unit) + "</span></tt>", 20, 2); } } List<ProducedType> sts = type.getSatisfiedTypes(); if (!sts.isEmpty()) { StringBuilder satisfies = new StringBuilder(); for (ProducedType st : sts) { if (satisfies.length() > 0) { satisfies.append(" & "); } satisfies.append(producedTypeLink(st, unit)); } HTML.addImageAndLabel(buffer, null, HTML.fileUrl("super.gif").toExternalForm(), 16, 16, "<tt><span style='font-size:" + smallerSize + "'>satisfies " + satisfies + "</span></tt>", 20, 2); } } private static void documentTypeParameters(Declaration dec, Node node, ProducedReference pr, StringBuilder buffer, Unit unit) { if (pr == null) { pr = getProducedReference(dec, node); } List<TypeParameter> typeParameters; if (dec instanceof Functional) { typeParameters = ((Functional) dec).getTypeParameters(); } else if (dec instanceof Interface) { typeParameters = ((Interface) dec).getTypeParameters(); } else { typeParameters = Collections.emptyList(); } for (TypeParameter tp : typeParameters) { StringBuilder bounds = new StringBuilder(); for (ProducedType st : tp.getSatisfiedTypes()) { if (bounds.length() == 0) { bounds.append(" satisfies "); } else { bounds.append(" & "); } bounds.append(producedTypeLink(st, dec.getUnit())); } String arg; ProducedType typeArg = pr == null ? null : pr.getTypeArguments().get(tp); if (typeArg != null && !tp.getType().isExactly(typeArg)) { arg = " = " + producedTypeLink(typeArg, unit); } else { arg = ""; } HTML.addImageAndLabel(buffer, tp, HTML.fileUrl(getIcon(tp)).toExternalForm(), 16, 16, "<tt><span style='font-size:" + smallerSize + "'>given <a " + HTML.link(tp) + ">" + tp.getName() + "</a>" + bounds + arg + "</span></tt>", 20, 4); } } private static String description(Declaration dec, Node node, ProducedReference pr, CeylonParseController cpc, Unit unit) { if (pr == null) { pr = getProducedReference(dec, node); } String description = getDocDescriptionFor(dec, pr, unit); if (dec instanceof TypeDeclaration) { TypeDeclaration td = (TypeDeclaration) dec; if (td.isAlias() && td.getExtendedType() != null) { description += " => " + td.getExtendedType().getProducedTypeName(); } } if (dec instanceof Value && !isVariable(dec) || dec instanceof Method) { description += getInitialValueDescription(dec, cpc); } return HTML.highlightLine(description); } private static void appendJavadoc(Declaration model, IProject project, StringBuilder buffer, Node node) { try { appendJavadoc(getJavaElement(model), buffer); } catch (JavaModelException jme) { jme.printStackTrace(); } } private static void appendDocAnnotationContent(Tree.AnnotationList annotationList, StringBuilder documentation, Scope linkScope) { if (annotationList != null) { AnonymousAnnotation aa = annotationList.getAnonymousAnnotation(); if (aa != null) { documentation .append(markdown(aa.getStringLiteral().getText(), linkScope, annotationList.getUnit())); // HTML.addImageAndLabel(documentation, null, // HTML.fileUrl("toc_obj.gif").toExternalForm(), // 16, 16, // markdown(aa.getStringLiteral().getText(), // linkScope, annotationList.getUnit()), // 20, 0); } for (Tree.Annotation annotation : annotationList.getAnnotations()) { Tree.Primary annotPrim = annotation.getPrimary(); if (annotPrim instanceof Tree.BaseMemberExpression) { String name = ((Tree.BaseMemberExpression) annotPrim).getIdentifier().getText(); if ("doc".equals(name)) { Tree.PositionalArgumentList argList = annotation.getPositionalArgumentList(); if (argList != null) { List<Tree.PositionalArgument> args = argList.getPositionalArguments(); if (!args.isEmpty()) { Tree.PositionalArgument a = args.get(0); if (a instanceof Tree.ListedArgument) { String text = ((Tree.ListedArgument) a).getExpression().getTerm().getText(); if (text != null) { documentation.append(markdown(text, linkScope, annotationList.getUnit())); } } } } } } } } } private static void appendDeprecatedAnnotationContent(Tree.AnnotationList annotationList, StringBuilder documentation, Scope linkScope) { if (annotationList != null) { for (Tree.Annotation annotation : annotationList.getAnnotations()) { Tree.Primary annotPrim = annotation.getPrimary(); if (annotPrim instanceof Tree.BaseMemberExpression) { String name = ((Tree.BaseMemberExpression) annotPrim).getIdentifier().getText(); if ("deprecated".equals(name)) { Tree.PositionalArgumentList argList = annotation.getPositionalArgumentList(); if (argList != null) { List<Tree.PositionalArgument> args = argList.getPositionalArguments(); if (!args.isEmpty()) { Tree.PositionalArgument a = args.get(0); if (a instanceof Tree.ListedArgument) { String text = ((Tree.ListedArgument) a).getExpression().getTerm().getText(); if (text != null) { documentation.append( markdown("_(This is a deprecated program element.)_\n\n" + text, linkScope, annotationList.getUnit())); } } } } } } } } } private static void appendSeeAnnotationContent(Tree.AnnotationList annotationList, StringBuilder documentation) { if (annotationList != null) { for (Tree.Annotation annotation : annotationList.getAnnotations()) { Tree.Primary annotPrim = annotation.getPrimary(); if (annotPrim instanceof Tree.BaseMemberExpression) { String name = ((Tree.BaseMemberExpression) annotPrim).getIdentifier().getText(); if ("see".equals(name)) { Tree.PositionalArgumentList argList = annotation.getPositionalArgumentList(); if (argList != null) { StringBuilder sb = new StringBuilder(); List<Tree.PositionalArgument> args = argList.getPositionalArguments(); for (Tree.PositionalArgument arg : args) { if (arg instanceof Tree.ListedArgument) { Tree.Term term = ((Tree.ListedArgument) arg).getExpression().getTerm(); if (term instanceof Tree.MetaLiteral) { Declaration dec = ((Tree.MetaLiteral) term).getDeclaration(); if (dec != null) { String dn = dec.getName(); if (dec.isClassOrInterfaceMember()) { dn = ((ClassOrInterface) dec.getContainer()).getName() + "." + dn; } if (sb.length() != 0) sb.append(", "); sb.append("<tt><a " + HTML.link(dec) + ">" + dn + "</a></tt>"); } } } } if (sb.length() != 0) { HTML.addImageAndLabel(documentation, null, HTML.fileUrl("link_obj.gif"/*getIcon(dec)*/).toExternalForm(), 16, 16, "see " + sb + ".", 20, 2); } } } } } } } private static void appendThrowAnnotationContent(Tree.AnnotationList annotationList, StringBuilder documentation, Scope linkScope) { if (annotationList != null) { for (Tree.Annotation annotation : annotationList.getAnnotations()) { Tree.Primary annotPrim = annotation.getPrimary(); if (annotPrim instanceof Tree.BaseMemberExpression) { String name = ((Tree.BaseMemberExpression) annotPrim).getIdentifier().getText(); if ("throws".equals(name)) { Tree.PositionalArgumentList argList = annotation.getPositionalArgumentList(); if (argList != null) { List<Tree.PositionalArgument> args = argList.getPositionalArguments(); if (args.isEmpty()) continue; Tree.PositionalArgument typeArg = args.get(0); Tree.PositionalArgument textArg = args.size() > 1 ? args.get(1) : null; if (typeArg instanceof Tree.ListedArgument && (textArg == null || textArg instanceof Tree.ListedArgument)) { Tree.Term typeArgTerm = ((Tree.ListedArgument) typeArg).getExpression().getTerm(); Tree.Term textArgTerm = textArg == null ? null : ((Tree.ListedArgument) textArg).getExpression().getTerm(); String text = textArgTerm instanceof Tree.StringLiteral ? textArgTerm.getText() : ""; if (typeArgTerm instanceof Tree.MetaLiteral) { Declaration dec = ((Tree.MetaLiteral) typeArgTerm).getDeclaration(); if (dec != null) { String dn = dec.getName(); if (typeArgTerm instanceof Tree.QualifiedMemberOrTypeExpression) { Tree.Primary p = ((Tree.QualifiedMemberOrTypeExpression) typeArgTerm) .getPrimary(); if (p instanceof Tree.MemberOrTypeExpression) { dn = ((Tree.MemberOrTypeExpression) p).getDeclaration().getName() + "." + dn; } } HTML.addImageAndLabel(documentation, dec, HTML.fileUrl("ihigh_obj.gif"/*getIcon(dec)*/).toExternalForm(), 16, 16, "throws <tt><a " + HTML.link(dec) + ">" + dn + "</a></tt>" + markdown(text, linkScope, annotationList.getUnit()), 20, 2); } } } } } } } } } private static String markdown(String text, final Scope linkScope, final Unit unit) { if (text == null || text.isEmpty()) { return text; } Builder builder = Configuration.builder().forceExtentedProfile(); builder.setCodeBlockEmitter(new CeylonBlockEmitter()); if (linkScope != null && unit != null) { builder.setSpecialLinkEmitter(new CeylonSpanEmitter(linkScope, unit)); } else { builder.setSpecialLinkEmitter(new UnlinkedSpanEmitter()); } return Processor.process(text, builder.build()); } private static Scope resolveScope(Declaration decl) { if (decl == null) { return null; } else if (decl instanceof Scope) { return (Scope) decl; } else { return decl.getContainer(); } } static Module resolveModule(Scope scope) { if (scope == null) { return null; } else if (scope instanceof Package) { return ((Package) scope).getModule(); } else { return resolveModule(scope.getContainer()); } } /** * Creates the "enriched" control. */ private final class PresenterControlCreator extends AbstractReusableInformationControlCreator { private final DocumentationHover docHover; PresenterControlCreator(DocumentationHover docHover) { this.docHover = docHover; } @Override public IInformationControl doCreateInformationControl(Shell parent) { if (isAvailable(parent)) { ToolBarManager tbm = new ToolBarManager(SWT.FLAT); BrowserInformationControl control = new BrowserInformationControl(parent, APPEARANCE_JAVADOC_FONT, tbm); final BackAction backAction = new BackAction(control); backAction.setEnabled(false); tbm.add(backAction); final ForwardAction forwardAction = new ForwardAction(control); tbm.add(forwardAction); forwardAction.setEnabled(false); //final ShowInJavadocViewAction showInJavadocViewAction= new ShowInJavadocViewAction(iControl); //tbm.add(showInJavadocViewAction); final OpenDeclarationAction openDeclarationAction = new OpenDeclarationAction(control); tbm.add(openDeclarationAction); // final SimpleSelectionProvider selectionProvider = new SimpleSelectionProvider(); //TODO: an action to open the generated ceylondoc // from the doc archive, in a browser window /*if (fSite != null) { OpenAttachedJavadocAction openAttachedJavadocAction= new OpenAttachedJavadocAction(fSite); openAttachedJavadocAction.setSpecialSelectionProvider(selectionProvider); openAttachedJavadocAction.setImageDescriptor(DESC_ELCL_OPEN_BROWSER); openAttachedJavadocAction.setDisabledImageDescriptor(DESC_DLCL_OPEN_BROWSER); selectionProvider.addSelectionChangedListener(openAttachedJavadocAction); selectionProvider.setSelection(new StructuredSelection()); tbm.add(openAttachedJavadocAction); }*/ IInputChangedListener inputChangeListener = new IInputChangedListener() { public void inputChanged(Object newInput) { backAction.update(); forwardAction.update(); // if (newInput == null) { // selectionProvider.setSelection(new StructuredSelection()); // } // else boolean isDeclaration = false; if (newInput instanceof CeylonBrowserInput) { // Object inputElement = ((CeylonBrowserInput) newInput).getInputElement(); // selectionProvider.setSelection(new StructuredSelection(inputElement)); //showInJavadocViewAction.setEnabled(isJavaElementInput); isDeclaration = ((CeylonBrowserInput) newInput).getAddress() != null; } openDeclarationAction.setEnabled(isDeclaration); } }; control.addInputChangeListener(inputChangeListener); tbm.update(true); docHover.addLinkListener(control); return control; } else { return new DefaultInformationControl(parent, true); } } } private final class HoverControlCreator extends AbstractReusableInformationControlCreator { private final DocumentationHover docHover; private String statusLineMessage; private final IInformationControlCreator enrichedControlCreator; HoverControlCreator(DocumentationHover docHover, IInformationControlCreator enrichedControlCreator, String statusLineMessage) { this.docHover = docHover; this.enrichedControlCreator = enrichedControlCreator; this.statusLineMessage = statusLineMessage; } @Override public IInformationControl doCreateInformationControl(Shell parent) { if (enrichedControlCreator != null && isAvailable(parent)) { BrowserInformationControl control = new BrowserInformationControl(parent, APPEARANCE_JAVADOC_FONT, statusLineMessage) { @Override public IInformationControlCreator getInformationPresenterControlCreator() { return enrichedControlCreator; } }; if (docHover != null) { docHover.addLinkListener(control); } return control; } else { return new DefaultInformationControl(parent, statusLineMessage); } } } }