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.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.getJavaElement;
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.imports.ModuleImportUtil.appendNativeBackends;
import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.ALTERNATE_ICONS;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getModelLoader;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getTypeCheckers;
import static com.redhat.ceylon.eclipse.core.debug.DebugUtils.getFrame;
import static com.redhat.ceylon.eclipse.core.debug.DebugUtils.getJdiProducedType;
import static com.redhat.ceylon.eclipse.core.debug.DebugUtils.producedTypeFromTypeDescriptor;
import static com.redhat.ceylon.eclipse.core.debug.DebugUtils.toModelProducedType;
import static com.redhat.ceylon.eclipse.core.debug.hover.CeylonDebugHover.jdiVariableForTypeParameter;
import static com.redhat.ceylon.eclipse.util.Highlights.ANNOTATIONS;
import static com.redhat.ceylon.eclipse.util.Highlights.ANNOTATION_STRINGS;
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 com.redhat.ceylon.eclipse.util.InteropUtils.toJavaString;
import static com.redhat.ceylon.model.typechecker.model.ModelUtil.isResolvable;
import static java.lang.Character.codePointCount;
import static java.lang.Double.parseDouble;
import static java.lang.Long.parseLong;
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.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.DebugException;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaVariable;
import org.eclipse.jdt.internal.debug.core.model.JDIStackFrame;
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.text.IDocument;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.ui.ISharedImages;
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.ModuleSearchResult.ModuleDetails;
import com.redhat.ceylon.common.Backends;
import com.redhat.ceylon.compiler.typechecker.TypeChecker;
import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit;
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.eclipse.code.browser.BrowserInformationControl;
import com.redhat.ceylon.eclipse.code.browser.BrowserInput;
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.ui.CeylonPlugin;
import com.redhat.ceylon.eclipse.util.UnlinkedSpanEmitter;
import com.redhat.ceylon.ide.common.model.BaseIdeModelLoader;
import com.redhat.ceylon.ide.common.model.CeylonUnit;
import com.redhat.ceylon.model.cmr.JDKUtils;
import com.redhat.ceylon.model.typechecker.model.Cancellable;
import com.redhat.ceylon.model.typechecker.model.Class;
import com.redhat.ceylon.model.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.model.typechecker.model.Constructor;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Function;
import com.redhat.ceylon.model.typechecker.model.FunctionOrValue;
import com.redhat.ceylon.model.typechecker.model.Functional;
import com.redhat.ceylon.model.typechecker.model.Generic;
import com.redhat.ceylon.model.typechecker.model.Interface;
import com.redhat.ceylon.model.typechecker.model.Module;
import com.redhat.ceylon.model.typechecker.model.NothingType;
import com.redhat.ceylon.model.typechecker.model.Package;
import com.redhat.ceylon.model.typechecker.model.Parameter;
import com.redhat.ceylon.model.typechecker.model.ParameterList;
import com.redhat.ceylon.model.typechecker.model.Reference;
import com.redhat.ceylon.model.typechecker.model.Referenceable;
import com.redhat.ceylon.model.typechecker.model.Scope;
import com.redhat.ceylon.model.typechecker.model.Type;
import com.redhat.ceylon.model.typechecker.model.TypeAlias;
import com.redhat.ceylon.model.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.model.typechecker.model.TypeParameter;
import com.redhat.ceylon.model.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.model.typechecker.model.TypedReference;
import com.redhat.ceylon.model.typechecker.model.Unit;
import com.redhat.ceylon.model.typechecker.model.Value;
import com.redhat.ceylon.model.typechecker.util.TypePrinter;
public class DocumentationHover extends SourceInfoHover {
public static final String smallerSize = "90%";
public static final String annotationSize = "85%";
public static final String largerSize = "103%";
public DocumentationHover(CeylonEditor editor) {
super(editor);
}
/**
* 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);
}
}
}
static void close(BrowserInformationControl control) {
control.notifyDelayedInputChange(null);
control.dispose();
}
@Override
public IInformationControlCreator getHoverControlCreator() {
return new CeylonInformationControlCreator(editor,
"F2 for focus");
}
public static Referenceable getLinkedModel(String location,
CeylonEditor editor) {
CeylonParseController controller =
editor.getParseController();
if (location==null) {
return null;
}
else if (location.matches("doc:ceylon.language/.*:ceylon.language:Nothing")) {
Unit unit = controller.getLastCompilationUnit().getUnit();
return unit.getNothingDeclaration();
}
return getLinkedModel(location,
controller.getTypeChecker());
}
public static Referenceable getLinkedModel(String location) {
if (location==null) {
return null;
}
for (TypeChecker typeChecker: getTypeCheckers()) {
Referenceable linkedModel =
getLinkedModel(location, typeChecker);
if (linkedModel!=null) {
return linkedModel;
}
}
return null;
}
public static Referenceable getLinkedModel(String location,
TypeChecker typeChecker) {
int firstColumnIndex = location.indexOf(':');
int moduleVersionSeparator = location.indexOf('/', firstColumnIndex);
String moduleName = location.substring(firstColumnIndex+1, moduleVersionSeparator);
int secondColumnIndex = location.indexOf(':', moduleVersionSeparator);
String moduleVersion = location.substring(moduleVersionSeparator+1, secondColumnIndex);
String remainingLocation = location.substring(secondColumnIndex+1);
BaseIdeModelLoader modelLoader =
getModelLoader(typeChecker);
Module module =
modelLoader.getLoadedModule(moduleName,
moduleVersion);
if (module==null || remainingLocation.isEmpty()) {
return module;
}
String[] bits = remainingLocation.split(":");
Referenceable target =
module.getPackage(bits[0]);
for (int i=1; i<bits.length; i++) {
Scope scope;
if (target instanceof Scope) {
scope = (Scope) target;
}
else if (target instanceof TypedDeclaration) {
TypedDeclaration td =
(TypedDeclaration) target;
scope = td.getType().getDeclaration();
}
else {
return null;
}
if (scope instanceof Value) {
Value v = (Value) scope;
TypeDeclaration val =
v.getTypeDeclaration();
if (val.isAnonymous()) {
scope = val;
}
}
target = scope.getDirectMember(bits[i], null, false);
}
return target;
}
public String getHoverInfo(ITextViewer textViewer,
IRegion hoverRegion) {
if (editor==null ||
editor.getSelectionProvider()==null) {
return null;
}
String result =
getExpressionHoverText(editor, hoverRegion);
if (result==null) {
result = getHoverText(editor, hoverRegion);
}
return result;
}
@Override
public CeylonBrowserInput getHoverInfo2(
ITextViewer textViewer, IRegion hoverRegion) {
if (editor==null ||
editor.getSelectionProvider()==null) {
return null;
}
String result =
getExpressionHoverText(editor, hoverRegion);
if (result!=null) {
return new CeylonBrowserInput(null, null, result);
}
else {
result = getHoverText(editor, hoverRegion);
if (result!=null) {
return new CeylonBrowserInput(null,
getModel(editor, hoverRegion),
result);
}
}
return null;
}
static String getExpressionHoverText(
CeylonEditor editor, IRegion hoverRegion) {
CeylonParseController parseController =
editor.getParseController();
if (parseController==null) {
return null;
}
Tree.CompilationUnit rootNode =
parseController.getTypecheckedRootNode();
if (rootNode!=null) {
int hoffset = hoverRegion.getOffset();
int hlength = hoverRegion.getLength();
ITextSelection selection =
editor.getSelectionFromThread();
if (selection!=null) {
int offset = selection.getOffset();
int length = selection.getLength();
if (offset<=hoffset &&
offset+length>=hoffset+hlength) {
Node node =
findNode(rootNode,
parseController.getTokens(),
selection);
IDocument document =
editor.getCeylonSourceViewer()
.getDocument();
IProject project =
editor.getParseController()
.getProject();
if (node instanceof Tree.Type) {
return getTypeHoverText(node,
selection.getText(),
document,
project);
}
if (node instanceof Tree.Expression) {
Tree.Expression expression =
(Tree.Expression) node;
node = expression.getTerm();
}
if (node instanceof Tree.Term) {
return getTermTypeHoverText(node,
selection.getText(),
document,
project);
}
}
}
}
return null;
}
static Referenceable getModel(CeylonEditor editor,
IRegion hoverRegion) {
Node node =
getHoverNode(hoverRegion,
editor.getParseController());
return node==null ? null :
getReferencedDeclaration(node);
}
static String getHoverText(CeylonEditor editor,
IRegion hoverRegion) {
CeylonParseController parseController =
editor.getParseController();
Node node =
getHoverNode(hoverRegion, parseController);
if (node!=null) {
IProject project =
parseController.getProject();
if (node instanceof Tree.LocalModifier) {
return getInferredTypeHoverText(node,
project);
}
else if (node instanceof Tree.Literal) {
IDocument document =
editor.getCeylonSourceViewer()
.getDocument();
return getTermTypeHoverText(node, null,
document, project);
}
else {
Referenceable model =
getReferencedDeclaration(node);
return getDocumentationHoverText(model,
editor, node, null);
}
}
else {
return null;
}
}
private static String getInferredTypeHoverText(Node node,
IProject project) {
Tree.LocalModifier local = (Tree.LocalModifier) node;
Type t = local.getTypeModel();
if (t==null) return null;
StringBuilder buffer = new StringBuilder();
HTMLPrinter.insertPageProlog(buffer, 0,
HTML.getStyleSheet());
appendTypeInfo(buffer, node, t);
buffer.append("<br/>");
if (!t.containsUnknowns()) {
buffer.append("One quick assist available:<br/>");
HTML.addImageAndLabel(buffer, null,
HTML.fileUrl("correction_change.png")
.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 buffer.toString();
}
static String getTypeHoverText(
Node node, String selectedText,
IDocument doc, IProject project) {
Tree.Type type = (Tree.Type) node;
Type t = type.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());
appendTypeInfo(buffer, node, t);
HTMLPrinter.addPageEpilog(buffer);
return buffer.toString();
}
private static void appendTypeInfo(StringBuilder buffer,
Node node, Type type) {
Unit unit = node.getUnit();
String abbreviated =
PRINTER.print(type,unit);
String unabbreviated =
VERBOSE_PRINTER.print(type,unit);
String simplified =
PRINTER.print(unit.denotableType(type),unit);
String desc;
if (node instanceof Tree.Term) {
desc = node instanceof Tree.Literal ?
"Literal of type" :
"Expression of type";
}
else if (node instanceof Tree.LocalModifier) {
desc = "Inferred type";
}
else {
desc = "Type";
}
HTML.addImageAndLabel(buffer, null,
HTML.fileUrl("types.png").toExternalForm(),
16, 16,
desc +
" <tt>" +
producedTypeLink(type,unit) +
"</tt> ",
20, 4);
if (!abbreviated.equals(unabbreviated)) {
buffer.append("<p>Abbreviation of ")
.append(unabbreviated)
.append("</p>");
}
if (!simplified.equals(unabbreviated) &&
!simplified.equals(abbreviated)) {
buffer.append("<p>Simplifies to ")
.append(simplified)
.append("</p>");
}
}
private static String escape(String string) {
return string
.replace("\0", "\\0")
.replace("\b", "\\b")
.replace("\t", "\\t")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\f", "\\f")
.replace("\u001b", "\\e");
}
private static String getTermTypeHoverText(
Node node, String selectedText,
IDocument doc, IProject project) {
Tree.Term term = (Tree.Term) node;
Type type = term.getTypeModel();
if (type==null) return null;
StringBuilder buffer = new StringBuilder();
HTMLPrinter.insertPageProlog(buffer, 0,
HTML.getStyleSheet());
appendTypeInfo(buffer, node, type);
String text = node.getText();
if (node instanceof Tree.StringLiteral) {
appendStringHoverInfo(buffer, text);
// If a single char selection, then append info
// on that character too
if (selectedText!=null) {
int count =
codePointCount(selectedText,
0, selectedText.length());
if (count == 1) {
appendCharacterHoverInfo(buffer,
selectedText);
}
}
}
else if (node instanceof Tree.CharLiteral) {
if (text.length()>2) {
appendCharacterHoverInfo(buffer,
text.substring(1, text.length()-1));
}
}
else if (node instanceof Tree.NaturalLiteral) {
appendIntegerHoverInfo(buffer, text);
}
else if (node instanceof Tree.FloatLiteral) {
appendFloatHoverInfo(buffer, text);
}
HTMLPrinter.addPageEpilog(buffer);
return buffer.toString();
}
private static void appendStringHoverInfo(
StringBuilder buffer, String text) {
String escaped = escape(text);
if (escaped.length()>250) {
escaped = escaped.substring(0,250) + "...";
}
String html =
convertToHTMLContent(escaped)
.replace("\\n", "<br/>");
buffer.append( "<br/>")
.append("<code style='color:")
.append(toHex(getCurrentThemeColor(STRINGS)))
.append("'><pre>")
.append('\"')
.append(html)
.append('\"')
.append("</pre></code>");
}
private static void appendFloatHoverInfo(
StringBuilder buffer, String text) {
text = text.replace("_", "");
try {
buffer.append("<br/>")
.append("<code style='color:")
.append(toHex(getCurrentThemeColor(NUMBERS)))
.append("'>")
.append(parseDouble(text))
.append("</code>");
}
catch (NumberFormatException nfe) {}
}
private static void appendIntegerHoverInfo(
StringBuilder buffer, String text) {
text = text.replace("_", "");
try {
buffer.append("<br/>")
.append("<code style='color:")
.append(toHex(getCurrentThemeColor(NUMBERS)))
.append("'>");
switch (text.charAt(0)) {
case '#':
long hex = parseLong(text.substring(1), 16);
buffer.append(hex);
break;
case '$':
long bin = parseLong(text.substring(1), 2);
buffer.append(bin);
break;
default:
buffer.append(parseLong(text));
}
buffer.append("</code>");
}
catch (NumberFormatException nfe) {}
}
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);
String hex =
Integer.toHexString(codepoint)
.toUpperCase();
while (hex.length() < 4) {
hex = "0" + hex;
}
String category =
getCodepointGeneralCategoryName(codepoint);
String script =
Character.UnicodeScript.of(codepoint)
.name();
String block =
Character.UnicodeBlock.of(codepoint)
.toString();
buffer.append("<br/>Unicode Name: <code>")
.append(name)
.append("</code>");
buffer.append("<br/>Codepoint: <code>")
.append("U+")
.append(hex)
.append("</code>");
buffer.append("<br/>General Category: <code>")
.append(category)
.append("</code>");
buffer.append("<br/>Script: <code>")
.append(script)
.append("</code>");
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;
}
static String getIcon(Object obj) {
if (obj instanceof Module) {
return "jar_l_obj.gif";
}
else if (obj instanceof Package) {
return "package_obj.png";
}
else if (obj instanceof Declaration) {
Declaration dec = (Declaration) obj;
if (CeylonPlugin.getPreferences().getBoolean(ALTERNATE_ICONS)) {
if (dec instanceof Class) {
if (dec.isAnonymous()) {
return "anonymousClass.png";
}
return "class.png";
}
else if (dec instanceof Interface) {
return "interface.png";
}
else if (dec instanceof Constructor) {
return "classInitializer.png";
}
else if (dec.isParameter()) {
return "parameter.png";
}
else if (dec instanceof Value) {
return "field.png";
}
else if (dec instanceof Function) {
return dec.isShared() ?
"method.png" :
"function.png";
}
else if (dec instanceof TypeParameter) {
return "variable.png";
}
}
if (dec instanceof Class) {
String icon = dec.isShared() ?
"class_obj.png" :
"innerclass_private_obj.png";
return decorateTypeIcon(dec, icon);
}
else if (dec instanceof Interface) {
String icon = dec.isShared() ?
"int_obj.png" :
"innerinterface_private_obj.png";
return decorateTypeIcon(dec, icon);
}
else if (dec instanceof Constructor) {
String icon = dec.isShared() ?
"constructor.png" :
"constructor.png"; //TODO!!!!!!
return icon;
// return decorateTypeIcon(dec, icon);
}
else if (dec instanceof TypeAlias||
dec instanceof NothingType) {
return "type_alias.gif";
}
else if (dec.isParameter()) {
if (dec instanceof Function) {
return "methpro_obj.png";
}
else {
return "field_protected_obj.png";
}
}
else if (dec instanceof Function) {
String icon = dec.isShared() ?
"methpub_obj.png" :
"methpri_obj.png";
return decorateFunctionIcon(dec, icon);
}
else if (dec instanceof FunctionOrValue) {
return dec.isShared() ?
"field_public_obj.png" :
"field_private_obj.png";
}
else if (dec instanceof TypeParameter) {
return "typevariable_obj.png";
}
}
return null;
}
private static String decorateFunctionIcon(
Declaration dec, String icon) {
if (dec.isAnnotation()) {
return icon.replace("obj", "ann")
.replace("png", "gif");
}
else {
return icon;
}
}
private static String decorateTypeIcon(
Declaration dec, String icon) {
TypeDeclaration td = (TypeDeclaration) dec;
if (td.getCaseTypes()!=null) {
return icon.replace("obj", "enum")
.replace("png", "gif");
}
else if (dec.isAnnotation()) {
return icon.replace("obj", "ann")
.replace("png", "gif");
}
else if (td.isAlias()) {
return icon.replace("obj", "alias")
.replace("png", "gif");
}
else {
return icon;
}
}
/**
* Computes the hover info.
* @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
*/
public static String getDocumentationHoverText(
Referenceable model, CeylonEditor editor,
Node node, IProgressMonitor monitor) {
CeylonParseController parseController =
editor.getParseController();
if (model instanceof Declaration) {
Declaration dec = (Declaration) model;
return getDocumentationFor(parseController, dec,
node, null, null);
}
else if (model instanceof Package) {
Package dec = (Package) model;
return getDocumentationFor(parseController, dec);
}
else if (model instanceof Module) {
Module dec = (Module) model;
return getDocumentationFor(parseController, 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);
String javadocText = getHtmlContent(mem);
if (javadocText!=null) {
sb.append("<br/>").append(javadocText);
String base =
getBaseURL(mem, mem.isBinary());
int endHeadIdx= sb.indexOf("</head>");
if (endHeadIdx>0) {
sb.insert(endHeadIdx,
"\n<base href='" + base + "'>\n");
}
}
}
catch (JavaModelException e) {
e.printStackTrace();
}
}
}
private static String getHtmlContent(IMember mem) {
for (java.lang.reflect.Method m:
JavadocContentAccess2.class
.getDeclaredMethods()) {
if (m.getName().equals("getHTMLContent")) {
try {
Object[] args = { mem, true };
return (String) m.invoke(null, args);
}
catch (Exception e) {}
}
}
return null;
}
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) {
String urlString = baseURL.toExternalForm();
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 =
getWorkbench()
.getHelpSystem()
.resolve(urlString, true);
if (baseURL2 != null) { // can be null if org.eclipse.help.ui is not available
baseURL = baseURL2;
}
}
return urlString;
}
}
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 controller, Package pack) {
StringBuilder buffer = new StringBuilder();
insertPageProlog(buffer, 0, HTML.getStyleSheet());
addMainPackageDescription(pack, buffer);
addPackageDocumentation(controller, 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.getName()==null) {
continue;
}
if (dec instanceof Class) {
Class c = (Class) dec;
if (c.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 model) {
buffer.append("<tt><a ")
.append(HTML.link(model))
.append(">")
.append(model.getNameAsString())
.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();
String label;
boolean defaultPackage =
mod.getNameAsString().isEmpty() ||
mod.getNameAsString().equals("default");
if (defaultPackage) {
label = "<span>Belongs to default module.</span>";
}
else {
label = "<span>Belongs to " +
link(mod) +
" <tt><span style='color:" +
toHex(getCurrentThemeColor(STRINGS)) +
"'>\"" +
mod.getVersion() +
"\"</span></tt>" + ".</span>";
}
HTML.addImageAndLabel(buffer, mod,
HTML.fileUrl(getIcon(mod))
.toExternalForm(),
16, 16, label, 20, 2);
}
private static String description(Package pack) {
return "package " + pack.getNameAsString();
}
public static String getDocumentationFor(ModuleDetails mod,
String version, Scope scope, Unit unit) {
return getDocumentationForModule(mod.getName(),
version, mod.getDoc(), scope, unit);
}
public static String getDocumentationFor(ModuleDetails mod,
String version, String packageName, Scope scope,
Unit unit) {
StringBuilder buffer = new StringBuilder();
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, null,
HTML.fileUrl("package_obj.png")
.toExternalForm(),
16, 16,
"<tt><span style='font-size:" + largerSize + "'>" +
HTML.highlightLine("package " + packageName) +
"</span></tt>",
20, 4);
buffer.append("<p>This package belongs to the unimported module <code> ")
.append(mod.getName())
.append("</code>, which will be automatically added to the descriptor of the current module.<p>");
HTML.addImageAndLabel(buffer, null,
HTML.fileUrl("jar_l_obj.gif")
.toExternalForm(),
16, 16,
"<tt><span style='font-size:" + largerSize + "'>" +
HTML.highlightLine("import " + mod.getName() +
" \"" + version + "\"") +
"</span></tt></b>",
20, 4);
if (mod.getDoc()!=null) {
buffer.append(markdown(mod.getDoc(), scope, unit));
}
insertPageProlog(buffer, 0, HTML.getStyleSheet());
addPageEpilog(buffer);
return buffer.toString();
}
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 + "\"";
}
public static String getDocumentationFor(
CeylonParseController controller, Module mod) {
StringBuilder buffer = new StringBuilder();
insertPageProlog(buffer, 0, HTML.getStyleSheet());
addMainModuleDescription(mod, buffer);
addAdditionalModuleInfo(buffer, mod);
addModuleDocumentation(controller, 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.isDefaultModule()) {
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) {
StringBuilder buf = new StringBuilder();
if (mod.isNative()) buf.append("native");
Backends nativeBackends = mod.getNativeBackends();
if (!nativeBackends.none() && !Backends.HEADER.equals(nativeBackends)) {
String color = toHex(getCurrentThemeColor(ANNOTATION_STRINGS));
buf.append("(<span style='color:")
.append(color)
.append("'>");
appendNativeBackends(buf, nativeBackends);
buf.append("</span>)");
}
if (mod.isNative()) buf.append(" ");
if (buf.length()!=0) {
String color = toHex(getCurrentThemeColor(ANNOTATIONS));
HTML.addImageAndLabel(buffer, null,
HTML.fileUrl("annotation_obj.gif")
.toExternalForm(),
16, 16,
"<tt><span style='font-size:" +
annotationSize + ";color:" + color + "'>" +
buf + "</span></tt>",
20, 4);
}
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());
Tree.AnnotationList annotationList =
refnode.getAnnotationList();
appendDocAnnotationContent(annotationList,
buffer, linkScope);
appendThrowAnnotationContent(annotationList,
buffer, linkScope);
appendSeeAnnotationContent(annotationList,
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) {
Tree.AnnotationList annotationList =
refnode.getAnnotationList();
appendDocAnnotationContent(annotationList,
buffer, pack);
appendThrowAnnotationContent(annotationList,
buffer, pack);
appendSeeAnnotationContent(annotationList,
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 " +
mod.getNameAsString() +
" \"" + mod.getVersion() + "\"";
}
public static String getDocumentationFor(
CeylonParseController controller,
Declaration dec,
IProgressMonitor monitor) {
return getDocumentationFor(controller, dec, null, null, monitor);
}
public static String getDocumentationFor(
CeylonParseController controller,
Declaration dec, Reference pr,
IProgressMonitor monitor) {
return getDocumentationFor(controller, dec, null, pr, monitor);
}
private static String getDocumentationFor(
CeylonParseController controller,
Declaration dec, Node node, Reference pr,
IProgressMonitor monitor) {
if (dec==null) return null;
if (dec instanceof FunctionOrValue) {
FunctionOrValue value = (FunctionOrValue) dec;
TypeDeclaration valueType =
value.getTypeDeclaration();
if (valueType!=null &&
valueType.isAnonymous() &&
!value.getType().isTypeConstructor()) {
dec = valueType;
}
}
Unit unit = controller==null ? null :
controller.getLastCompilationUnit().getUnit();
Unit declarationUnit = dec.getUnit();
if (declarationUnit instanceof CeylonUnit) {
CeylonUnit cu = (CeylonUnit) declarationUnit;
PhasedUnit declarationPhasedUnit = cu.getPhasedUnit();
if (declarationPhasedUnit!=null) {
declarationPhasedUnit.analyseTypes(Cancellable.ALWAYS_CANCELLED);
}
}
StringBuilder buffer = new StringBuilder();
insertPageProlog(buffer, 0, HTML.getStyleSheet());
addMainDescription(buffer, dec, node, pr, controller, unit);
boolean obj = addInheritanceInfo(dec, node, pr, buffer, unit);
addContainerInfo(dec, pr, node, buffer);
if (!(dec instanceof NothingType)) {
addPackageInfo(dec, buffer);
}
boolean hasDoc = addDoc(dec, node, buffer, monitor);
addRefinementInfo(dec, pr, node, buffer, hasDoc, unit);
addReturnType(dec, buffer, node, pr, obj, unit);
addParameters(controller, dec, node, pr, buffer, unit);
addClassMembersInfo(dec, buffer);
if (dec instanceof NothingType) {
addNothingTypeInfo(buffer);
}
else {
addUnitInfo(dec, buffer);
}
addPageEpilog(buffer);
return buffer.toString();
}
private static void addMainDescription(StringBuilder buffer,
Declaration dec, Node node, Reference 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 value = (Value) dec;
if (value.isLate()) buf.append("late ");
}
if (isVariable(dec)) buf.append("variable ");
if (dec.isNative()) buf.append("native");
Backends nativeBackends = dec.getNativeBackends();
if (!nativeBackends.none() && !Backends.HEADER.equals(nativeBackends)) {
String color = toHex(getCurrentThemeColor(ANNOTATION_STRINGS));
buf.append("(<span style='color:")
.append(color)
.append("'>");
appendNativeBackends(buf, nativeBackends);
buf.append("</span>)");
}
if (dec.isNative()) buf.append(" ");
if (dec instanceof TypeDeclaration) {
TypeDeclaration td = (TypeDeclaration) dec;
if (td.isSealed()) buf.append("sealed ");
if (td.isFinal() && !(td instanceof Constructor)) {
buf.append("final ");
}
if (td instanceof Class) {
Class c = (Class) td;
if (c.isAbstract()) {
buf.append("abstract ");
}
}
}
if (dec.isAnnotation()) buf.append("annotation ");
if (buf.length()!=0) {
String color = toHex(getCurrentThemeColor(ANNOTATIONS));
HTML.addImageAndLabel(buffer, null,
HTML.fileUrl("annotation_obj.gif")
.toExternalForm(),
16, 16,
"<tt><span style='font-size:" +
annotationSize + ";color:" + color + "'>" +
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) {
boolean first = true;
for (Declaration mem: dec.getMembers()) {
if (isResolvable(mem) && mem.isShared() &&
(!mem.isOverloaded() ||
mem.isAbstraction())) {
if (first) {
buffer.append("<p>Members: ");
first = false;
}
else {
buffer.append(", ");
}
appendLink(buffer, mem);
}
}
if (!first) {
buffer.append(".</p>");
}
}
}
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, Reference pr, StringBuilder buffer,
Unit unit) {
buffer.append("<p><div style='padding-left:20px'>");
boolean obj=false;
if (dec instanceof TypedDeclaration) {
TypedDeclaration d = (TypedDeclaration) dec;
TypeDeclaration td = d.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(Declaration dec,
Reference pr, 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();
Type sup =
getQualifyingType(dec, pr, node)
.getSupertype(superclass);
String icon = rd.isFormal() ?
"implm_co.png" : "over_co.png";
HTML.addImageAndLabel(buffer, rd,
HTML.fileUrl(icon).toExternalForm(),
16, 16,
"Refines " + link(rd) +
" declared by <tt>" +
producedTypeLink(sup, unit) + "</tt>.",
20, 2);
buffer.append("</p>");
if (!hasDoc) {
Tree.Declaration decNode =
(Tree.Declaration)
getReferencedNode(rd);
if (decNode!=null) {
Tree.AnnotationList annotationList =
decNode.getAnnotationList();
appendDocAnnotationContent(annotationList,
buffer, resolveScope(rd));
}
}
}
}
private static void appendParameters(Declaration dec,
Reference pr, Unit unit,
StringBuilder result/*, CeylonParseController cpc*/) {
if (dec instanceof Functional) {
Functional fun = (Functional) dec;
List<ParameterList> plists =
fun.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,
Reference pr, Parameter p, Unit unit) {
result.append("<tt>");
if (p.getModel() == null) {
result.append(p.getName());
result.append("</tt>");
}
else {
TypedReference ppr = pr==null ? null :
pr.getTypedParameter(p);
if (p.isDeclaredVoid()) {
result.append(HTML.keyword("void"));
}
else {
if (ppr!=null) {
Type 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 Function) {
result.append(HTML.keyword("function"));
}
else {
result.append(HTML.keyword("value"));
}
}
result.append(" ");
result.append("</tt>");
appendLink(result, p.getModel());
appendParameters(p.getModel(), ppr, unit, result);
}
}
private static void addParameters(CeylonParseController cpc,
Declaration dec, Node node, Reference pr,
StringBuilder buffer, Unit unit) {
if (dec instanceof Functional) {
if (pr==null) {
pr = appliedReference(dec, node);
}
if (pr==null) return;
Functional fun = (Functional) dec;
List<ParameterList> pls =
fun.getParameterLists();
for (ParameterList pl: pls) {
if (!pl.getParameters().isEmpty()) {
buffer.append("<p>");
for (Parameter p: pl.getParameters()) {
FunctionOrValue model = p.getModel();
if (model!=null) {
StringBuilder param =
new StringBuilder();
param.append("Accepts ");
// param.append("<span style='font-size:" + smallerSize + "'>accepts ");
appendParameter(param, pr, p, unit);
String init =
getInitialValueDescription(
model, cpc);
param.append("<tt>")
.append(HTML.highlightLine(init))
.append("</tt>")
.append(".");
Tree.Declaration refNode =
(Tree.Declaration)
getReferencedNode(model);
if (refNode!=null) {
Tree.AnnotationList annotationList =
refNode.getAnnotationList();
appendDocAnnotationContent(annotationList,
param, resolveScope(dec));
}
// param.append("</span>");
HTML.addImageAndLabel(buffer, model,
HTML.fileUrl("methpro_obj.png")
.toExternalForm(),
16, 16, param.toString(), 20, 2);
}
}
buffer.append("</p>");
}
}
}
}
private static void addReturnType(
Declaration dec, StringBuilder buffer,
Node node, Reference pr, boolean obj,
Unit unit) {
if (dec instanceof TypedDeclaration && !obj) {
if (pr==null) {
pr = appliedReference(dec, node);
}
if (pr==null) return;
Type 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.png")
.toExternalForm(),
16, 16, buf.toString(), 20, 2);
buffer.append("</p>");
}
}
}
private static TypePrinter printer(boolean abbreviate) {
return new TypePrinter(abbreviate, true, false, true, false) {
@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 ">";
}
};
}
static TypePrinter PRINTER = printer(true);
static TypePrinter VERBOSE_PRINTER = printer(false);
private static String producedTypeLink(Type pt, Unit unit) {
return PRINTER.print(pt, unit);
}
private static List<Type> getTypeParameters(Declaration dec) {
if (dec instanceof Generic) {
List<TypeParameter> typeParameters =
((Generic) dec).getTypeParameters();
if (typeParameters.isEmpty()) {
return Collections.<Type>emptyList();
}
else {
List<Type> list =
new ArrayList<Type>();
for (TypeParameter p: typeParameters) {
list.add(p.getType());
}
return list;
}
}
else {
return Collections.<Type>emptyList();
}
}
private static Reference appliedReference(Declaration dec,
Node node) {
if (node instanceof Tree.TypeDeclaration) {
TypeDeclaration td = (TypeDeclaration) dec;
return td.getType();
}
else if (node instanceof Tree.MemberOrTypeExpression) {
Tree.MemberOrTypeExpression mte =
(Tree.MemberOrTypeExpression) node;
return mte.getTarget();
}
else if (node instanceof Tree.Type) {
Tree.Type t = (Tree.Type) node;
return t.getTypeModel();
}
else {
//a member declaration - unfortunately there is
//nothing matching TypeDeclaration.getType() for
//TypedDeclarations!
Type qt;
if (dec.isClassOrInterfaceMember()) {
ClassOrInterface ci =
(ClassOrInterface)
dec.getContainer();
qt = ci.getType();
}
else {
qt = null;
}
return dec.appliedReference(qt,
getTypeParameters(dec));
}
}
private static boolean addDoc(Declaration dec,
Node node, StringBuilder buffer, IProgressMonitor monitor) {
boolean hasDoc = false;
Node rn = getReferencedNode(dec);
if (rn instanceof Tree.SpecifierStatement) {
Tree.SpecifierStatement ss =
(Tree.SpecifierStatement) rn;
rn = getReferencedNode(ss.getRefined());
}
if (rn instanceof Tree.Declaration) {
Tree.Declaration refnode =
(Tree.Declaration) rn;
Tree.AnnotationList annotationList =
refnode.getAnnotationList();
Scope scope = resolveScope(dec);
appendDeprecatedAnnotationContent(annotationList,
buffer, scope);
int len = buffer.length();
appendDocAnnotationContent(annotationList,
buffer, scope);
hasDoc = buffer.length()!=len;
appendThrowAnnotationContent(annotationList,
buffer, scope);
appendSeeAnnotationContent(annotationList,
buffer);
}
else {
appendJavadoc(dec, buffer, monitor);
}
return hasDoc;
}
private static void addContainerInfo(
Declaration dec, Reference pr,
Node node, StringBuilder buffer) {
Unit unit = node==null ? null : node.getUnit();
buffer.append("<p>");
if (dec.isParameter()) {
FunctionOrValue mv = (FunctionOrValue) dec;
Parameter ip = mv.getInitializerParameter();
Declaration pd = ip.getDeclaration();
if (pd.getName()==null) {
if (pd instanceof Constructor) {
buffer.append("Parameter of default constructor of");
appendParameterLink(buffer,
(Declaration) pd.getContainer());
buffer.append(".");
}
}
else if (pd.getName().startsWith("anonymous#")) {
buffer.append("Parameter of anonymous function.");
}
else {
buffer.append("Parameter of");
appendParameterLink(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) {
TypeParameter tp = (TypeParameter) dec;
Declaration pd = tp.getDeclaration();
buffer.append("Type parameter of");
appendParameterLink(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()) {
Type qt = getQualifyingType(dec, pr, node);
if (qt!=null) {
String desc;
if (dec instanceof Constructor) {
if (dec.getName()==null) {
desc = "Default constructor of";
}
else {
desc = "Constructor of";
}
}
else if (dec instanceof Value) {
if (dec.isStaticallyImportable()) {
desc = "Static attribute of";
}
else {
desc = "Attribute of";
}
}
else if (dec instanceof Function) {
if (dec.isStaticallyImportable()) {
desc = "Static method of";
}
else {
desc = "Method of";
}
}
else {
if (dec.isStaticallyImportable()) {
desc = "Static member of";
}
else {
desc = "Member of";
}
}
String typeDesc;
if (qt.getDeclaration().getName()
.startsWith("anonymous#")) {
typeDesc = " anonymous class";
}
else {
typeDesc = " " + "<tt>" +
producedTypeLink(qt, unit) +
"</tt>";
}
buffer.append(desc + typeDesc + ".");
// 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 appendParameterLink(
StringBuilder buffer, Declaration pd) {
if (pd instanceof Class) {
buffer.append(" class");
}
else if (pd instanceof Interface) {
buffer.append(" interface");
}
else if (pd instanceof Function) {
if (pd.isClassOrInterfaceMember()) {
buffer.append(" method");
}
else {
buffer.append(" function");
}
}
else if (pd instanceof Constructor) {
buffer.append(" constructor");
}
buffer.append(" ");
if (pd.isClassOrInterfaceMember()) {
appendLink(buffer,
(Referenceable) pd.getContainer());
buffer.append(".");
}
appendLink(buffer, pd);
}
private static void addPackageInfo(Declaration dec,
StringBuilder buffer) {
Package pack = dec.getUnit().getPackage();
if ((/*dec.isShared() ||*/ dec.isToplevel()) &&
!(dec instanceof NothingType)) {
String label;
if (pack.getNameAsString().isEmpty()) {
label = "<span>Member of default package.</span>";
}
else {
label = "<span>Member of package " +
link(pack) + ".</span>";
}
HTML.addImageAndLabel(buffer, pack,
HTML.fileUrl(getIcon(pack))
.toExternalForm(),
16, 16, label, 20, 2);
}
}
private static Type getQualifyingType(Declaration dec,
Reference r, Node node) {
if (r!=null) {
return r.getQualifyingType();
}
ClassOrInterface outer =
(ClassOrInterface)
dec.getContainer();
if (outer == null) {
return null;
}
if (node instanceof Tree.MemberOrTypeExpression) {
Tree.MemberOrTypeExpression mte =
(Tree.MemberOrTypeExpression) node;
Reference pr = mte.getTarget();
if (pr!=null) {
return pr.getQualifyingType();
}
}
if (node instanceof Tree.QualifiedType) {
Tree.QualifiedType qt =
(Tree.QualifiedType) node;
return qt.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.
CeylonUnit ceylonUnit =
(CeylonUnit) dec.getUnit();
unitName = toJavaString(ceylonUnit.getCeylonFileName());
}
if (unitName == null) {
unitName = dec.getUnit().getFilename();
}
HTML.addImageAndLabel(buffer, null,
HTML.fileUrl("unit.gif").toExternalForm(),
16, 16,
"<span>Declared in <tt><a href='dec:" +
HTML.declink(dec) + "'>" +
unitName + "</a></tt>.</span>",
20, 2);
addPackageModuleInfo(dec.getUnit().getPackage(), buffer);
//}
buffer.append("</p>");
}
private static void documentInheritance(TypeDeclaration dec,
Node node, Reference pr, StringBuilder buffer,
Unit unit) {
if (pr==null) {
pr = appliedReference(dec, node);
}
Type type;
if (pr instanceof Type) {
type = (Type) pr;
}
else {
type = dec.getType();
}
List<Type> cts = type.getCaseTypes();
if (cts!=null) {
StringBuilder cases = new StringBuilder();
for (Type 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.png").toExternalForm(),
16, 16,
" <tt><span style='font-size:" +
smallerSize + "'>of " +
cases +"</span></tt>",
20, 2);
}
if (dec instanceof Class) {
Type sup = type.getExtendedType();
if (sup!=null) {
HTML.addImageAndLabel(buffer, sup.getDeclaration(),
HTML.fileUrl("superclass.png").toExternalForm(),
16, 16,
"<tt><span style='font-size:" +
smallerSize + "'>extends " +
producedTypeLink(sup, unit) +"</span></tt>",
20, 2);
}
}
List<Type> sts = type.getSatisfiedTypes();
if (!sts.isEmpty()) {
StringBuilder satisfies = new StringBuilder();
for (Type st: sts) {
if (satisfies.length()>0) {
satisfies.append(" & ");
}
satisfies.append(producedTypeLink(st, unit));
}
HTML.addImageAndLabel(buffer, null,
HTML.fileUrl("super.png").toExternalForm(),
16, 16,
"<tt><span style='font-size:" +
smallerSize + "'>satisfies " +
satisfies + "</span></tt>",
20, 2);
}
}
private static void documentTypeParameters(Declaration dec,
Node node, Reference pr, StringBuilder buffer,
Unit unit) {
if (pr==null) {
pr = appliedReference(dec, node);
}
List<TypeParameter> typeParameters;
if (dec instanceof Generic) {
Generic g = (Generic) dec;
typeParameters = g.getTypeParameters();
}
else {
typeParameters = Collections.emptyList();
}
for (TypeParameter tp: typeParameters) {
StringBuilder bounds = new StringBuilder();
for (Type st: tp.getSatisfiedTypes()) {
if (bounds.length() == 0) {
bounds.append(" satisfies ");
}
else {
bounds.append(" & ");
}
Unit du = dec.getUnit();
bounds.append(producedTypeLink(st, du));
}
String arg= "";
String liveValue = getLiveValue(tp, unit);
if (liveValue!=null) {
arg = liveValue;
}
else {
Type typeArg = pr==null ? null :
pr.getTypeArguments().get(tp);
if (typeArg!=null &&
!tp.getType().isExactly(typeArg)) {
arg = " = " +
producedTypeLink(typeArg, unit);
}
}
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, Reference pr,
CeylonParseController cpc, Unit unit) {
if (pr==null) {
pr = appliedReference(dec, node);
}
String doc = getDocDescriptionFor(dec, pr, unit);
StringBuffer description = new StringBuffer(doc);
if (dec instanceof TypeDeclaration) {
TypeDeclaration td = (TypeDeclaration) dec;
if (td.isAlias()) {
Type et = td.getExtendedType();
if (et!=null) {
description.append(" => ")
.append(et.asString());
}
}
}
if (dec instanceof Value && !isVariable(dec) ||
dec instanceof Function) {
description.append(getInitialValueDescription(dec, cpc));
}
String result = HTML.highlightLine(description.toString());
String liveValue = getLiveValue(dec, unit);
return liveValue==null ? result : result+liveValue;
}
static String getLiveValue(Declaration dec, Unit unit) {
if (dec instanceof TypeParameter && unit!=null) {
TypeParameter typeParameter = (TypeParameter) dec;
JDIStackFrame stackFrame = getFrame();
if (stackFrame!=null) {
try {
IJavaVariable typeDescriptor =
jdiVariableForTypeParameter(
stackFrame.getJavaDebugTarget(),
stackFrame, typeParameter);
if (typeDescriptor!=null) {
IJavaObject jdiProducedType =
getJdiProducedType(typeDescriptor.getValue(),
producedTypeFromTypeDescriptor);
Type producedType =
toModelProducedType(jdiProducedType);
if (producedType != null) {
return new StringBuilder()
.append(" <i>= ")
.append(producedTypeLink(producedType, unit))
.append("</i>").toString();
}
}
}
catch (DebugException e) {
e.printStackTrace();
}
}
}
return null;
}
static void appendJavadoc(Declaration model,
StringBuilder buffer, IProgressMonitor monitor) {
try {
appendJavadoc(getJavaElement(model, monitor), buffer);
}
catch (JavaModelException jme) {
jme.printStackTrace();
}
}
private static void appendDocAnnotationContent(
Tree.AnnotationList annotationList,
StringBuilder documentation, Scope linkScope) {
if (annotationList!=null) {
AnonymousAnnotation aa =
annotationList.getAnonymousAnnotation();
Unit unit = annotationList.getUnit();
if (aa!=null) {
String text = aa.getStringLiteral().getText();
documentation.append(markdown(text, linkScope, unit));
// 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) {
Tree.BaseMemberExpression bme =
(Tree.BaseMemberExpression)
annotPrim;
String name = bme.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) {
Tree.ListedArgument la =
(Tree.ListedArgument) a;
String text =
la.getExpression()
.getTerm().getText();
if (text!=null) {
documentation.append(markdown(text,
linkScope, unit));
}
}
}
}
}
}
}
}
}
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) {
Tree.BaseMemberExpression bme =
(Tree.BaseMemberExpression)
annotPrim;
String name = bme.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) {
Tree.ListedArgument la =
(Tree.ListedArgument) a;
String text =
la.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) {
Tree.BaseMemberExpression bme =
(Tree.BaseMemberExpression)
annotPrim;
String name = bme.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.ListedArgument la =
(Tree.ListedArgument) arg;
Tree.Term term = la.getExpression().getTerm();
if (term instanceof Tree.MetaLiteral) {
Tree.MetaLiteral ml =
(Tree.MetaLiteral) term;
Declaration dec = ml.getDeclaration();
if (dec!=null) {
String dn = dec.getName();
if (dec.isClassOrInterfaceMember()) {
ClassOrInterface container =
(ClassOrInterface)
dec.getContainer();
dn = container.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) {
Tree.BaseMemberExpression bme =
(Tree.BaseMemberExpression)
annotPrim;
String name = bme.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.ListedArgument typeListedArg =
(Tree.ListedArgument) typeArg;
Tree.ListedArgument textListedArg =
(Tree.ListedArgument) textArg;
Tree.Term typeArgTerm =
typeListedArg.getExpression().getTerm();
Tree.Term textArgTerm = textArg==null ?
null : textListedArg.getExpression().getTerm();
String text =
textArgTerm instanceof Tree.StringLiteral ?
textArgTerm.getText() : "";
if (typeArgTerm instanceof Tree.MetaLiteral) {
Tree.MetaLiteral ml =
(Tree.MetaLiteral) typeArgTerm;
Declaration dec = ml.getDeclaration();
if (dec!=null) {
String dn = dec.getName();
if (typeArgTerm instanceof Tree.QualifiedMemberOrTypeExpression) {
Tree.QualifiedMemberOrTypeExpression qmte =
(Tree.QualifiedMemberOrTypeExpression) typeArgTerm;
Tree.Primary p = qmte.getPrimary();
if (p instanceof Tree.MemberOrTypeExpression) {
Tree.MemberOrTypeExpression mte =
(Tree.MemberOrTypeExpression) p;
dn = mte.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);
}
}
}
}
}
}
}
}
}
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();
}
}
public IInformationControlCreator getInformationPresenterControlCreator() {
return new CeylonEnrichedInformationControlCreator(editor);
}
}