Source Code Cross Referenced for JsCodeCompletion.java in  » IDE-Netbeans » javascript » org » netbeans » modules » javascript » editing » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » IDE Netbeans » javascript » org.netbeans.modules.javascript.editing 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


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:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.