blob: d0ddfdc726fccb3f2ead8f28d531f9e6c00d48e9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.javascript2.editor;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.ImageIcon;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.csl.api.CodeCompletionResult;
import org.netbeans.modules.csl.api.CompletionProposal;
import org.netbeans.modules.csl.api.ElementHandle;
import org.netbeans.modules.csl.api.ElementKind;
import org.netbeans.modules.csl.api.HtmlFormatter;
import org.netbeans.modules.csl.api.Modifier;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.modules.csl.spi.ParserResult;
import org.netbeans.modules.csl.spi.support.CancelSupport;
import org.netbeans.modules.html.editor.lib.api.model.HtmlTagAttribute;
import org.netbeans.modules.javascript2.lexer.api.JsTokenId;
import org.netbeans.modules.javascript2.lexer.api.LexUtilities;
import org.netbeans.modules.javascript2.model.api.IndexedElement;
import org.netbeans.modules.javascript2.model.api.JsElement;
import org.netbeans.modules.javascript2.model.api.JsFunction;
import org.netbeans.modules.javascript2.model.api.JsObject;
import org.netbeans.modules.javascript2.types.api.Type;
import org.netbeans.modules.javascript2.types.api.TypeUsage;
import org.netbeans.modules.javascript2.model.api.ModelUtils;
import org.netbeans.modules.javascript2.editor.options.OptionsUtils;
import org.netbeans.modules.javascript2.editor.parser.JsParserResult;
import org.netbeans.modules.javascript2.editor.spi.CompletionContext;
import org.netbeans.modules.javascript2.model.api.Index;
import org.netbeans.modules.javascript2.model.api.Model;
import org.netbeans.modules.parsing.api.Snapshot;
import org.openide.filesystems.FileObject;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle.Messages;
/**
*
* @author Petr Pisl
*/
public class JsCompletionItem implements CompletionProposal {
protected final CompletionRequest request;
protected final ElementHandle element;
JsCompletionItem(ElementHandle element, CompletionRequest request) {
this.element = element;
this.request = request;
}
@Override
public int getAnchorOffset() {
return LexUtilities.getLexerOffset((JsParserResult)request.info, request.anchor);
}
@Override
public ElementHandle getElement() {
return element;
}
@Override
public String getName() {
return element.getName();
}
@Override
public String getInsertPrefix() {
return element.getName();
}
@Override
public String getSortText() {
StringBuilder sb = new StringBuilder();
if (element != null) {
FileObject sourceFo = request.result.getSnapshot().getSource().getFileObject();
FileObject elementFo = element.getFileObject();
if (elementFo != null && sourceFo != null && sourceFo.equals(elementFo)) {
sb.append("1"); //NOI18N
} else {
if (OffsetRange.NONE.equals(element.getOffsetRange(request.result))) {
sb.append("8");
} else {
sb.append("9"); //NOI18N
}
}
}
sb.append(getName());
return sb.toString();
}
protected boolean isDeprecated() {
return element.getModifiers().contains(Modifier.DEPRECATED);
}
@Override
public String getLhsHtml(HtmlFormatter formatter) {
formatName(formatter);
return formatter.getText();
}
protected void formatName(HtmlFormatter formatter) {
if (isDeprecated()) {
formatter.deprecated(true);
formatter.appendText(getName());
formatter.deprecated(false);
} else {
formatter.appendText(getName());
}
}
@Messages("JsCompletionItem.lbl.js.platform=JS Platform")
@Override
public String getRhsHtml(HtmlFormatter formatter) {
String location = null;
if (element instanceof JsElement) {
JsElement jsElement = (JsElement) element;
if (jsElement.isPlatform()) {
location = Bundle.JsCompletionItem_lbl_js_platform();
} else if (jsElement.getSourceLabel() != null) {
location = jsElement.getSourceLabel();
}
}
if (location == null) {
location = getFileNameURL();
}
if (location == null) {
return null;
}
formatter.reset();
boolean isgues = OffsetRange.NONE.equals(element.getOffsetRange(request.result));
if (isgues) {
formatter.appendHtml("<font color=#999999>");
}
formatter.appendText(location);
if (isgues) {
formatter.appendHtml("</font>");
}
return formatter.getText();
}
@Override
public ElementKind getKind() {
return element.getKind();
}
@Override
public ImageIcon getIcon() {
return null;
}
@Override
public Set<Modifier> getModifiers() {
Set<Modifier> modifiers = (getElement() == null || getElement().getModifiers().isEmpty() ? Collections.EMPTY_SET : EnumSet.copyOf(getElement().getModifiers()));
if (modifiers.contains(Modifier.PRIVATE) && (modifiers.contains(Modifier.PUBLIC) || modifiers.contains(Modifier.PROTECTED))) {
modifiers.remove(Modifier.PUBLIC);
modifiers.remove(Modifier.PROTECTED);
}
return modifiers;
}
@Override
public boolean isSmart() {
// TODO implemented properly
return false;
}
@Override
public int getSortPrioOverride() {
int order = 100;
if (element != null && element instanceof JsElement) {
if (((JsElement)element).isPlatform()) {
if (ModelUtils.PROTOTYPE.equals(element.getName())) { //NOI18N
order = 1;
} else {
order = 0;
}
}
if (OffsetRange.NONE.equals(element.getOffsetRange(request.result))) {
order = 120;
}
}
return order;
}
@Override
public String getCustomInsertTemplate() {
return null;
}
@CheckForNull
public final String getFileNameURL() {
ElementHandle elem = getElement();
if (elem == null) {
return null;
}
FileObject fo = elem.getFileObject();
if (fo != null) {
return fo.getNameExt();
}
return getName();
}
public static class CompletionRequest {
public int anchor;
public JsParserResult result;
public ParserResult info;
public String prefix;
public CompletionContext completionContext;
public boolean addHtmlTagAttributes;
public CancelSupport cancelSupport;
}
private static ImageIcon priviligedIcon = null;
private static ImageIcon publicGenerator = null;
private static ImageIcon privateGenerator = null;
private static ImageIcon priviligedGenerator = null;
public static class JsFunctionCompletionItem extends JsCompletionItem {
private final Set<String> returnTypes;
private final Map<String, Set<String>> parametersTypes;
JsFunctionCompletionItem(ElementHandle element, CompletionRequest request, Set<String> resolvedReturnTypes, Map<String, Set<String>> parametersTypes) {
super(element, request);
this.returnTypes = resolvedReturnTypes != null ? resolvedReturnTypes : Collections.EMPTY_SET;
this.parametersTypes = parametersTypes != null ? parametersTypes : Collections.EMPTY_MAP;
}
@Override
public String getLhsHtml(HtmlFormatter formatter) {
formatter.setMaxLength(OptionsUtils.forLanguage(JsTokenId.javascriptLanguage()).getCodeCompletionItemSignatureWidth());
formatter.emphasis(true);
formatName(formatter);
formatter.emphasis(false);
if (!asObject()) {
formatter.appendText("("); //NOI18N
appendParamsStr(formatter);
formatter.appendText(")"); //NOI18N
appendReturnTypes(formatter);
}
return formatter.getText();
}
private void appendParamsStr(HtmlFormatter formatter){
for (Iterator<Map.Entry<String, Set<String>>> it = parametersTypes.entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Set<String>> entry = it.next();
formatter.parameters(true);
formatter.appendText(entry.getKey());
formatter.parameters(false);
Collection<String> types = entry.getValue();
if (!types.isEmpty()) {
formatter.type(true);
formatter.appendText(": "); //NOI18N
for (Iterator<String> itTypes = types.iterator(); itTypes.hasNext();) {
formatter.appendText(itTypes.next());
if (itTypes.hasNext()) {
formatter.appendText("|"); //NOI18N
}
}
formatter.type(false);
}
if (it.hasNext()) {
formatter.appendText(", "); //NOI18N
}
}
}
private void appendReturnTypes(HtmlFormatter formatter) {
if (!returnTypes.isEmpty()) {
formatter.appendText(": "); //NOI18N
formatter.type(true);
for (Iterator<String> it = returnTypes.iterator(); it.hasNext();) {
formatter.appendText(it.next());
if (it.hasNext()) {
formatter.appendText("|"); //NOI18N
}
}
formatter.type(false);
}
}
@Override
public String getCustomInsertTemplate() {
StringBuilder template = new StringBuilder();
template.append(getName());
if (!asObject()) {
if (parametersTypes.isEmpty()) {
template.append("()${cursor}"); //NOI18N
} else {
template.append("(${cursor})"); //NOI18N
}
} else {
template.append("${cursor}"); //NOI18N
}
return template.toString();
}
@Override
public ImageIcon getIcon() {
if (getModifiers().contains(Modifier.PROTECTED)) {
if(priviligedIcon == null) {
priviligedIcon = new ImageIcon(ImageUtilities.loadImage("org/netbeans/modules/javascript2/editor/resources/methodPriviliged.png")); //NOI18N
}
return priviligedIcon;
}
return super.getIcon(); //To change body of generated methods, choose Tools | Templates.
}
private boolean isAfterNewKeyword() {
boolean isAfterNew = false;
Snapshot snapshot = request.result.getSnapshot();
int offset = request.anchor;
TokenSequence<? extends JsTokenId> ts = LexUtilities.getJsTokenSequence(snapshot, snapshot.getOriginalOffset(offset));
if (ts != null) {
ts.move(offset);
if (ts.moveNext()) {
Token<? extends JsTokenId> token = LexUtilities.findPrevious(ts, Arrays.asList(JsTokenId.IDENTIFIER, JsTokenId.OPERATOR_DOT, JsTokenId.BLOCK_COMMENT, JsTokenId.WHITESPACE, JsTokenId.LINE_COMMENT, JsTokenId.EOL));
if (token.id() == JsTokenId.KEYWORD_NEW) {
isAfterNew = true;
}
}
}
return isAfterNew;
}
/**
*
* @return true if the element should be treated as an object or function in the context
*/
private boolean asObject() {
boolean result = false;
char firstChar = getName().charAt(0);
JsElement.Kind jsKind = null;
if (element instanceof JsElement) {
jsKind = ((JsElement)element).getJSKind();
}
if ((jsKind != null && jsKind == JsElement.Kind.CONSTRUCTOR) || Character.isUpperCase(firstChar)) {
boolean isAfterNew = isAfterNewKeyword();
if (!isAfterNew) {
// check return types, whether it can be really constructor
for (String type : returnTypes) {
if (type.endsWith(element.getName())) {
return true;
}
}
if (returnTypes.isEmpty()) {
result = true;
} else if (returnTypes.size() == 1) {
String type = returnTypes.iterator().next();
firstChar = type.charAt(0);
if (Character.isUpperCase(firstChar) && !(Type.NUMBER.equals(type) || Type.BOOLEAN.equals(type)
|| Type.STRING.equals(type) || Type.ARRAY.equals(type))) {
result = true;
}
}
}
}
return result;
}
}
public static class JsGeneratorCompletionItem extends JsFunctionCompletionItem {
public JsGeneratorCompletionItem(ElementHandle element, CompletionRequest request, Set<String> resolvedReturnTypes, Map<String, Set<String>> parametersTypes) {
super(element, request, resolvedReturnTypes, parametersTypes);
}
@Override
public ImageIcon getIcon() {
if (getModifiers().contains(Modifier.PUBLIC)) {
if (publicGenerator == null) {
publicGenerator = new ImageIcon(ImageUtilities.loadImage("org/netbeans/modules/javascript2/editor/resources/generatorPublic.png")); //NOI18N
}
return publicGenerator;
} else if (getModifiers().contains(Modifier.PRIVATE)) {
if (privateGenerator == null) {
privateGenerator = new ImageIcon(ImageUtilities.loadImage("org/netbeans/modules/javascript2/editor/resources/generatorPrivate.png")); //NOI18N
}
return privateGenerator;
} else if (getModifiers().contains(Modifier.PROTECTED)) {
if (priviligedGenerator == null) {
priviligedGenerator = new ImageIcon(ImageUtilities.loadImage("org/netbeans/modules/javascript2/editor/resources/generatorPriviliged.png")); //NOI18N
}
return priviligedGenerator;
}
return super.getIcon();
}
}
public static class JsCallbackCompletionItem extends JsCompletionItem {
private static ImageIcon callbackIcon = null;
private IndexedElement.FunctionIndexedElement function;
public JsCallbackCompletionItem(IndexedElement.FunctionIndexedElement element, CompletionRequest request) {
super(element, request);
function = element;
}
@Override
public ImageIcon getIcon() {
if (callbackIcon == null) {
callbackIcon = new ImageIcon(ImageUtilities.loadImage("org/netbeans/modules/javascript2/editor/resources/methodCallback.png")); //NOI18N
}
return callbackIcon;
}
@Override
public String getLhsHtml(HtmlFormatter formatter) {
formatter.setMaxLength(OptionsUtils.forLanguage(JsTokenId.javascriptLanguage()).getCodeCompletionItemSignatureWidth());
formatter.name(ElementKind.KEYWORD, true);
formatter.appendText("function"); //NOI18N
formatter.name(ElementKind.KEYWORD, false);
formatter.appendText(" ("); //NOI18N
appendParamsStr(formatter);
formatter.appendText(")"); //NOI18N
return formatter.getText();
}
@Override
public int getSortPrioOverride() {
return 90; // display as first items?
}
private void appendParamsStr(HtmlFormatter formatter){
for (Iterator<Map.Entry<String, Collection<String>>> it = function.getParameters().entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Collection<String>> entry = it.next();
formatter.parameters(true);
formatter.appendText(entry.getKey());
formatter.parameters(false);
Collection<String> types = entry.getValue();
if (!types.isEmpty()) {
formatter.type(true);
formatter.appendText(": "); //NOI18N
for (Iterator<String> itTypes = types.iterator(); itTypes.hasNext();) {
formatter.appendText(itTypes.next());
if (itTypes.hasNext()) {
formatter.appendText("|"); //NOI18N
}
}
formatter.type(false);
}
if (it.hasNext()) {
formatter.appendText(", "); //NOI18N
}
}
}
@Override
public String getCustomInsertTemplate() {
StringBuilder template = new StringBuilder();
template.append(" \n /** "); //NOI18N
for (Iterator<Map.Entry<String, Collection<String>>> it = function.getParameters().entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Collection<String>> entry = it.next();
Collection<String> types = entry.getValue();
template.append("\n * @param {");//NOI18N
if (!types.isEmpty()) {
for (Iterator<String> itTypes = types.iterator(); itTypes.hasNext();) {
template.append(itTypes.next());
if (itTypes.hasNext()) {
template.append("|"); //NOI18N
}
}
} else {
template.append("Object");//NOI18N
}
template.append("} ");//NOI18N
template.append(entry.getKey());
}
template.append("\n */");//NOI18N
template.append("\nfunction (");//NOI18N
for (Iterator<Map.Entry<String, Collection<String>>> it = function.getParameters().entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Collection<String>> entry = it.next();
template.append(entry.getKey());
if (it.hasNext()) {
template.append(", "); //NOI18N
}
}
template.append(") {\n ${cursor}\n}");//NOI18N
return template.toString();
}
@Override
public String getName() {
return "function";
}
}
static class KeywordItem extends JsCompletionItem {
private static ImageIcon keywordIcon = null;
private final String keyword;
private final JsKeywords.CompletionDescription description;
public KeywordItem(String keyword, JsKeywords.CompletionDescription description, CompletionRequest request) {
super(null, request);
this.keyword = keyword;
this.description = description;
}
@Override
public String getName() {
return keyword;
}
@Override
public String getLhsHtml(HtmlFormatter formatter) {
formatter.name(getKind(), true);
formatter.appendText(getName());
formatter.name(getKind(), false);
return formatter.getText();
}
@Override
public ElementKind getKind() {
return ElementKind.KEYWORD;
}
@Override
public String getRhsHtml(HtmlFormatter formatter) {
JsVersion since = description.getVersion();
if (since != null) {
formatter.appendText(since.getDisplayName());
return formatter.getText();
}
return null;
}
@Override
public ImageIcon getIcon() {
if (keywordIcon == null) {
keywordIcon = new ImageIcon(ImageUtilities.loadImage("org/netbeans/modules/javascript2/editor/resources/javascript.png")); //NOI18N
}
return keywordIcon;
}
@Override
public String getInsertPrefix() {
return getName();
}
@Override
public String getCustomInsertTemplate() {
StringBuilder builder = new StringBuilder();
JsKeywords.CompletionType type = description.getType();
if (type == null) {
return getName();
}
switch(type) {
case SIMPLE:
builder.append(getName());
break;
case ENDS_WITH_SPACE:
builder.append(getName());
builder.append(" ${cursor}"); //NOI18N
break;
case CURSOR_INSIDE_BRACKETS:
builder.append(getName());
builder.append("(${cursor})"); //NOI18N
break;
case ENDS_WITH_CURLY_BRACKETS:
builder.append(getName());
builder.append(" {${cursor}}"); //NOI18N
break;
case ENDS_WITH_SEMICOLON:
builder.append(getName());
CharSequence text = request.info.getSnapshot().getText();
int index = request.anchor + request.prefix.length();
if (index == text.length() || ';' != text.charAt(index)) { //NOI18N
builder.append(";"); //NOI18N
}
break;
case ENDS_WITH_COLON:
builder.append(getName());
builder.append(" ${cursor}:"); //NOI18N
break;
case ENDS_WITH_DOT:
builder.append(getName());
builder.append(".${cursor}"); //NOI18N
break;
default:
assert false : type.toString();
break;
}
return builder.toString();
}
@Override
public int getSortPrioOverride() {
return 130;
}
}
static class CssCompletionItem extends JsCompletionItem {
private static ImageIcon cssIcon = null;
private final String name;
public CssCompletionItem(String name, CompletionRequest request) {
super(null, request);
this.name = name;
}
@Override
public ImageIcon getIcon() {
if (cssIcon == null) {
cssIcon = new ImageIcon(ImageUtilities.loadImage("org/netbeans/modules/javascript2/jquery/resources/style_sheet_16.png")); //NOI18N
}
return cssIcon;
}
@Override
public String getName() {
return name;
}
@Override
public String getInsertPrefix() {
return getName();
}
@Override
public String getLhsHtml(HtmlFormatter formatter) {
formatter.reset();
formatter.appendText(getName());
return formatter.getText();
}
@Override
public ElementKind getKind() {
return ElementKind.RULE;
}
@Override
public String getRhsHtml(HtmlFormatter formatter) {
return null;
}
}
public static class JsHtmlAttributeItem extends JsCompletionItem {
private final HtmlTagAttribute attr;
public JsHtmlAttributeItem(HtmlTagAttribute attr, CompletionRequest request) {
super(new HtmlAttrElement(attr), request);
this.attr = attr;
}
@Override
public String getName() {
return attr.getName();
}
@Override
public String getInsertPrefix() {
return getName();
}
@Override
public String getLhsHtml(HtmlFormatter formatter) {
formatter.reset();
formatter.appendText(getName());
return formatter.getText();
}
@Override
public ElementKind getKind() {
return ElementKind.ATTRIBUTE;
}
@Messages("JsCompletionItem.lbl.html.attribute=HTML Attribute")
@Override
public String getRhsHtml(HtmlFormatter formatter) {
formatter.reset();
formatter.appendHtml("<font color=#999999>");
formatter.appendText(Bundle.JsCompletionItem_lbl_html_attribute());
formatter.appendHtml("</font>");
return formatter.getText();
}
private static class HtmlAttrElement extends SimpleDocElement {
private final HtmlTagAttribute attribute;
public HtmlAttrElement(HtmlTagAttribute attribute) {
super(attribute.getName(), ElementKind.ATTRIBUTE);
this.attribute = attribute;
}
@Override
public String getDocumentation() {
String content = attribute.getHelp().getHelpContent();
if (content == null) {
if (attribute.getHelp().getHelpResolver() != null && attribute.getHelp().getHelpURL() != null) {
content = attribute.getHelp().getHelpResolver().getHelpContent(attribute.getHelp().getHelpURL());
}
}
return content;
}
}
}
public static class JsPropertyCompletionItem extends JsCompletionItem {
private final Set<String> resolvedTypes;
JsPropertyCompletionItem(ElementHandle element, CompletionRequest request, Set<String> resolvedTypes) {
super(element, request);
this.resolvedTypes = resolvedTypes != null ? resolvedTypes : Collections.EMPTY_SET;
}
@Override
public String getLhsHtml(HtmlFormatter formatter) {
formatName(formatter);
if (!resolvedTypes.isEmpty()) {
formatter.type(true);
formatter.appendText(": "); //NOI18N
for (Iterator<String> it = resolvedTypes.iterator(); it.hasNext();) {
formatter.appendText(it.next());
if (it.hasNext()) {
formatter.appendText("|"); //NOI18N
}
}
formatter.type(false);
}
return formatter.getText();
}
@Override
public String getCustomInsertTemplate() {
if (request.completionContext == CompletionContext.OBJECT_PROPERTY_NAME) {
return getName() + ": ${cursor}"; // NOI18N
}
return super.getCustomInsertTemplate(); //To change body of generated methods, choose Tools | Templates.
}
}
public static class Factory {
public static void create( Map<String, List<JsElement>> items, CompletionRequest request, List<CompletionProposal> result) {
CancelSupport cancelSupport = request.cancelSupport;
if (cancelSupport.isCancelled()) {
return;
}
// This maps unresolved types to the display name of the resolved type.
// It should save time to not resolve one type more times
HashMap<String, Set<String>> resolvedTypes = new HashMap<String, Set<String>>();
for (Map.Entry<String, List<JsElement>> entry: items.entrySet()) {
// this helps to eleminate items that will look as the same items in the cc
HashMap<String, JsCompletionItem> signatures = new HashMap<String, JsCompletionItem>();
Index jsIndex = null;
if (OptionsUtils.forLanguage(JsTokenId.javascriptLanguage()).autoCompletionTypeResolution()) {
jsIndex = Index.get(request.info.getSnapshot().getSource().getFileObject());
}
for (JsElement element : entry.getValue()) {
if (cancelSupport.isCancelled()) {
return;
}
switch (element.getJSKind()) {
case CONSTRUCTOR:
case FUNCTION:
case METHOD:
case GENERATOR:
Set<String> returnTypes = new HashSet<String>();
HashMap<String, Set<String>> allParameters = new LinkedHashMap<String, Set<String>>();
if (element instanceof JsFunction) {
// count return types
Collection<TypeUsage> resolveTypes = ModelUtils.resolveTypes(((JsFunction) element).getReturnTypes(),
Model.getModel(request.info, false),
jsIndex, false);
returnTypes.addAll(Utils.getDisplayNames(resolveTypes));
// count parameters type
for (JsObject jsObject : ((JsFunction) element).getParameters()) {
Set<String> paramTypes = new HashSet<String>();
for (TypeUsage type : jsObject.getAssignmentForOffset(jsObject.getOffset() + 1)) {
Set<String> resolvedType = resolvedTypes.get(type.getType());
if (resolvedType == null) {
resolvedType = new HashSet(1);
String displayName = ModelUtils.getDisplayName(type);
if (!displayName.isEmpty()) {
resolvedType.add(displayName);
}
resolvedTypes.put(type.getType(), resolvedType);
}
paramTypes.addAll(resolvedType);
}
allParameters.put(jsObject.getName(), paramTypes);
}
} else if (element instanceof IndexedElement.FunctionIndexedElement) {
// count return types
HashSet<TypeUsage> returnTypeUsages = new HashSet<TypeUsage>();
for (String type : ((IndexedElement.FunctionIndexedElement) element).getReturnTypes()) {
returnTypeUsages.add(new TypeUsage(type, -1, false));
}
Collection<TypeUsage> resolveTypes = ModelUtils.resolveTypes(returnTypeUsages,
Model.getModel(request.info, false),
jsIndex, false);
returnTypes.addAll(Utils.getDisplayNames(resolveTypes));
// count parameters type
LinkedHashMap<String, Collection<String>> parameters = ((IndexedElement.FunctionIndexedElement) element).getParameters();
for (Map.Entry<String, Collection<String>> paramEntry : parameters.entrySet()) {
Set<String> paramTypes = new HashSet<String>();
for (String type : paramEntry.getValue()) {
Set<String> resolvedType = resolvedTypes.get(type);
if (resolvedType == null) {
resolvedType = new HashSet(1);
String displayName = ModelUtils.getDisplayName(type);
if (!displayName.isEmpty()) {
resolvedType.add(displayName);
}
resolvedTypes.put(type, resolvedType);
}
paramTypes.addAll(resolvedType);
}
allParameters.put(paramEntry.getKey(), paramTypes);
}
}
// create signature
String signature = createFnSignature(entry.getKey(), allParameters, returnTypes);
if (!signatures.containsKey(signature)) {
JsCompletionItem item = element.getJSKind() != JsElement.Kind.GENERATOR
? new JsFunctionCompletionItem(element, request, returnTypes, allParameters)
: new JsGeneratorCompletionItem(element, request, returnTypes, allParameters);
signatures.put(signature, item);
}
break;
case PARAMETER:
case PROPERTY:
case PROPERTY_GETTER:
case PROPERTY_SETTER:
case FIELD:
case VARIABLE:
Set<String> typesToDisplay = new HashSet<String>();
Collection<? extends TypeUsage> assignment = null;
if (element instanceof JsObject) {
JsObject jsObject = (JsObject) element;
assignment = jsObject.getAssignments();
} else if (element instanceof IndexedElement) {
IndexedElement iElement = (IndexedElement) element;
assignment = iElement.getAssignments();
}
if (assignment != null && !assignment.isEmpty()) {
HashSet<TypeUsage> toResolve = new HashSet<TypeUsage>();
for (TypeUsage type : assignment) {
if (type.isResolved()) {
if (!Type.UNDEFINED.equals(type.getType())) {
typesToDisplay.add(ModelUtils.getDisplayName(type));
}
} else {
Set<String> resolvedType = resolvedTypes.get(type.getType());
if (resolvedType == null) {
toResolve.clear();
toResolve.add(type);
resolvedType = new HashSet(1);
Collection<TypeUsage> resolved = ModelUtils.resolveTypes(toResolve,
Model.getModel(request.result, false),
jsIndex, false);
for (TypeUsage rType : resolved) {
String displayName = ModelUtils.getDisplayName(rType);
if (!displayName.isEmpty()) {
resolvedType.add(displayName);
}
}
resolvedTypes.put(type.getType(), resolvedType);
}
typesToDisplay.addAll(resolvedType);
}
}
}
// signatures
signature = element.getName() + ":" + createTypeSignature(typesToDisplay);
if (!signatures.containsKey(signature)) {
// add the item to the cc only if doesn't exist any similar
JsCompletionItem item = new JsPropertyCompletionItem(element, request, typesToDisplay);
signatures.put(signature, item);
}
break;
default:
signature = element.getName();
if (!signatures.containsKey(signature)) {
JsCompletionItem item = new JsCompletionItem(element, request);
signatures.put(signature, item);
}
}
}
for (JsCompletionItem item: signatures.values()) {
result.add(item);
}
}
}
private static String createFnSignature(String name, HashMap<String, Set<String>> params, Set<String> returnTypes) {
StringBuilder sb = new StringBuilder();
sb.append(name).append('(');
for (Map.Entry<String, Set<String>> entry : params.entrySet()) {
sb.append(entry.getKey()).append(':');
sb.append(createTypeSignature(entry.getValue()));
sb.append(',');
}
sb.append(')');
sb.append(createTypeSignature(returnTypes));
return sb.toString();
}
private static String createTypeSignature(Set<String> types) {
StringBuilder sb = new StringBuilder();
for(String name: types){
sb.append(name).append('|');
}
return sb.toString();
}
}
public abstract static class SimpleDocElement implements ElementHandle {
private final String name;
private final ElementKind kind;
public SimpleDocElement(String name, ElementKind kind) {
this.name = name;
this.kind = kind;
}
@Override
public FileObject getFileObject() {
return null;
}
@Override
public String getMimeType() {
return "";
}
@Override
public String getName() {
return name;
}
@Override
public String getIn() {
return "";
}
@Override
public ElementKind getKind() {
return kind;
}
@Override
public Set<Modifier> getModifiers() {
return Collections.<Modifier>emptySet();
}
@Override
public boolean signatureEquals(ElementHandle handle) {
return false;
}
@Override
public OffsetRange getOffsetRange(ParserResult result) {
return OffsetRange.NONE;
}
abstract public String getDocumentation();
}
}