0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * If you wish your version of this file to be governed by only the CDDL
0025: * or only the GPL Version 2, indicate your decision by adding
0026: * "[Contributor] elects to include this software in this distribution
0027: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0028: * single choice of license, a recipient has the option to distribute
0029: * your version of this file under either the CDDL, the GPL Version 2 or
0030: * to extend the choice of license to its licensees as provided above.
0031: * However, if you add GPL Version 2 code and therefore, elected the GPL
0032: * Version 2 license, then the option applies only if the new code is
0033: * made subject to such option by the copyright holder.
0034: *
0035: * Contributor(s):
0036: *
0037: * Portions Copyrighted 2007 Sun Microsystems, Inc.
0038: */
0039:
0040: package org.netbeans.modules.javascript.editing;
0041:
0042: import java.io.IOException;
0043: import java.util.ArrayList;
0044: import java.util.Collection;
0045: import java.util.Collections;
0046: import java.util.HashSet;
0047: import java.util.Iterator;
0048: import java.util.List;
0049: import java.util.ListIterator;
0050: import java.util.Map;
0051: import java.util.Set;
0052: import javax.swing.ImageIcon;
0053: import javax.swing.text.BadLocationException;
0054: import javax.swing.text.Document;
0055: import javax.swing.text.JTextComponent;
0056: import org.mozilla.javascript.Node;
0057: import org.netbeans.editor.ext.html.parser.SyntaxElement;
0058: import org.netbeans.modules.gsf.api.CompilationInfo;
0059: import org.netbeans.modules.gsf.api.Completable;
0060: import org.netbeans.modules.gsf.api.CompletionProposal;
0061: import org.netbeans.modules.gsf.api.ElementHandle;
0062: import org.netbeans.modules.gsf.api.ElementKind;
0063: import org.netbeans.modules.gsf.api.HtmlFormatter;
0064: import org.netbeans.modules.gsf.api.Modifier;
0065: import org.netbeans.modules.gsf.api.NameKind;
0066: import org.netbeans.modules.gsf.api.ParameterInfo;
0067: import org.netbeans.api.lexer.Token;
0068: import org.netbeans.api.lexer.TokenHierarchy;
0069: import org.netbeans.api.lexer.TokenId;
0070: import org.netbeans.api.lexer.TokenSequence;
0071: import org.netbeans.editor.BaseDocument;
0072: import org.netbeans.editor.Utilities;
0073: import org.netbeans.modules.gsf.api.OffsetRange;
0074: import org.netbeans.modules.gsf.api.ParserResult;
0075: import org.netbeans.modules.html.editor.gsf.HtmlParserResult;
0076: import org.netbeans.modules.javascript.editing.JsParser.Sanitize;
0077: import org.netbeans.modules.javascript.editing.lexer.Call;
0078: import org.netbeans.modules.javascript.editing.lexer.JsTokenId;
0079: import org.netbeans.modules.javascript.editing.lexer.LexUtilities;
0080: import org.openide.filesystems.FileObject;
0081: import org.openide.util.Exceptions;
0082: import org.openide.util.NbBundle;
0083:
0084: /**
0085: * Code completion handler for JavaScript
0086: *
0087: * @todo Do completion on element id's inside $() calls (prototype.js) and $$() calls for CSS rules.
0088: * See http://www.sitepoint.com/article/painless-javascript-prototype
0089: * @todo Track logical classes and inheritance ("extend")
0090: * @todo Track global variables (these are vars which aren't local). Somehow cooperate work between
0091: * semantic highlighter and structure analyzer. I need to only store a single instance of each
0092: * global var in the index. The variable visitor should probably be part of the structure analyzer,
0093: * since global variables also need to be tracked there. Another possibility is having the
0094: * parser track variables - but that's trickier. Perhaps a second pass over the parse tree
0095: * (where I set parent pointers) is where I can do this? I can even change node types to be
0096: * more obvious...
0097: * @todo I should NOT include in queries functions that are known to be methods if you're not doing
0098: * "unnown type" completion!
0099: * @todo Today's feature work:
0100: * - this.-completion should do something useful
0101: * - I need to model prototype inheritance, and then use it in code completion queries
0102: * - Skip no-doc'ed methods
0103: * - Improve type analysis:
0104: * - known types (element, document, ...)
0105: * - variable-name guessing (el, doc, etc ...)
0106: * - return value tracking
0107: * - Improve indexing:
0108: * - store @-private, etc.
0109: * - more efficient browser-compat flags
0110: * - Fix case-sensitivity on index queries such that open type and other forms of completion
0111: * work better!
0112: * @todo Distinguish properties and globals and functions? Perhaps with attributes in the flags!
0113: * @todo Display more information in parameter tooltips, such as type hints (perhaps do smart
0114: * filtering Java-style?), and explanations for each parameter
0115: * @todo Need preindexing support for unit tests - and separate files
0116: *
0117: * @author Tor Norbye
0118: */
0119: public class JsCodeCompletion implements Completable {
0120: private static ImageIcon keywordIcon;
0121: private boolean caseSensitive;
0122: private static final String[] REGEXP_WORDS = new String[] {
0123: // Dbl-space lines to keep formatter from collapsing pairs into a block
0124:
0125: // Literals
0126:
0127: "\\0", "The NUL character (\\u0000)",
0128:
0129: "\\t", "Tab (\\u0009)",
0130:
0131: "\\n", "Newline (\\u000A)",
0132:
0133: "\\v", "Vertical tab (\\u000B)",
0134:
0135: "\\f", "Form feed (\\u000C)",
0136:
0137: "\\r", "Carriage return (\\u000D)",
0138:
0139: "\\x",
0140: "\\x<i>nn</i>: The latin character in hex <i>nn</i>",
0141:
0142: "\\u",
0143: "\\u<i>xxxx</i>: The Unicode character in hex <i>xxxx</i>",
0144:
0145: "\\c",
0146: "\\c<i>X</i>: The control character ^<i>X</i>",
0147:
0148: // Character classes
0149: "[]", "Any one character between the brackets",
0150:
0151: "[^]", "Any one character not between the brackets",
0152:
0153: "\\w", "Any ASCII word character; same as [0-9A-Za-z_]",
0154:
0155: "\\W", "Not a word character; same as [^0-9A-Za-z_]",
0156:
0157: "\\s", "Unicode space character",
0158:
0159: "\\S",
0160: "Non-space character",
0161:
0162: "\\d",
0163: "Digit character; same as [0-9]",
0164:
0165: "\\D",
0166: "Non-digit character; same as [^0-9]",
0167:
0168: "[\\b]",
0169: "Literal backspace",
0170:
0171: // Match positions
0172: "^",
0173: "Start of line",
0174:
0175: "$",
0176: "End of line",
0177:
0178: "\\b",
0179: "Word boundary (if not in a range specification)",
0180:
0181: "\\B",
0182: "Non-word boundary",
0183:
0184: // According to JavaScript The Definitive Guide, the following are not supported
0185: // in JavaScript:
0186: // \\a, \\e, \\l, \\u, \\L, \\U, \\E, \\Q, \\A, \\Z, \\z, and \\G
0187: //
0188: //"\\A", "Beginning of string",
0189: //"\\z", "End of string",
0190: //"\\Z", "End of string (except \\n)",
0191:
0192: "*", "Zero or more repetitions of the preceding",
0193:
0194: "+", "One or more repetitions of the preceding",
0195:
0196: "{m,n}",
0197: "At least m and at most n repetitions of the preceding",
0198:
0199: "?",
0200: "At most one repetition of the preceding; same as {0,1}",
0201:
0202: "|", "Either preceding or next expression may match",
0203:
0204: "()", "Grouping",
0205:
0206: //"[:alnum:]", "Alphanumeric character class",
0207: //"[:alpha:]", "Uppercase or lowercase letter",
0208: //"[:blank:]", "Blank and tab",
0209: //"[:cntrl:]", "Control characters (at least 0x00-0x1f,0x7f)",
0210: //"[:digit:]", "Digit",
0211: //"[:graph:]", "Printable character excluding space",
0212: //"[:lower:]", "Lowecase letter",
0213: //"[:print:]", "Any printable letter (including space)",
0214: //"[:punct:]", "Printable character excluding space and alphanumeric",
0215: //"[:space:]", "Whitespace (same as \\s)",
0216: //"[:upper:]", "Uppercase letter",
0217: //"[:xdigit:]", "Hex digit (0-9, a-f, A-F)",
0218: };
0219:
0220: // Strings section 7.8
0221: private static final String[] STRING_ESCAPES = new String[] {
0222:
0223: "\\0", "The NUL character (\\u0000)",
0224:
0225: "\\b", "Backspace (0x08)",
0226:
0227: "\\t", "Tab (\\u0009)",
0228:
0229: "\\n", "Newline (\\u000A)",
0230:
0231: "\\v", "Vertical tab (\\u000B)",
0232:
0233: "\\f", "Form feed (\\u000C)",
0234:
0235: "\\r", "Carriage return (\\u000D)",
0236:
0237: "\\\"", "Double Quote (\\u0022)",
0238:
0239: "\\'", "Single Quote (\\u0027)",
0240:
0241: "\\\\", "Backslash (\\u005C)",
0242:
0243: "\\x", "\\x<i>nn</i>: The latin character in hex <i>nn</i>",
0244:
0245: "\\u", "\\u<i>xxxx</i>: The Unicode character in hex <i>xxxx</i>",
0246:
0247: "\\", "\\<i>ooo</i>: The latin character in octal <i>ooo</i>",
0248:
0249: // PENDING: Is this supported?
0250: "\\c", "\\c<i>X</i>: The control character ^<i>X</i>",
0251:
0252: };
0253:
0254: public JsCodeCompletion() {
0255:
0256: }
0257:
0258: public List<CompletionProposal> complete(CompilationInfo info,
0259: int lexOffset, String prefix, NameKind kind,
0260: QueryType queryType, boolean caseSensitive,
0261: HtmlFormatter formatter) {
0262: // Temporary: case insensitive matches don't work very well for JavaScript
0263: if (kind == NameKind.CASE_INSENSITIVE_PREFIX) {
0264: kind = NameKind.PREFIX;
0265: }
0266:
0267: if (prefix == null) {
0268: prefix = "";
0269: }
0270: this .caseSensitive = caseSensitive;
0271:
0272: final Document document;
0273: try {
0274: document = info.getDocument();
0275: } catch (Exception e) {
0276: Exceptions.printStackTrace(e);
0277: return null;
0278: }
0279: final BaseDocument doc = (BaseDocument) document;
0280:
0281: List<CompletionProposal> proposals = new ArrayList<CompletionProposal>();
0282:
0283: JsParseResult parseResult = AstUtilities.getParseResult(info);
0284: doc.readLock(); // Read-lock due to Token hierarchy use
0285: try {
0286: Node root = parseResult.getRootNode();
0287: final int astOffset = AstUtilities.getAstOffset(info,
0288: lexOffset);
0289: if (astOffset == -1) {
0290: return null;
0291: }
0292: final TokenHierarchy<Document> th = TokenHierarchy
0293: .get(document);
0294: final FileObject fileObject = info.getFileObject();
0295:
0296: // Carry completion context around since this logic is split across lots of methods
0297: // and I don't want to pass dozens of parameters from method to method; just pass
0298: // a request context with supporting info needed by the various completion helpers i
0299: CompletionRequest request = new CompletionRequest();
0300: request.result = parseResult;
0301: request.formatter = formatter;
0302: request.lexOffset = lexOffset;
0303: request.astOffset = astOffset;
0304: request.index = JsIndex.get(info
0305: .getIndex(JsMimeResolver.JAVASCRIPT_MIME_TYPE));
0306: request.doc = doc;
0307: request.info = info;
0308: request.prefix = prefix;
0309: request.th = th;
0310: request.kind = kind;
0311: request.queryType = queryType;
0312: request.fileObject = fileObject;
0313: request.anchor = lexOffset - prefix.length();
0314: request.call = Call.getCallType(doc, th, lexOffset);
0315:
0316: Token<? extends TokenId> token = LexUtilities.getToken(doc,
0317: lexOffset);
0318: if (token == null) {
0319: return proposals;
0320: }
0321:
0322: TokenId id = token.id();
0323: if (id == JsTokenId.LINE_COMMENT) {
0324: // TODO - Complete symbols in comments?
0325: return proposals;
0326: } else if (id == JsTokenId.STRING_LITERAL
0327: || id == JsTokenId.STRING_END) {
0328: completeStrings(proposals, request);
0329: return proposals;
0330: } else if (id == JsTokenId.REGEXP_LITERAL
0331: || id == JsTokenId.REGEXP_END) {
0332: completeRegexps(proposals, request);
0333: return proposals;
0334: }
0335:
0336: if (root != null) {
0337: final AstPath path = new AstPath(root, astOffset);
0338: request.path = path;
0339: request.fqn = AstUtilities.getFqn(path);
0340:
0341: final Node closest = path.leaf();
0342: request.root = root;
0343: request.node = closest;
0344:
0345: addLocals(proposals, request);
0346: }
0347:
0348: completeKeywords(proposals, request);
0349:
0350: if (root == null) {
0351: return proposals;
0352: }
0353: // Try to complete "new" RHS
0354: if (completeNew(proposals, request)) {
0355: return proposals;
0356: }
0357:
0358: if (completeObjectMethod(proposals, request)) {
0359: return proposals;
0360: }
0361:
0362: // Try to complete methods
0363: if (completeFunctions(proposals, request)) {
0364: return proposals;
0365: }
0366: } finally {
0367: doc.readUnlock();
0368: }
0369:
0370: return proposals;
0371: }
0372:
0373: private void addLocals(List<CompletionProposal> proposals,
0374: CompletionRequest request) {
0375: Node node = request.node;
0376: String prefix = request.prefix;
0377: NameKind kind = request.kind;
0378: JsParseResult result = request.result;
0379:
0380: // TODO - find the scope!!!
0381: VariableVisitor v = result.getVariableVisitor();
0382:
0383: Map<String, List<Node>> localVars = v.getLocalVars(node);
0384: for (String name : localVars.keySet()) {
0385: if (((kind == NameKind.EXACT_NAME) && prefix.equals(name))
0386: || ((kind != NameKind.EXACT_NAME) && startsWith(
0387: name, prefix))) {
0388: List<Node> nodeList = localVars.get(name);
0389: if (nodeList != null && nodeList.size() > 0) {
0390: AstElement element = AstElement.getElement(
0391: request.info, nodeList.get(0));
0392: proposals.add(new PlainItem(element, request));
0393: }
0394: }
0395: }
0396:
0397: // Add in "arguments" local variable which is available to all functions
0398: String ARGUMENTS = "arguments"; // NOI18N
0399: if (startsWith(ARGUMENTS, prefix)) {
0400: // Make sure we're in a function before adding the arguments property
0401: for (Node n = node; n != null; n = n.getParentNode()) {
0402: if (n.getType() == org.mozilla.javascript.Token.FUNCTION) {
0403: KeywordElement element = new KeywordElement(
0404: ARGUMENTS, ElementKind.VARIABLE);
0405: proposals.add(new PlainItem(element, request));
0406: break;
0407: }
0408: }
0409: }
0410: }
0411:
0412: private void completeKeywords(List<CompletionProposal> proposals,
0413: CompletionRequest request) {
0414: // No keywords possible in the RHS of a call (except for "this"?)
0415: if (request.call.getLhs() != null) {
0416: return;
0417: }
0418:
0419: String prefix = request.prefix;
0420:
0421: // // Keywords
0422: // if (prefix.equals("$")) {
0423: // // Show dollar variable matches (global vars from the user's
0424: // // code will also be shown
0425: // for (int i = 0, n = Js_DOLLAR_VARIABLES.length; i < n; i += 2) {
0426: // String word = Js_DOLLAR_VARIABLES[i];
0427: // String desc = Js_DOLLAR_VARIABLES[i + 1];
0428: //
0429: // KeywordItem item = new KeywordItem(word, desc, anchor, request);
0430: //
0431: // if (isSymbol) {
0432: // item.setSymbol(true);
0433: // }
0434: //
0435: // proposals.add(item);
0436: // }
0437: // }
0438: //
0439: // for (String keyword : Js_BUILTIN_VARS) {
0440: // if (startsWith(keyword, prefix)) {
0441: // KeywordItem item = new KeywordItem(keyword, null, anchor, request);
0442: //
0443: // if (isSymbol) {
0444: // item.setSymbol(true);
0445: // }
0446: //
0447: // proposals.add(item);
0448: // }
0449: // }
0450:
0451: for (String keyword : JsUtils.JAVASCRIPT_KEYWORDS) {
0452: if (startsWith(keyword, prefix)) {
0453: KeywordItem item = new KeywordItem(keyword, null,
0454: request);
0455:
0456: proposals.add(item);
0457: }
0458: }
0459:
0460: for (String keyword : JsUtils.JAVASCRIPT_RESERVED_WORDS) {
0461: if (startsWith(keyword, prefix)) {
0462: KeywordItem item = new KeywordItem(keyword, null,
0463: request);
0464:
0465: proposals.add(item);
0466: }
0467: }
0468: }
0469:
0470: private boolean startsWith(String theString, String prefix) {
0471: if (prefix.length() == 0) {
0472: return true;
0473: }
0474:
0475: return caseSensitive ? theString.startsWith(prefix) : theString
0476: .toLowerCase().startsWith(prefix.toLowerCase());
0477: }
0478:
0479: private boolean completeRegexps(List<CompletionProposal> proposals,
0480: CompletionRequest request) {
0481: String prefix = request.prefix;
0482:
0483: // Regular expression matching. {
0484: for (int i = 0, n = REGEXP_WORDS.length; i < n; i += 2) {
0485: String word = REGEXP_WORDS[i];
0486: String desc = REGEXP_WORDS[i + 1];
0487:
0488: if (startsWith(word, prefix)) {
0489: KeywordItem item = new KeywordItem(word, desc, request);
0490: proposals.add(item);
0491: }
0492: }
0493:
0494: return true;
0495: }
0496:
0497: private boolean completeStrings(List<CompletionProposal> proposals,
0498: CompletionRequest request) {
0499: String prefix = request.prefix;
0500:
0501: // See if we're in prototype js functions, $() and $F(), and if so,
0502: // offer to complete the function ids
0503: TokenSequence<? extends JsTokenId> ts = LexUtilities
0504: .getPositionedSequence(request.doc, request.lexOffset);
0505: assert ts != null; // or we wouldn't have been called in the first place
0506: //Token<? extends JsTokenId> stringToken = ts.token();
0507: int stringOffset = ts.offset();
0508:
0509: tokenLoop: while (ts.movePrevious()) {
0510: Token<? extends JsTokenId> token = ts.token();
0511: TokenId id = token.id();
0512: if (id == JsTokenId.IDENTIFIER) {
0513: String text = token.text().toString();
0514: if ("$".equals(text) || "$F".equals(text)) { // NOI18N
0515: String HTML_MIME_TYPE = "text/html"; // NOI18N
0516: ParserResult result = request.info
0517: .getEmbeddedResult(HTML_MIME_TYPE, 0);
0518: if (result != null) {
0519: HtmlParserResult htmlResult = (HtmlParserResult) result;
0520: Set<SyntaxElement.TagAttribute> elementIds = htmlResult
0521: .elementsIds();
0522:
0523: if (elementIds.size() > 0) {
0524: // Compute a custom prefix
0525: int lexOffset = request.lexOffset;
0526: if (lexOffset > stringOffset) {
0527: try {
0528: prefix = request.doc.getText(
0529: stringOffset, lexOffset
0530: - stringOffset);
0531: } catch (BadLocationException ex) {
0532: Exceptions.printStackTrace(ex);
0533: }
0534: } else {
0535: prefix = "";
0536: }
0537:
0538: String filename = request.fileObject
0539: .getNameExt();
0540:
0541: for (SyntaxElement.TagAttribute tag : elementIds) {
0542: String elementId = tag.getValue();
0543: // Strip "'s surrounding value, if any
0544: if (elementId.length() > 2
0545: && elementId.startsWith("\"") && // NOI18N
0546: elementId.endsWith("\"")) { // NOI18N
0547: elementId = elementId.substring(1,
0548: elementId.length() - 1);
0549: }
0550:
0551: if (startsWith(elementId, prefix)) {
0552: TagItem item = new TagItem(
0553: elementId, filename,
0554: request);
0555: proposals.add(item);
0556: }
0557: }
0558: }
0559: }
0560: }
0561:
0562: return true;
0563: } else if (id == JsTokenId.STRING_BEGIN) {
0564: stringOffset = ts.offset() + token.length();
0565: } else if (!(id == JsTokenId.WHITESPACE
0566: || id == JsTokenId.STRING_LITERAL || id == JsTokenId.LPAREN)) {
0567: break tokenLoop;
0568: }
0569: }
0570:
0571: for (int i = 0, n = STRING_ESCAPES.length; i < n; i += 2) {
0572: String word = STRING_ESCAPES[i];
0573: String desc = STRING_ESCAPES[i + 1];
0574:
0575: if (startsWith(word, prefix)) {
0576: KeywordItem item = new KeywordItem(word, desc, request);
0577: proposals.add(item);
0578: }
0579: }
0580:
0581: return true;
0582: }
0583:
0584: /**
0585: * Compute an appropriate prefix to use for code completion.
0586: * In Strings, we want to return the -whole- string if you're in a
0587: * require-statement string, otherwise we want to return simply "" or the previous "\"
0588: * for quoted strings, and ditto for regular expressions.
0589: * For non-string contexts, just return null to let the default identifier-computation
0590: * kick in.
0591: */
0592: @SuppressWarnings("unchecked")
0593: public String getPrefix(CompilationInfo info, int lexOffset,
0594: boolean upToOffset) {
0595: try {
0596: BaseDocument doc = (BaseDocument) info.getDocument();
0597:
0598: TokenHierarchy<Document> th = TokenHierarchy
0599: .get((Document) doc);
0600: doc.readLock(); // Read-lock due to token hierarchy use
0601: try {
0602: // int requireStart = LexUtilities.getRequireStringOffset(lexOffset, th);
0603: //
0604: // if (requireStart != -1) {
0605: // // XXX todo - do upToOffset
0606: // return doc.getText(requireStart, lexOffset - requireStart);
0607: // }
0608:
0609: TokenSequence<? extends JsTokenId> ts = LexUtilities
0610: .getJsTokenSequence(th, lexOffset);
0611:
0612: if (ts == null) {
0613: return null;
0614: }
0615:
0616: ts.move(lexOffset);
0617:
0618: if (!ts.moveNext() && !ts.movePrevious()) {
0619: return null;
0620: }
0621:
0622: if (ts.offset() == lexOffset) {
0623: // We're looking at the offset to the RIGHT of the caret
0624: // and here I care about what's on the left
0625: ts.movePrevious();
0626: }
0627:
0628: Token<? extends JsTokenId> token = ts.token();
0629:
0630: if (token != null) {
0631: TokenId id = token.id();
0632:
0633: if (id == JsTokenId.STRING_BEGIN
0634: || id == JsTokenId.STRING_END
0635: || id == JsTokenId.STRING_LITERAL
0636: || id == JsTokenId.REGEXP_LITERAL
0637: || id == JsTokenId.REGEXP_BEGIN
0638: || id == JsTokenId.REGEXP_END) {
0639: if (lexOffset > 0) {
0640: char prevChar = doc.getText(lexOffset - 1,
0641: 1).charAt(0);
0642: if (prevChar == '\\') {
0643: return "\\";
0644: }
0645: return "";
0646: }
0647: }
0648: //
0649: // // We're within a String that has embedded Js. Drop into the
0650: // // embedded language and see if we're within a literal string there.
0651: // if (id == JsTokenId.EMBEDDED_RUBY) {
0652: // ts = (TokenSequence)ts.embedded();
0653: // assert ts != null;
0654: // ts.move(lexOffset);
0655: //
0656: // if (!ts.moveNext() && !ts.movePrevious()) {
0657: // return null;
0658: // }
0659: //
0660: // token = ts.token();
0661: // id = token.id();
0662: // }
0663: //
0664: // String tokenText = token.text().toString();
0665: //
0666: // if ((id == JsTokenId.STRING_BEGIN) || (id == JsTokenId.QUOTED_STRING_BEGIN) ||
0667: // ((id == JsTokenId.ERROR) && tokenText.equals("%"))) {
0668: // int currOffset = ts.offset();
0669: //
0670: // // Percent completion
0671: // if ((currOffset == (lexOffset - 1)) && (tokenText.length() > 0) &&
0672: // (tokenText.charAt(0) == '%')) {
0673: // return "%";
0674: // }
0675: // }
0676: // }
0677: //
0678: // int doubleQuotedOffset = LexUtilities.getDoubleQuotedStringOffset(lexOffset, th);
0679: //
0680: // if (doubleQuotedOffset != -1) {
0681: // // Tokenize the string and offer the current token portion as the text
0682: // if (doubleQuotedOffset == lexOffset) {
0683: // return "";
0684: // } else if (doubleQuotedOffset < lexOffset) {
0685: // String text = doc.getText(doubleQuotedOffset, lexOffset - doubleQuotedOffset);
0686: // TokenHierarchy hi =
0687: // TokenHierarchy.create(text, JsStringTokenId.languageDouble());
0688: //
0689: // TokenSequence seq = hi.tokenSequence();
0690: //
0691: // seq.move(lexOffset - doubleQuotedOffset);
0692: //
0693: // if (!seq.moveNext() && !seq.movePrevious()) {
0694: // return "";
0695: // }
0696: //
0697: // TokenId id = seq.token().id();
0698: // String s = seq.token().text().toString();
0699: //
0700: // if ((id == JsStringTokenId.STRING_ESCAPE) ||
0701: // (id == JsStringTokenId.STRING_INVALID)) {
0702: // return s;
0703: // } else if (s.startsWith("\\")) {
0704: // return s;
0705: // } else {
0706: // return "";
0707: // }
0708: // } else {
0709: // // The String offset is greater than the caret position.
0710: // // This means that we're inside the string-begin section,
0711: // // for example here: %q|(
0712: // // In this case, report no prefix
0713: // return "";
0714: // }
0715: // }
0716: //
0717: // int singleQuotedOffset = LexUtilities.getSingleQuotedStringOffset(lexOffset, th);
0718: //
0719: // if (singleQuotedOffset != -1) {
0720: // if (singleQuotedOffset == lexOffset) {
0721: // return "";
0722: // } else if (singleQuotedOffset < lexOffset) {
0723: // String text = doc.getText(singleQuotedOffset, lexOffset - singleQuotedOffset);
0724: // TokenHierarchy hi =
0725: // TokenHierarchy.create(text, JsStringTokenId.languageSingle());
0726: //
0727: // TokenSequence seq = hi.tokenSequence();
0728: //
0729: // seq.move(lexOffset - singleQuotedOffset);
0730: //
0731: // if (!seq.moveNext() && !seq.movePrevious()) {
0732: // return "";
0733: // }
0734: //
0735: // TokenId id = seq.token().id();
0736: // String s = seq.token().text().toString();
0737: //
0738: // if ((id == JsStringTokenId.STRING_ESCAPE) ||
0739: // (id == JsStringTokenId.STRING_INVALID)) {
0740: // return s;
0741: // } else if (s.startsWith("\\")) {
0742: // return s;
0743: // } else {
0744: // return "";
0745: // }
0746: // } else {
0747: // // The String offset is greater than the caret position.
0748: // // This means that we're inside the string-begin section,
0749: // // for example here: %q|(
0750: // // In this case, report no prefix
0751: // return "";
0752: // }
0753: // }
0754: //
0755: // // Regular expression
0756: // int regexpOffset = LexUtilities.getRegexpOffset(lexOffset, th);
0757: //
0758: // if ((regexpOffset != -1) && (regexpOffset <= lexOffset)) {
0759: // // This is not right... I need to actually parse the regexp
0760: // // (I should use my Regexp lexer tokens which will be embedded here)
0761: // // such that escaping sequences (/\\\\\/) will work right, or
0762: // // character classes (/[foo\]). In both cases the \ may not mean escape.
0763: // String tokenText = token.text().toString();
0764: // int index = lexOffset - ts.offset();
0765: //
0766: // if ((index > 0) && (index <= tokenText.length()) &&
0767: // (tokenText.charAt(index - 1) == '\\')) {
0768: // return "\\";
0769: // } else {
0770: // // No prefix for regexps unless it's \
0771: // return "";
0772: // }
0773: //
0774: // //return doc.getText(regexpOffset, offset-regexpOffset);
0775: // }
0776: }
0777:
0778: int lineBegin = Utilities.getRowStart(doc, lexOffset);
0779: if (lineBegin != -1) {
0780: int lineEnd = Utilities.getRowEnd(doc, lexOffset);
0781: String line = doc.getText(lineBegin, lineEnd
0782: - lineBegin);
0783: int lineOffset = lexOffset - lineBegin;
0784: int start = lineOffset;
0785: if (lineOffset > 0) {
0786: for (int i = lineOffset - 1; i >= 0; i--) {
0787: char c = line.charAt(i);
0788: if (!JsUtils.isIdentifierChar(c)) {
0789: break;
0790: } else {
0791: start = i;
0792: }
0793: }
0794: }
0795:
0796: // Find identifier end
0797: String prefix;
0798: if (upToOffset) {
0799: prefix = line.substring(start, lineOffset);
0800: } else {
0801: if (lineOffset == line.length()) {
0802: prefix = line.substring(start);
0803: } else {
0804: int n = line.length();
0805: int end = lineOffset;
0806: for (int j = lineOffset; j < n; j++) {
0807: char d = line.charAt(j);
0808: // Try to accept Foo::Bar as well
0809: if (!JsUtils.isStrictIdentifierChar(d)) {
0810: break;
0811: } else {
0812: end = j + 1;
0813: }
0814: }
0815: prefix = line.substring(start, end);
0816: }
0817: }
0818:
0819: if (prefix.length() > 0) {
0820: if (prefix.endsWith("::")) {
0821: return "";
0822: }
0823:
0824: if (prefix.endsWith(":") && prefix.length() > 1) {
0825: return null;
0826: }
0827:
0828: // Strip out LHS if it's a qualified method, e.g. Benchmark::measure -> measure
0829: int q = prefix.lastIndexOf("::");
0830:
0831: if (q != -1) {
0832: prefix = prefix.substring(q + 2);
0833: }
0834:
0835: // The identifier chars identified by JsLanguage are a bit too permissive;
0836: // they include things like "=", "!" and even "&" such that double-clicks will
0837: // pick up the whole "token" the user is after. But "=" is only allowed at the
0838: // end of identifiers for example.
0839: if (prefix.length() == 1) {
0840: char c = prefix.charAt(0);
0841: if (!(Character.isJavaIdentifierPart(c)
0842: || c == '@' || c == '$' || c == ':')) {
0843: return null;
0844: }
0845: } else {
0846: for (int i = prefix.length() - 2; i >= 0; i--) { // -2: the last position (-1) can legally be =, ! or ?
0847: char c = prefix.charAt(i);
0848: if (i == 0 && c == ':') {
0849: // : is okay at the begining of prefixes
0850: } else if (!(Character
0851: .isJavaIdentifierPart(c)
0852: || c == '@' || c == '$')) {
0853: prefix = prefix.substring(i + 1);
0854: break;
0855: }
0856: }
0857: }
0858:
0859: return prefix;
0860: }
0861: }
0862: } finally {
0863: doc.readUnlock();
0864: }
0865: // Else: normal identifier: just return null and let the machinery do the rest
0866: } catch (IOException ioe) {
0867: Exceptions.printStackTrace(ioe);
0868: } catch (BadLocationException ble) {
0869: Exceptions.printStackTrace(ble);
0870: }
0871:
0872: // Default behavior
0873: return null;
0874: }
0875:
0876: /** Determine if we're trying to complete the name for a "def" (in which case
0877: * we'd show the inherited methods).
0878: * This needs to be enhanced to handle "Foo." prefixes, e.g. def self.foo
0879: */
0880: private boolean completeFunctions(
0881: List<CompletionProposal> proposals,
0882: CompletionRequest request) {
0883: JsIndex index = request.index;
0884: String prefix = request.prefix;
0885: TokenHierarchy<Document> th = request.th;
0886: NameKind kind = request.kind;
0887: String fqn = request.fqn;
0888: JsParseResult result = request.result;
0889:
0890: Set<IndexedElement> matches;
0891: if (fqn != null) {
0892: matches = index.getElements(prefix, fqn, kind,
0893: JsIndex.ALL_SCOPE, result);
0894: } else {
0895: matches = index.getAllNames(prefix, kind,
0896: JsIndex.ALL_SCOPE, result);
0897: }
0898: // Also add in non-fqn-prefixed elements
0899: Set<IndexedElement> top = index.getElements(prefix, null, kind,
0900: JsIndex.ALL_SCOPE, result);
0901: if (top.size() > 0) {
0902: matches.addAll(top);
0903: }
0904:
0905: for (IndexedElement element : matches) {
0906: JsCompletionItem item;
0907: if (element instanceof IndexedFunction) {
0908: item = new FunctionItem((IndexedFunction) element,
0909: request);
0910: } else {
0911: item = new PlainItem(request, element);
0912: }
0913: proposals.add(item);
0914:
0915: }
0916:
0917: return true;
0918: }
0919:
0920: /** Determine if we're trying to complete the name of a method on another object rather
0921: * than an inherited or local one. These should list ALL known methods, unless of course
0922: * we know the type of the method we're operating on (such as strings or regexps),
0923: * or types inferred through data flow analysis
0924: *
0925: * @todo Look for self or this or super; these should be limited to inherited.
0926: */
0927: private boolean completeObjectMethod(
0928: List<CompletionProposal> proposals,
0929: CompletionRequest request) {
0930:
0931: JsIndex index = request.index;
0932: String prefix = request.prefix;
0933: int astOffset = request.astOffset;
0934: int lexOffset = request.lexOffset;
0935: TokenHierarchy<Document> th = request.th;
0936: BaseDocument doc = request.doc;
0937: AstPath path = request.path;
0938: NameKind kind = request.kind;
0939: FileObject fileObject = request.fileObject;
0940: Node node = request.node;
0941: JsParseResult result = request.result;
0942: CompilationInfo info = request.info;
0943:
0944: String fqn = request.fqn;
0945: Call call = request.call;
0946:
0947: TokenSequence<? extends JsTokenId> ts = LexUtilities
0948: .getJsTokenSequence(th, lexOffset);
0949:
0950: // Look in the token stream for constructs of the type
0951: // foo.x^
0952: // or
0953: // foo.^
0954: // and if found, add all methods
0955: // (no keywords etc. are possible matches)
0956: if ((index != null) && (ts != null)) {
0957: boolean skipPrivate = true;
0958:
0959: if ((call == Call.LOCAL) || (call == Call.NONE)) {
0960: return false;
0961: }
0962:
0963: // If we're not sure we're only looking for a method, don't abort after this
0964: boolean done = call.isMethodExpected();
0965:
0966: // boolean skipInstanceMethods = call.isStatic();
0967:
0968: Set<IndexedElement> elements = Collections.emptySet();
0969:
0970: String type = call.getType();
0971: String lhs = call.getLhs();
0972:
0973: if ((type == null) && (lhs != null) && (node != null)
0974: && call.isSimpleIdentifier()) {
0975: Node method = AstUtilities.findLocalScope(node, path);
0976:
0977: if (method != null) {
0978: // TODO - if the lhs is "foo.bar." I need to split this
0979: // up and do it a bit more cleverly
0980: JsTypeAnalyzer analyzer = new JsTypeAnalyzer(info, /*request.info.getParserResult(),*/
0981: index, method, node, astOffset, lexOffset,
0982: doc, fileObject);
0983: type = analyzer.getType(lhs);
0984: }
0985: }
0986:
0987: // I'm not doing any data flow analysis at this point, so
0988: // I can't do anything with a LHS like "foo.". Only actual types.
0989: if ((type != null) && (type.length() > 0)) {
0990: if ("this".equals(lhs)) {
0991: type = fqn;
0992: skipPrivate = false;
0993: // } else if ("super".equals(lhs)) {
0994: // skipPrivate = false;
0995: //
0996: // IndexedClass sc = index.getSuperclass(fqn);
0997: //
0998: // if (sc != null) {
0999: // type = sc.getFqn();
1000: // } else {
1001: // ClassNode cls = AstUtilities.findClass(path);
1002: //
1003: // if (cls != null) {
1004: // type = AstUtilities.getSuperclass(cls);
1005: // }
1006: // }
1007: //
1008: // if (type == null) {
1009: // type = "Object"; // NOI18N
1010: // }
1011: }
1012:
1013: if ((type != null) && (type.length() > 0)) {
1014: // Possibly a class on the left hand side: try searching with the class as a qualifier.
1015: // Try with the LHS + current FQN recursively. E.g. if we're in
1016: // Test::Unit when there's a call to Foo.x, we'll try
1017: // Test::Unit::Foo, and Test::Foo
1018: while (elements.size() == 0 && fqn != null
1019: && !fqn.equals(type)) {
1020: elements = index
1021: .getElements(prefix, fqn + "." + type,
1022: kind, JsIndex.ALL_SCOPE, result);
1023:
1024: int f = fqn.lastIndexOf("::");
1025:
1026: if (f == -1) {
1027: break;
1028: } else {
1029: fqn = fqn.substring(0, f);
1030: }
1031: }
1032:
1033: // Add methods in the class (without an FQN)
1034: Set<IndexedElement> m = index.getElements(prefix,
1035: type, kind, JsIndex.ALL_SCOPE, result);
1036:
1037: if (m.size() > 0) {
1038: elements = m;
1039: }
1040: }
1041: } else if (lhs != null && lhs.length() > 0) {
1042: // No type but an LHS - perhaps it's a type?
1043: Set<IndexedElement> m = index.getElements(prefix, lhs,
1044: kind, JsIndex.ALL_SCOPE, result);
1045:
1046: if (m.size() > 0) {
1047: elements = m;
1048: }
1049: }
1050:
1051: // Try just the method call (e.g. across all classes). This is ignoring the
1052: // left hand side because we can't resolve it.
1053: if ((elements.size() == 0)
1054: && (prefix.length() > 0 || type == null)) {
1055: elements = index.getAllNames(prefix, kind,
1056: JsIndex.ALL_SCOPE, result);
1057: }
1058:
1059: for (IndexedElement element : elements) {
1060: // Skip constructors - you don't want to call
1061: // x.Foo !
1062: // if (element.getKind() == ElementKind.CONSTRUCTOR) {
1063: // continue;
1064: // }
1065:
1066: // // Don't include private or protected methods on other objects
1067: // if (skipPrivate && (method.isPrivate() && !"new".equals(method.getName()))) {
1068: // // TODO - "initialize" removal here should not be necessary since they should
1069: // // be marked as private, but index doesn't contain that yet
1070: // continue;
1071: // }
1072: //
1073: // // We can only call static methods
1074: // if (skipInstanceMethods && !method.isStatic()) {
1075: // continue;
1076: // }
1077: //
1078: if (element.isNoDoc()) {
1079: continue;
1080: }
1081:
1082: if (element instanceof IndexedFunction) {
1083: FunctionItem item = new FunctionItem(
1084: (IndexedFunction) element, request);
1085: proposals.add(item);
1086: } else {
1087: PlainItem item = new PlainItem(request, element);
1088: proposals.add(item);
1089: }
1090: }
1091:
1092: return done;
1093: }
1094:
1095: return false;
1096: }
1097:
1098: /** Determine if we're trying to complete the name for a "new" (in which case
1099: * we show available constructors.
1100: */
1101: private boolean completeNew(List<CompletionProposal> proposals,
1102: CompletionRequest request) {
1103: JsIndex index = request.index;
1104: String prefix = request.prefix;
1105: int lexOffset = request.lexOffset;
1106: TokenHierarchy<Document> th = request.th;
1107: NameKind kind = request.kind;
1108:
1109: TokenSequence<? extends JsTokenId> ts = LexUtilities
1110: .getJsTokenSequence(th, lexOffset);
1111:
1112: if ((index != null) && (ts != null)) {
1113: ts.move(lexOffset);
1114:
1115: if (!ts.moveNext() && !ts.movePrevious()) {
1116: return false;
1117: }
1118:
1119: if (ts.offset() == lexOffset) {
1120: // We're looking at the offset to the RIGHT of the caret
1121: // position, which could be whitespace, e.g.
1122: // "def fo| " <-- looking at the whitespace
1123: ts.movePrevious();
1124: }
1125:
1126: Token<? extends JsTokenId> token = ts.token();
1127:
1128: if (token != null) {
1129: TokenId id = token.id();
1130:
1131: // See if we're in the identifier - "foo" in "def foo"
1132: // I could also be a keyword in case the prefix happens to currently
1133: // match a keyword, such as "next"
1134: if ((id == JsTokenId.IDENTIFIER)
1135: || (id == JsTokenId.CONSTANT)
1136: || id.primaryCategory().equals("keyword")) {
1137: if (!ts.movePrevious()) {
1138: return false;
1139: }
1140:
1141: token = ts.token();
1142: id = token.id();
1143: }
1144:
1145: // If we're not in the identifier we need to be in the whitespace after "def"
1146: if (id != JsTokenId.WHITESPACE) {
1147: // Do something about http://www.netbeans.org/issues/show_bug.cgi?id=100452 here
1148: // In addition to checking for whitespace I should look for "Foo." here
1149: return false;
1150: }
1151:
1152: // There may be more than one whitespace; skip them
1153: while (ts.movePrevious()) {
1154: token = ts.token();
1155:
1156: if (token.id() != JsTokenId.WHITESPACE) {
1157: break;
1158: }
1159: }
1160:
1161: if (token.id() == JsTokenId.NEW) {
1162: Set<IndexedElement> elements = index
1163: .getConstructors(prefix, kind,
1164: JsIndex.ALL_SCOPE);
1165: String lhs = request.call.getLhs();
1166: if (lhs != null && lhs.length() > 0) {
1167: Set<IndexedElement> m = index.getElements(
1168: prefix, lhs, kind, JsIndex.ALL_SCOPE,
1169: null);
1170: if (m.size() > 0) {
1171: if (elements.size() == 0) {
1172: elements = new HashSet<IndexedElement>();
1173: }
1174: for (IndexedElement f : m) {
1175: if (f.getKind() == ElementKind.CONSTRUCTOR
1176: || f.getKind() == ElementKind.PACKAGE) {
1177: elements.add(f);
1178: }
1179: }
1180: }
1181: } else if (prefix.length() > 0) {
1182: Set<IndexedElement> m = index.getElements(
1183: prefix, null, kind, JsIndex.ALL_SCOPE,
1184: null);
1185: if (m.size() > 0) {
1186: if (elements.size() == 0) {
1187: elements = new HashSet<IndexedElement>();
1188: }
1189: for (IndexedElement f : m) {
1190: if (f.getKind() == ElementKind.CONSTRUCTOR
1191: || f.getKind() == ElementKind.PACKAGE) {
1192: elements.add(f);
1193: }
1194: }
1195: }
1196: }
1197:
1198: for (IndexedElement element : elements) {
1199: // Hmmm, is this necessary? Filtering should happen in the getInheritedMEthods call
1200: if ((prefix.length() > 0)
1201: && !element.getName()
1202: .startsWith(prefix)) {
1203: continue;
1204: }
1205:
1206: // // For def completion, skip local methods, only include superclass and included
1207: // if ((fqn != null) && fqn.equals(method.getClz())) {
1208: // continue;
1209: // }
1210: //
1211: // if (method.isNoDoc()) {
1212: // continue;
1213: // }
1214:
1215: // If a method is an "initialize" method I should do something special so that
1216: // it shows up as a "constructor" (in a new() statement) but not as a directly
1217: // callable initialize method (it should already be culled because it's private)
1218: JsCompletionItem item;
1219: if (element instanceof IndexedFunction) {
1220: item = new FunctionItem(
1221: (IndexedFunction) element, request);
1222: } else {
1223: item = new PlainItem(request, element);
1224: }
1225: // Exact matches
1226: // item.setSmart(method.isSmart());
1227: proposals.add(item);
1228: }
1229:
1230: return true;
1231: // } else if (token.id() == JsTokenId.IDENTIFIER && "include".equals(token.text().toString())) {
1232: // // Module completion
1233: // Set<IndexedClass> classes = index.getClasses(prefix, kind, false, true, false);
1234: // for (IndexedClass clz : classes) {
1235: // if (clz.isNoDoc()) {
1236: // continue;
1237: // }
1238: //
1239: // ClassItem item = new ClassItem(clz, anchor, request);
1240: // item.setSmart(true);
1241: // proposals.add(item);
1242: // }
1243: //
1244: // return true;
1245: }
1246: }
1247: }
1248:
1249: return false;
1250: }
1251:
1252: public QueryType getAutoQuery(JTextComponent component,
1253: String typedText) {
1254: char c = typedText.charAt(0);
1255:
1256: // TODO - auto query on ' and " when you're in $() or $F()
1257:
1258: if (c == '\n' || c == '(' || c == '[' || c == '{' || c == ';') {
1259: return QueryType.STOP;
1260: }
1261:
1262: if (c != '.'/* && c != ':'*/) {
1263: return QueryType.NONE;
1264: }
1265:
1266: int offset = component.getCaretPosition();
1267: BaseDocument doc = (BaseDocument) component.getDocument();
1268:
1269: if (".".equals(typedText)) { // NOI18N
1270: // See if we're in Js context
1271: TokenSequence<? extends JsTokenId> ts = LexUtilities
1272: .getJsTokenSequence(doc, offset);
1273: if (ts == null) {
1274: return QueryType.NONE;
1275: }
1276: ts.move(offset);
1277: if (!ts.moveNext()) {
1278: if (!ts.movePrevious()) {
1279: return QueryType.NONE;
1280: }
1281: }
1282: if (ts.offset() == offset && !ts.movePrevious()) {
1283: return QueryType.NONE;
1284: }
1285: Token<? extends JsTokenId> token = ts.token();
1286: TokenId id = token.id();
1287:
1288: // // ".." is a range, not dot completion
1289: // if (id == JsTokenId.RANGE) {
1290: // return QueryType.NONE;
1291: // }
1292:
1293: // TODO - handle embedded JavaScript
1294: if ("comment".equals(id.primaryCategory()) || // NOI18N
1295: "string".equals(id.primaryCategory()) || // NOI18N
1296: "regexp".equals(id.primaryCategory())) { // NOI18N
1297: return QueryType.NONE;
1298: }
1299:
1300: return QueryType.COMPLETION;
1301: }
1302:
1303: // if (":".equals(typedText)) { // NOI18N
1304: // // See if it was "::" and we're in ruby context
1305: // int dot = component.getSelectionStart();
1306: // try {
1307: // if ((dot > 1 && component.getText(dot-2, 1).charAt(0) == ':') && // NOI18N
1308: // isJsContext(doc, dot-1)) {
1309: // return QueryType.COMPLETION;
1310: // }
1311: // } catch (BadLocationException ble) {
1312: // Exceptions.printStackTrace(ble);
1313: // }
1314: // }
1315: //
1316: return QueryType.NONE;
1317: }
1318:
1319: public static boolean isJsContext(BaseDocument doc, int offset) {
1320: TokenSequence<? extends JsTokenId> ts = LexUtilities
1321: .getJsTokenSequence(doc, offset);
1322:
1323: if (ts == null) {
1324: return false;
1325: }
1326:
1327: ts.move(offset);
1328:
1329: if (!ts.movePrevious() && !ts.moveNext()) {
1330: return true;
1331: }
1332:
1333: TokenId id = ts.token().id();
1334: if ("comment".equals(id.primaryCategory())
1335: || "string".equals(id.primaryCategory()) || // NOI18N
1336: "regexp".equals(id.primaryCategory())) { // NOI18N
1337: return false;
1338: }
1339:
1340: return true;
1341: }
1342:
1343: public String resolveTemplateVariable(String variable,
1344: CompilationInfo info, int caretOffset, String name,
1345: Map parameters) {
1346: throw new UnsupportedOperationException("Not supported yet.");
1347: }
1348:
1349: public String document(CompilationInfo info, ElementHandle handle) {
1350: Element element = ElementUtilities.getElement(info, handle);
1351: if (element == null) {
1352: return null;
1353: }
1354: if (element instanceof KeywordElement) {
1355: return null; //getKeywordHelp(((KeywordElement)element).getName());
1356: } else if (element instanceof CommentElement) {
1357: // Text is packaged as the name
1358: String comment = element.getName();
1359: String[] comments = comment.split("\n");
1360: StringBuilder sb = new StringBuilder();
1361: for (int i = 0, n = comments.length; i < n; i++) {
1362: String line = comments[i];
1363: if (line.startsWith("/**")) {
1364: sb.append(line.substring(3));
1365: } else if (i == n - 1 && line.trim().endsWith("*/")) {
1366: sb.append(line.substring(0, line.length() - 2));
1367: continue;
1368: } else if (line.startsWith("//")) {
1369: sb.append(line.substring(2));
1370: } else if (line.startsWith("/*")) {
1371: sb.append(line.substring(2));
1372: } else if (line.startsWith("*")) {
1373: sb.append(line.substring(1));
1374: } else {
1375: sb.append(line);
1376: }
1377: }
1378: String html = sb.toString();
1379: return html;
1380: }
1381:
1382: List<String> comments = ElementUtilities.getComments(info,
1383: element);
1384: if (comments == null) {
1385: String html = ElementUtilities.getSignature(element)
1386: + "\n<hr>\n<i>"
1387: + NbBundle.getMessage(JsCodeCompletion.class,
1388: "NoCommentFound") + "</i>";
1389:
1390: return html;
1391: }
1392:
1393: JsCommentFormatter formatter = new JsCommentFormatter(comments);
1394: if (element instanceof IndexedElement) {
1395: String url = ((IndexedElement) element).getFilenameUrl();
1396: if (url.indexOf("jsstubs/") != -1) { // NOI18N
1397: formatter.setFormattedComment(true);
1398: }
1399: }
1400: String name = element.getName();
1401: if (name != null && name.length() > 0) {
1402: formatter.setSeqName(name);
1403: }
1404:
1405: String html = formatter.toHtml();
1406: html = ElementUtilities.getSignature(element) + "\n<hr>\n"
1407: + html;
1408: return html;
1409: }
1410:
1411: public Set<String> getApplicableTemplates(CompilationInfo info,
1412: int selectionBegin, int selectionEnd) {
1413: return Collections.emptySet();
1414: }
1415:
1416: public ParameterInfo parameters(CompilationInfo info,
1417: int lexOffset, CompletionProposal proposal) {
1418: IndexedFunction[] methodHolder = new IndexedFunction[1];
1419: int[] paramIndexHolder = new int[1];
1420: int[] anchorOffsetHolder = new int[1];
1421: int astOffset = AstUtilities.getAstOffset(info, lexOffset);
1422: if (!computeMethodCall(info, lexOffset, astOffset,
1423: methodHolder, paramIndexHolder, anchorOffsetHolder,
1424: null)) {
1425:
1426: return ParameterInfo.NONE;
1427: }
1428:
1429: IndexedFunction method = methodHolder[0];
1430: if (method == null) {
1431: return ParameterInfo.NONE;
1432: }
1433: int index = paramIndexHolder[0];
1434: int anchorOffset = anchorOffsetHolder[0];
1435:
1436: // TODO: Make sure the caret offset is inside the arguments portion
1437: // (parameter hints shouldn't work on the method call name itself
1438: // See if we can find the method corresponding to this call
1439: // if (proposal != null) {
1440: // Element element = proposal.getElement();
1441: // if (element instanceof IndexedFunction) {
1442: // method = ((IndexedFunction)element);
1443: // }
1444: // }
1445:
1446: List<String> params = method.getParameters();
1447:
1448: if ((params != null) && (params.size() > 0)) {
1449: return new ParameterInfo(params, index, anchorOffset);
1450: }
1451:
1452: return ParameterInfo.NONE;
1453: }
1454:
1455: private static int callLineStart = -1;
1456: private static IndexedFunction callMethod;
1457:
1458: /** Compute the current method call at the given offset. Returns false if we're not in a method call.
1459: * The argument index is returned in parameterIndexHolder[0] and the method being
1460: * called in methodHolder[0].
1461: */
1462: static boolean computeMethodCall(CompilationInfo info,
1463: int lexOffset, int astOffset,
1464: IndexedFunction[] methodHolder, int[] parameterIndexHolder,
1465: int[] anchorOffsetHolder,
1466: Set<IndexedFunction>[] alternativesHolder) {
1467: try {
1468: Node root = AstUtilities.getRoot(info);
1469:
1470: if (root == null) {
1471: return false;
1472: }
1473:
1474: IndexedFunction targetMethod = null;
1475: int index = -1;
1476:
1477: AstPath path = null;
1478: // Account for input sanitation
1479: // TODO - also back up over whitespace, and if I hit the method
1480: // I'm parameter number 0
1481: int originalAstOffset = astOffset;
1482:
1483: // Adjust offset to the left
1484: BaseDocument doc = (BaseDocument) info.getDocument();
1485: int newLexOffset = LexUtilities.findSpaceBegin(doc,
1486: lexOffset);
1487: if (newLexOffset < lexOffset) {
1488: astOffset -= (lexOffset - newLexOffset);
1489: }
1490:
1491: JsParseResult rpr = AstUtilities.getParseResult(info);
1492: OffsetRange range = rpr.getSanitizedRange();
1493: if (range != OffsetRange.NONE
1494: && range.containsInclusive(astOffset)) {
1495: if (astOffset != range.getStart()) {
1496: astOffset = range.getStart() - 1;
1497: if (astOffset < 0) {
1498: astOffset = 0;
1499: }
1500: path = new AstPath(root, astOffset);
1501: }
1502: }
1503:
1504: if (path == null) {
1505: path = new AstPath(root, astOffset);
1506: }
1507:
1508: int currentLineStart = Utilities
1509: .getRowStart(doc, lexOffset);
1510: if (callLineStart != -1
1511: && currentLineStart == callLineStart) {
1512: // We know the method call
1513: targetMethod = callMethod;
1514: if (targetMethod != null) {
1515: // Somehow figure out the argument index
1516: // Perhaps I can keep the node tree around and look in it
1517: // (This is all trying to deal with temporarily broken
1518: // or ambiguous calls.
1519: }
1520: }
1521: // Compute the argument index
1522:
1523: Node call = null;
1524: int anchorOffset = -1;
1525:
1526: if (targetMethod != null) {
1527: Iterator<Node> it = path.leafToRoot();
1528: String name = targetMethod.getName();
1529: while (it.hasNext()) {
1530: Node node = it.next();
1531: // if (AstUtilities.isCall(node) &&
1532: // name.equals(AstUtilities.getCallName(node))) {
1533: // if (node.nodeId == NodeTypes.CALLNODE) {
1534: // Node argsNode = ((CallNode)node).getArgsNode();
1535: //
1536: // if (argsNode != null) {
1537: // index = AstUtilities.findArgumentIndex(argsNode, astOffset);
1538: //
1539: // if (index == -1 && astOffset < originalAstOffset) {
1540: // index = AstUtilities.findArgumentIndex(argsNode, originalAstOffset);
1541: // }
1542: //
1543: // if (index != -1) {
1544: // call = node;
1545: // anchorOffset = argsNode.getPosition().getStartOffset();
1546: // }
1547: // }
1548: // } else if (node.nodeId == NodeTypes.FCALLNODE) {
1549: // Node argsNode = ((FCallNode)node).getArgsNode();
1550: //
1551: // if (argsNode != null) {
1552: // index = AstUtilities.findArgumentIndex(argsNode, astOffset);
1553: //
1554: // if (index == -1 && astOffset < originalAstOffset) {
1555: // index = AstUtilities.findArgumentIndex(argsNode, originalAstOffset);
1556: // }
1557: //
1558: // if (index != -1) {
1559: // call = node;
1560: // anchorOffset = argsNode.getPosition().getStartOffset();
1561: // }
1562: // }
1563: // } else if (node.nodeId == NodeTypes.VCALLNODE) {
1564: // // We might be completing at the end of a method call
1565: // // and we don't have parameters yet so it just looks like
1566: // // a vcall, e.g.
1567: // // create_table |
1568: // // This is okay as long as the caret is outside and to
1569: // // the right of this call. However
1570: // final OffsetRange callRange = AstUtilities.getCallRange(node);
1571: // AstUtilities.getCallName(node);
1572: // if (originalAstOffset > callRange.getEnd()) {
1573: // index = 0;
1574: // call = node;
1575: // anchorOffset = callRange.getEnd()+1;
1576: // }
1577: // }
1578: //
1579: // break;
1580: // }
1581: }
1582: }
1583:
1584: boolean haveSanitizedComma = rpr.getSanitized() == Sanitize.EDITED_DOT
1585: || rpr.getSanitized() == Sanitize.ERROR_DOT;
1586: if (haveSanitizedComma) {
1587: // We only care about removed commas since that
1588: // affects the parameter count
1589: if (rpr.getSanitizedContents().indexOf(',') == -1) {
1590: haveSanitizedComma = false;
1591: }
1592: }
1593:
1594: if (call == null) {
1595: // Find the call in around the caret. Beware of
1596: // input sanitization which could have completely
1597: // removed the current parameter (e.g. with just
1598: // a comma, or something like ", @" or ", :")
1599: // where we accidentally end up in the previous
1600: // parameter.
1601: ListIterator<Node> it = path.leafToRoot();
1602: nodesearch: while (it.hasNext()) {
1603: Node node = it.next();
1604:
1605: if (node.getType() == org.mozilla.javascript.Token.CALL) {
1606: call = node;
1607: index = AstUtilities.findArgumentIndex(call,
1608: astOffset, path);
1609: break;
1610: }
1611: // if (node.nodeId == NodeTypes.CALLNODE) {
1612: // final OffsetRange callRange = AstUtilities.getCallRange(node);
1613: // if (haveSanitizedComma && originalAstOffset > callRange.getEnd() && it.hasNext()) {
1614: // for (int i = 0; i < 3; i++) {
1615: // // It's not really a peek in the sense
1616: // // that there's no reason to retry these
1617: // // nodes later
1618: // Node peek = it.next();
1619: // if (AstUtilities.isCall(peek) &&
1620: // Utilities.getRowStart(doc, LexUtilities.getLexerOffset(info, peek.getPosition().getStartOffset())) ==
1621: // Utilities.getRowStart(doc, lexOffset)) {
1622: // // Use the outer method call instead
1623: // it.previous();
1624: // continue nodesearch;
1625: // }
1626: // }
1627: // }
1628: //
1629: // Node argsNode = ((CallNode)node).getArgsNode();
1630: //
1631: // if (argsNode != null) {
1632: // index = AstUtilities.findArgumentIndex(argsNode, astOffset);
1633: //
1634: // if (index == -1 && astOffset < originalAstOffset) {
1635: // index = AstUtilities.findArgumentIndex(argsNode, originalAstOffset);
1636: // }
1637: //
1638: // if (index != -1) {
1639: // call = node;
1640: // anchorOffset = argsNode.getPosition().getStartOffset();
1641: //
1642: // break;
1643: // }
1644: // } else {
1645: // if (originalAstOffset > callRange.getEnd()) {
1646: // index = 0;
1647: // call = node;
1648: // anchorOffset = callRange.getEnd()+1;
1649: // break;
1650: // }
1651: // }
1652: // } else if (node.nodeId == NodeTypes.FCALLNODE) {
1653: // final OffsetRange callRange = AstUtilities.getCallRange(node);
1654: // if (haveSanitizedComma && originalAstOffset > callRange.getEnd() && it.hasNext()) {
1655: // for (int i = 0; i < 3; i++) {
1656: // // It's not really a peek in the sense
1657: // // that there's no reason to retry these
1658: // // nodes later
1659: // Node peek = it.next();
1660: // if (AstUtilities.isCall(peek) &&
1661: // Utilities.getRowStart(doc, LexUtilities.getLexerOffset(info, peek.getPosition().getStartOffset())) ==
1662: // Utilities.getRowStart(doc, lexOffset)) {
1663: // // Use the outer method call instead
1664: // it.previous();
1665: // continue nodesearch;
1666: // }
1667: // }
1668: // }
1669: //
1670: // Node argsNode = ((FCallNode)node).getArgsNode();
1671: //
1672: // if (argsNode != null) {
1673: // index = AstUtilities.findArgumentIndex(argsNode, astOffset);
1674: //
1675: // if (index == -1 && astOffset < originalAstOffset) {
1676: // index = AstUtilities.findArgumentIndex(argsNode, originalAstOffset);
1677: // }
1678: //
1679: // if (index != -1) {
1680: // call = node;
1681: // anchorOffset = argsNode.getPosition().getStartOffset();
1682: //
1683: // break;
1684: // }
1685: // }
1686: // } else if (node.nodeId == NodeTypes.VCALLNODE) {
1687: // // We might be completing at the end of a method call
1688: // // and we don't have parameters yet so it just looks like
1689: // // a vcall, e.g.
1690: // // create_table |
1691: // // This is okay as long as the caret is outside and to
1692: // // the right of this call.
1693: //
1694: // final OffsetRange callRange = AstUtilities.getCallRange(node);
1695: // if (haveSanitizedComma && originalAstOffset > callRange.getEnd() && it.hasNext()) {
1696: // for (int i = 0; i < 3; i++) {
1697: // // It's not really a peek in the sense
1698: // // that there's no reason to retry these
1699: // // nodes later
1700: // Node peek = it.next();
1701: // if (AstUtilities.isCall(peek) &&
1702: // Utilities.getRowStart(doc, LexUtilities.getLexerOffset(info, peek.getPosition().getStartOffset())) ==
1703: // Utilities.getRowStart(doc, lexOffset)) {
1704: // // Use the outer method call instead
1705: // it.previous();
1706: // continue nodesearch;
1707: // }
1708: // }
1709: // }
1710: //
1711: // if (originalAstOffset > callRange.getEnd()) {
1712: // index = 0;
1713: // call = node;
1714: // anchorOffset = callRange.getEnd()+1;
1715: // break;
1716: // }
1717: // }
1718: }
1719: }
1720:
1721: if (index != -1 && haveSanitizedComma && call != null) {
1722: Node an = null;
1723: // if (call.nodeId == NodeTypes.FCALLNODE) {
1724: // an = ((FCallNode)call).getArgsNode();
1725: // } else if (call.nodeId == NodeTypes.CALLNODE) {
1726: // an = ((CallNode)call).getArgsNode();
1727: // }
1728: // if (an != null && index < an.childNodes().size() &&
1729: // ((Node)an.childNodes().get(index)).nodeId == NodeTypes.HASHNODE) {
1730: // // We should stay within the hashnode, so counteract the
1731: // // index++ which follows this if-block
1732: // index--;
1733: // }
1734:
1735: // Adjust the index to account for our removed
1736: // comma
1737: index++;
1738: }
1739:
1740: if ((call == null) || (index == -1)) {
1741: callLineStart = -1;
1742: callMethod = null;
1743: return false;
1744: } else if (targetMethod == null) {
1745: // Look up the
1746: // See if we can find the method corresponding to this call
1747: targetMethod = new JsDeclarationFinder()
1748: .findMethodDeclaration(info, call, path,
1749: alternativesHolder);
1750: if (targetMethod == null) {
1751: return false;
1752: }
1753: }
1754:
1755: callLineStart = currentLineStart;
1756: callMethod = targetMethod;
1757:
1758: methodHolder[0] = callMethod;
1759: parameterIndexHolder[0] = index;
1760:
1761: if (anchorOffset == -1) {
1762: anchorOffset = call.getSourceStart(); // TODO - compute
1763: }
1764: anchorOffsetHolder[0] = anchorOffset;
1765: } catch (IOException ex) {
1766: Exceptions.printStackTrace(ex);
1767: return false;
1768: } catch (BadLocationException ble) {
1769: Exceptions.printStackTrace(ble);
1770: return false;
1771: }
1772:
1773: return true;
1774: }
1775:
1776: private static class CompletionRequest {
1777: private TokenHierarchy<Document> th;
1778: private CompilationInfo info;
1779: private AstPath path;
1780: private Node node;
1781: private Node root;
1782: private int anchor;
1783: private int lexOffset;
1784: private int astOffset;
1785: private BaseDocument doc;
1786: private String prefix;
1787: private JsIndex index;
1788: private NameKind kind;
1789: private JsParseResult result;
1790: private QueryType queryType;
1791: private FileObject fileObject;
1792: private HtmlFormatter formatter;
1793: private Call call;
1794: private String fqn;
1795: }
1796:
1797: private abstract class JsCompletionItem implements
1798: CompletionProposal {
1799: protected CompletionRequest request;
1800: protected Element element;
1801: protected IndexedElement indexedElement;
1802:
1803: private JsCompletionItem(Element element,
1804: CompletionRequest request) {
1805: this .element = element;
1806: this .request = request;
1807: }
1808:
1809: private JsCompletionItem(CompletionRequest request,
1810: IndexedElement element) {
1811: this (element, request);
1812: this .indexedElement = element;
1813: }
1814:
1815: public int getAnchorOffset() {
1816: return request.anchor;
1817: }
1818:
1819: public String getName() {
1820: return element.getName();
1821: }
1822:
1823: public String getInsertPrefix() {
1824: if (getKind() == ElementKind.PACKAGE) {
1825: return getName() + ".";
1826: } else {
1827: return getName();
1828: }
1829: }
1830:
1831: public String getSortText() {
1832: return getName();
1833: }
1834:
1835: public ElementHandle getElement() {
1836: // XXX Is this called a lot? I shouldn't need it most of the time
1837: return element;
1838: }
1839:
1840: public ElementKind getKind() {
1841: return element.getKind();
1842: }
1843:
1844: public ImageIcon getIcon() {
1845: return null;
1846: }
1847:
1848: public String getLhsHtml() {
1849: ElementKind kind = getKind();
1850: HtmlFormatter formatter = request.formatter;
1851: formatter.reset();
1852: boolean emphasize = (kind != ElementKind.PACKAGE && indexedElement != null) ? !indexedElement
1853: .isInherited()
1854: : false;
1855: if (emphasize) {
1856: formatter.emphasis(true);
1857: }
1858: formatter.name(kind, true);
1859: formatter.appendText(getName());
1860: formatter.name(kind, false);
1861: if (emphasize) {
1862: formatter.emphasis(false);
1863: }
1864:
1865: return formatter.getText();
1866: }
1867:
1868: public String getRhsHtml() {
1869: HtmlFormatter formatter = request.formatter;
1870: formatter.reset();
1871:
1872: String in = element.getIn();
1873:
1874: if (in != null) {
1875: formatter.appendText(in);
1876: return formatter.getText();
1877: } else if (element instanceof IndexedElement) {
1878: IndexedElement ie = (IndexedElement) element;
1879: String filename = ie.getFilenameUrl();
1880: if (filename != null) {
1881: if (filename.indexOf("jsstubs") == -1) { // NOI18N
1882: int index = filename.lastIndexOf('/');
1883: if (index != -1) {
1884: filename = filename.substring(index + 1);
1885: }
1886: formatter.appendText(filename);
1887: return formatter.getText();
1888: } else if (filename.indexOf("/stub_core_") != -1) { // NOI18N
1889: formatter.appendText("Core JS");
1890: return formatter.getText();
1891: } else if (filename.indexOf("/stub_") != -1) { // NOI18N
1892: formatter.appendText("DOM");
1893: return formatter.getText();
1894: }
1895: }
1896:
1897: return null;
1898: }
1899:
1900: return null;
1901: }
1902:
1903: public Set<Modifier> getModifiers() {
1904: return element.getModifiers();
1905: }
1906:
1907: @Override
1908: public String toString() {
1909: String cls = this .getClass().getName();
1910: cls = cls.substring(cls.lastIndexOf('.') + 1);
1911:
1912: return cls + "(" + getKind() + "): " + getName();
1913: }
1914:
1915: public boolean isSmart() {
1916: return indexedElement != null ? indexedElement.isSmart()
1917: : true;
1918: }
1919:
1920: public List<String> getInsertParams() {
1921: return null;
1922: }
1923:
1924: public String[] getParamListDelimiters() {
1925: return new String[] { "(", ")" }; // NOI18N
1926: }
1927:
1928: public String getCustomInsertTemplate() {
1929: return null;
1930: }
1931: }
1932:
1933: private class FunctionItem extends JsCompletionItem {
1934: private IndexedFunction function;
1935:
1936: FunctionItem(IndexedFunction element, CompletionRequest request) {
1937: super (request, element);
1938: this .function = element;
1939: }
1940:
1941: @Override
1942: public String getInsertPrefix() {
1943: return getName();
1944: }
1945:
1946: @Override
1947: public String getLhsHtml() {
1948: ElementKind kind = getKind();
1949: HtmlFormatter formatter = request.formatter;
1950: formatter.reset();
1951: boolean strike = !SupportedBrowsers.getInstance()
1952: .isSupported(function.getCompatibility());
1953: if (strike) {
1954: formatter.deprecated(true);
1955: }
1956: boolean emphasize = !function.isInherited();
1957: if (emphasize) {
1958: formatter.emphasis(true);
1959: }
1960: formatter.name(kind, true);
1961: formatter.appendText(getName());
1962: formatter.name(kind, false);
1963: if (emphasize) {
1964: formatter.emphasis(false);
1965: }
1966:
1967: Collection<String> parameters = function.getParameters();
1968:
1969: formatter.appendHtml("("); // NOI18N
1970: if ((parameters != null) && (parameters.size() > 0)) {
1971:
1972: Iterator<String> it = parameters.iterator();
1973:
1974: while (it.hasNext()) { // && tIt.hasNext()) {
1975: formatter.parameters(true);
1976: formatter.appendText(it.next());
1977: formatter.parameters(false);
1978:
1979: if (it.hasNext()) {
1980: formatter.appendText(", "); // NOI18N
1981: }
1982: }
1983:
1984: }
1985: formatter.appendHtml(")"); // NOI18N
1986:
1987: if (strike) {
1988: formatter.deprecated(false);
1989: }
1990:
1991: return formatter.getText();
1992: }
1993:
1994: @Override
1995: public List<String> getInsertParams() {
1996: return function.getParameters();
1997: }
1998:
1999: @Override
2000: public String getCustomInsertTemplate() {
2001: final String insertPrefix = getInsertPrefix();
2002: List<String> params = getInsertParams();
2003: String startDelimiter = "(";
2004: String endDelimiter = ")";
2005: int paramCount = params.size();
2006:
2007: StringBuilder sb = new StringBuilder();
2008: sb.append(insertPrefix);
2009: sb.append(startDelimiter);
2010:
2011: int id = 1;
2012: for (int i = 0; i < paramCount; i++) {
2013: String paramDesc = params.get(i);
2014: sb.append("${"); //NOI18N
2015: // Ensure that we don't use one of the "known" logical parameters
2016: // such that a parameter like "path" gets replaced with the source file
2017: // path!
2018: sb.append("js-cc-"); // NOI18N
2019: sb.append(Integer.toString(id++));
2020: sb.append(" default=\""); // NOI18N
2021: sb.append(paramDesc);
2022: sb.append("\""); // NOI18N
2023: sb.append("}"); //NOI18N
2024: if (i < paramCount - 1) {
2025: sb.append(", "); //NOI18N
2026: }
2027: }
2028: sb.append(endDelimiter);
2029:
2030: sb.append("${cursor}"); // NOI18N
2031:
2032: // Facilitate method parameter completion on this item
2033: try {
2034: callLineStart = Utilities.getRowStart(request.doc,
2035: request.anchor);
2036: callMethod = function;
2037: } catch (BadLocationException ble) {
2038: Exceptions.printStackTrace(ble);
2039: }
2040:
2041: return sb.toString();
2042: }
2043: }
2044:
2045: private class KeywordItem extends JsCompletionItem {
2046: private static final String Js_KEYWORD = "org/netbeans/modules/javascript/editing/javascript.png"; //NOI18N
2047: private final String keyword;
2048: private final String description;
2049:
2050: KeywordItem(String keyword, String description,
2051: CompletionRequest request) {
2052: super (null, request);
2053: this .keyword = keyword;
2054: this .description = description;
2055: }
2056:
2057: @Override
2058: public String getName() {
2059: return keyword;
2060: }
2061:
2062: @Override
2063: public ElementKind getKind() {
2064: return ElementKind.KEYWORD;
2065: }
2066:
2067: //@Override
2068: //public String getLhsHtml() {
2069: // // Override so we can put HTML contents in
2070: // ElementKind kind = getKind();
2071: // HtmlFormatter formatter = request.formatter;
2072: // formatter.reset();
2073: // formatter.name(kind, true);
2074: // //formatter.appendText(getName());
2075: // formatter.appendHtml(getName());
2076: // formatter.name(kind, false);
2077: //
2078: // return formatter.getText();
2079: //}
2080:
2081: @Override
2082: public String getRhsHtml() {
2083: if (description != null) {
2084: HtmlFormatter formatter = request.formatter;
2085: formatter.reset();
2086: //formatter.appendText(description);
2087: formatter.appendHtml(description);
2088:
2089: return formatter.getText();
2090: } else {
2091: return null;
2092: }
2093: }
2094:
2095: @Override
2096: public ImageIcon getIcon() {
2097: if (keywordIcon == null) {
2098: keywordIcon = new ImageIcon(org.openide.util.Utilities
2099: .loadImage(Js_KEYWORD));
2100: }
2101:
2102: return keywordIcon;
2103: }
2104:
2105: @Override
2106: public Set<Modifier> getModifiers() {
2107: return Collections.emptySet();
2108: }
2109:
2110: @Override
2111: public ElementHandle getElement() {
2112: // For completion documentation
2113: return new KeywordElement(keyword);
2114: }
2115:
2116: @Override
2117: public boolean isSmart() {
2118: return false;
2119: }
2120: }
2121:
2122: private class TagItem extends JsCompletionItem {
2123: private final String tag;
2124: private final String description;
2125:
2126: TagItem(String keyword, String description,
2127: CompletionRequest request) {
2128: super (null, request);
2129: this .tag = keyword;
2130: this .description = description;
2131: }
2132:
2133: @Override
2134: public String getName() {
2135: return tag;
2136: }
2137:
2138: @Override
2139: public ElementKind getKind() {
2140: return ElementKind.TAG;
2141: }
2142:
2143: //@Override
2144: //public String getLhsHtml() {
2145: // // Override so we can put HTML contents in
2146: // ElementKind kind = getKind();
2147: // HtmlFormatter formatter = request.formatter;
2148: // formatter.reset();
2149: // formatter.name(kind, true);
2150: // //formatter.appendText(getName());
2151: // formatter.appendHtml(getName());
2152: // formatter.name(kind, false);
2153: //
2154: // return formatter.getText();
2155: //}
2156:
2157: @Override
2158: public String getRhsHtml() {
2159: if (description != null) {
2160: HtmlFormatter formatter = request.formatter;
2161: formatter.reset();
2162: //formatter.appendText(description);
2163: formatter.appendHtml("<i>");
2164: formatter.appendHtml(description);
2165: formatter.appendHtml("</i>");
2166:
2167: return formatter.getText();
2168: } else {
2169: return null;
2170: }
2171: }
2172:
2173: @Override
2174: public Set<Modifier> getModifiers() {
2175: return Collections.emptySet();
2176: }
2177:
2178: @Override
2179: public ElementHandle getElement() {
2180: // For completion documentation
2181: return new KeywordElement(tag);
2182: }
2183:
2184: @Override
2185: public boolean isSmart() {
2186: return true;
2187: }
2188: }
2189:
2190: private class PlainItem extends JsCompletionItem {
2191: PlainItem(Element element, CompletionRequest request) {
2192: super (element, request);
2193: }
2194:
2195: PlainItem(CompletionRequest request, IndexedElement element) {
2196: super (request, element);
2197: }
2198: }
2199:
2200: public ElementHandle resolveLink(String link,
2201: ElementHandle elementHandle) {
2202: if (link.indexOf(':') != -1) {
2203: link = link.replace(':', '.');
2204: return new ElementHandle.UrlHandle(link);
2205: }
2206: return null;
2207: }
2208: }
|