1 19 package org.netbeans.modules.ruby; 20 21 import java.io.BufferedInputStream ; 22 import java.io.IOException ; 23 import java.io.IOException ; 24 import java.io.InputStream ; 25 import java.util.ArrayList ; 26 import java.util.Collection ; 27 import java.util.Collections ; 28 import java.util.HashMap ; 29 import java.util.HashSet ; 30 import java.util.Iterator ; 31 import java.util.List ; 32 import java.util.Map ; 33 import java.util.Set ; 34 import javax.swing.ImageIcon ; 35 import javax.swing.text.BadLocationException ; 36 import javax.swing.text.Document ; 37 import org.jruby.ast.ArgsNode; 38 import org.jruby.ast.ArgumentNode; 39 import org.jruby.ast.ClassNode; 40 import org.jruby.ast.ClassVarDeclNode; 41 import org.jruby.ast.ClassVarNode; 42 import org.jruby.ast.ConstDeclNode; 43 import org.jruby.ast.DAsgnNode; 44 import org.jruby.ast.DVarNode; 45 import org.jruby.ast.DefnNode; 46 import org.jruby.ast.DefsNode; 47 import org.jruby.ast.GlobalAsgnNode; 48 import org.jruby.ast.IScopingNode; 49 import org.jruby.ast.InstAsgnNode; 50 import org.jruby.ast.InstVarNode; 51 import org.jruby.ast.ListNode; 52 import org.jruby.ast.LocalAsgnNode; 53 import org.jruby.ast.LocalVarNode; 54 import org.jruby.ast.ModuleNode; 55 import org.jruby.ast.Node; 56 import org.jruby.ast.types.INameNode; 57 import org.jruby.lexer.yacc.ISourcePosition; 58 import org.netbeans.api.gsf.CompilationInfo; 59 import org.netbeans.api.gsf.Completable; 60 import org.netbeans.api.gsf.CompletionProposal; 61 import org.netbeans.api.gsf.Element; 62 import org.netbeans.api.gsf.ElementKind; 63 import org.netbeans.api.gsf.GsfTokenId; 64 import org.netbeans.api.gsf.HtmlFormatter; 65 import org.netbeans.modules.ruby.elements.AstModuleElement; 66 import static org.netbeans.api.gsf.Index.*; 67 import org.netbeans.api.gsf.Modifier; 68 import org.netbeans.api.gsf.ParserResult; 69 import org.netbeans.api.lexer.Token; 70 import org.netbeans.api.lexer.TokenHierarchy; 71 import org.netbeans.api.lexer.TokenId; 72 import org.netbeans.api.lexer.TokenSequence; 73 import org.netbeans.editor.BaseDocument; 74 import org.netbeans.editor.Utilities; 75 import org.netbeans.editor.Utilities; 76 import org.netbeans.modules.ruby.elements.AstClassElement; 77 import org.netbeans.modules.ruby.elements.AstElement; 78 import org.netbeans.modules.ruby.elements.AstFieldElement; 79 import org.netbeans.modules.ruby.elements.AstVariableElement; 80 import org.netbeans.modules.ruby.elements.ClassElement; 81 import org.netbeans.modules.ruby.elements.IndexedClass; 82 import org.netbeans.modules.ruby.elements.IndexedElement; 83 import org.netbeans.modules.ruby.elements.IndexedMethod; 84 import org.netbeans.modules.ruby.elements.KeywordElement; 85 import org.netbeans.modules.ruby.elements.MethodElement; 86 import org.netbeans.modules.ruby.lexer.LexUtilities; 87 import org.netbeans.modules.ruby.lexer.LexUtilities.Call; 88 import org.netbeans.modules.ruby.lexer.RubyStringTokenId; 89 import org.netbeans.modules.ruby.lexer.RubyTokenId; 90 import org.openide.ErrorManager; 91 import org.openide.filesystems.FileStateInvalidException; 92 import org.openide.util.Exceptions; 93 94 95 114 public class CodeCompleter implements Completable { 115 private static final String EMPTY = ""; private static final String [] RUBY_BUILTINS = 117 new String [] { 118 "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", 120 "else", "elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", "next", 121 "nil", "not", "or", "redo", "rescue", "retry", "return", "self", "super", "then", "true", 122 "undef", "unless", "until", "when", "while", "yield", 123 124 "__FILE__", "__LINE__", "STDIN", "STDOUT", "STDERR", "ENV", "ARGF", "ARGV", "DATA", 126 "RUBY_VERSION", "RUBY_RELEASE_DATE", "RUBY_PLATFORM", "$DEBUG", "$FILENAME", 127 "$LOAD_PATH", "$stderr", "$stdin", "$stdout", "$VERBOSE", 128 }; 129 private static final String [] RUBY_REGEXP_WORDS = 130 new String [] { 131 "^", "Start of line", 133 134 "$", "End of line", 135 136 "\\A", "Beginning of string", 137 138 "\\z", "End of string", 139 140 "\\Z", "End of string (except \\n)", 141 142 "\\w", "Letter or digit; same as [0-9A-Za-z]", 143 144 "\\W", "Neither letter or digit", 145 146 "\\s", "Space character; same as [ \\t\\n\\r\\f]", 147 148 "\\S", "Non-space character", 149 150 "\\d", "Digit character; same as [0-9]", 151 152 "\\D", "Non-digit character", 153 154 "\\b", "Backspace (0x08) (only if in a range specification)", 155 156 "\\b", "Word boundary (if not in a range specification)", 157 158 "\\B", "Non-word boundary", 159 160 "*", "Zero or more repetitions of the preceding", 161 162 "+", "One or more repetitions of the preceding", 163 164 "{m,n}", "At least m and at most n repetitions of the preceding", 165 166 "?", "At most one repetition of the preceding; same as {0,1}", 167 168 "|", "Either preceding or next expression may match", 169 170 "()", "Grouping", 171 172 "[:alnum:]", "Alphanumeric character class", 173 174 "[:alpha:]", "Uppercase or lowercase letter", 175 176 "[:blank:]", "Blank and tab", 177 178 "[:cntrl:]", "Control characters (at least 0x00-0x1f,0x7f)", 179 180 "[:digit:]", "Digit", 181 182 "[:graph:]", "Printable character excluding space", 183 184 "[:lower:]", "Lowecase letter", 185 186 "[:print:]", "Any printable letter (including space)", 187 188 "[:punct:]", "Printable character excluding space and alphanumeric", 189 190 "[:space:]", "Whitespace (same as \\s)", 191 192 "[:upper:]", "Uppercase letter", 193 194 "[:xdigit:]", "Hex digit (0-9, a-f, A-F)", 195 }; 196 private static final String [] RUBY_PERCENT_WORDS = 197 new String [] { 198 "%q", "String (single-quoting rules)", 200 201 "%Q", "String (double-quoting rules)", 202 203 "%r", "Regular Expression", 204 205 "%x", "Commands", 206 207 "%W", "String Array (double quoting rules)", 208 209 "%w", "String Array (single quoting rules)", 210 211 "%s", "Symbol", 212 }; 213 private static final String [] RUBY_STRING_PAIRS = 214 new String [] { 215 "(", "(delimiters)", 217 218 "{", "{delimiters}", 219 220 "[", "[delimiters]", 221 222 "x", "<i>x</i>delimiters<i>x</i>", 223 }; 224 private static final String [] RUBY_DOLLAR_VARIABLES = 225 new String [] { 226 "$!", "Latest error message", 228 229 "$@", "Location of error", 230 231 "$_", "String last read by gets", 232 233 "$.", "Line number last read by interpreter", 234 235 "$&", "String last matched by regexp", 236 237 "$~", "The last regexp match, as an array of subexpressions", 238 239 "$n", "The nth subexpression in the last match (same as $~[n])", 240 241 "$=", "Case-insensitivity flag", 242 243 "$/", "Input record separator", 244 245 "$\\", "Output record separator", 246 247 "$0", "The name of the ruby script file", 248 249 "$*", "The command line arguments", 250 251 "$$", "Interpreter's process ID", 252 253 "$?", "Exit status of last executed child process", 254 }; 255 private static final String [] RUBY_QUOTED_STRING_ESCAPES = 256 new String [] { 257 "\\a", "Bell/alert (0x07)", 258 259 "\\b", "Backspace (0x08)", 260 261 "\\x", "\\x<i>nn</i>: Hex <i>nn</i>", 262 263 "\\e", "Escape (0x1b)", 264 265 "\\c", "Control-<i>x</i>", 266 267 "\\C-", "Control-<i>x</i>", 268 269 "\\f", "Formfeed (0x0c)", 270 271 "\\n", "Newline (0x0a)", 272 273 "\\M-", "\\M-<i>x</i>: Meta-<i>x</i>", 274 275 "\\r", "Return (0x0d)", 276 277 "\\M-\\C-", "Meta-control-<i>x</i>", 278 279 "\\s", "Space (0x20)", 280 281 "\\", "\\nnn Octal <i>nnn</i>", 282 283 "\\t", "Tab (0x09)", 285 286 "#{", "#{expr}: Value of expr", 287 288 "\\v", "Vertical tab (0x0b)", 289 }; 290 private static ImageIcon keywordIcon; 291 private boolean caseSensitive; 292 private int anchor; 293 private HtmlFormatter formatter; 294 295 public CodeCompleter() { 296 } 297 298 private boolean startsWith(String theString, String prefix) { 299 if ((prefix == null) || (prefix.length() == 0)) { 300 return true; 301 } 302 303 return caseSensitive ? theString.startsWith(prefix) 304 : theString.toLowerCase().startsWith(prefix.toLowerCase()); 305 } 306 307 315 @SuppressWarnings ("unchecked") 316 public String getPrefix(CompilationInfo info, int offset) { 317 try { 318 BaseDocument doc = (BaseDocument)info.getDocument(); 319 320 TokenHierarchy<Document > th = TokenHierarchy.get((Document )doc); 321 322 int requireStart = LexUtilities.getRequireStringOffset(offset, th); 323 324 if (requireStart != -1) { 325 return doc.getText(requireStart, offset - requireStart); 326 } 327 328 TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language()); 329 330 if (ts == null) { 331 return null; 332 } 333 334 ts.move(offset); 335 336 if (!ts.moveNext() && !ts.movePrevious()) { 337 return null; 338 } 339 340 if (ts.offset() == offset) { 341 ts.movePrevious(); 344 } 345 346 Token<?extends GsfTokenId> token = ts.token(); 347 348 if (token != null) { 349 TokenId id = token.id(); 350 351 if (id == RubyTokenId.EMBEDDED_RUBY) { 354 ts = (TokenSequence)ts.embedded(); 355 assert ts != null; 356 ts.move(offset); 357 358 if (!ts.moveNext() && !ts.movePrevious()) { 359 return null; 360 } 361 362 token = ts.token(); 363 id = token.id(); 364 } 365 366 String tokenText = token.text().toString(); 367 368 if ((id == RubyTokenId.STRING_BEGIN) || (id == RubyTokenId.QUOTED_STRING_BEGIN) || 369 ((id == RubyTokenId.ERROR) && tokenText.equals("%"))) { 370 int currOffset = ts.offset(); 371 372 if ((currOffset == (offset - 1)) && (tokenText.length() > 0) && 374 (tokenText.charAt(0) == '%')) { 375 return "%"; 376 } 377 } 378 } 379 380 int doubleQuotedOffset = LexUtilities.getDoubleQuotedStringOffset(offset, th); 381 382 if (doubleQuotedOffset != -1) { 383 if (doubleQuotedOffset == offset) { 385 return ""; 386 } else if (doubleQuotedOffset < offset) { 387 String text = doc.getText(doubleQuotedOffset, offset - doubleQuotedOffset); 388 TokenHierarchy hi = 389 TokenHierarchy.create(text, RubyStringTokenId.languageDouble()); 390 391 TokenSequence seq = hi.tokenSequence(); 392 393 seq.move(offset - doubleQuotedOffset); 394 395 if (!seq.moveNext() && !seq.movePrevious()) { 396 return ""; 397 } 398 399 TokenId id = seq.token().id(); 400 String s = seq.token().text().toString(); 401 402 if ((id == RubyStringTokenId.STRING_ESCAPE) || 403 (id == RubyStringTokenId.STRING_INVALID)) { 404 return s; 405 } else if (s.startsWith("\\")) { 406 return s; 407 } else { 408 return ""; 409 } 410 } else { 411 return ""; 416 } 417 } 418 419 int singleQuotedOffset = LexUtilities.getSingleQuotedStringOffset(offset, th); 420 421 if (singleQuotedOffset != -1) { 422 if (singleQuotedOffset == offset) { 423 return ""; 424 } else if (singleQuotedOffset < offset) { 425 String text = doc.getText(singleQuotedOffset, offset - singleQuotedOffset); 426 TokenHierarchy hi = 427 TokenHierarchy.create(text, RubyStringTokenId.languageSingle()); 428 429 TokenSequence seq = hi.tokenSequence(); 430 431 seq.move(offset - singleQuotedOffset); 432 433 if (!seq.moveNext() && !seq.movePrevious()) { 434 return ""; 435 } 436 437 TokenId id = seq.token().id(); 438 String s = seq.token().text().toString(); 439 440 if ((id == RubyStringTokenId.STRING_ESCAPE) || 441 (id == RubyStringTokenId.STRING_INVALID)) { 442 return s; 443 } else if (s.startsWith("\\")) { 444 return s; 445 } else { 446 return ""; 447 } 448 } else { 449 return ""; 454 } 455 } 456 457 int regexpOffset = LexUtilities.getRegexpOffset(offset, th); 459 460 if ((regexpOffset != -1) && (regexpOffset <= offset)) { 461 String tokenText = token.text().toString(); 466 int index = offset - ts.offset(); 467 468 if ((index > 0) && (index <= tokenText.length()) && 469 (tokenText.charAt(index - 1) == '\\')) { 470 return "\\"; 471 } else { 472 return ""; 474 } 475 476 } 478 479 } catch (IOException ioe) { 481 Exceptions.printStackTrace(ioe); 482 } catch (BadLocationException ble) { 483 Exceptions.printStackTrace(ble); 484 } 485 486 return null; 488 } 489 490 private boolean completeKeywords(List <CompletionProposal> proposals, String prefix, 491 boolean isSymbol) { 492 if ((prefix != null) && prefix.equals("$")) { 494 for (int i = 0, n = RUBY_DOLLAR_VARIABLES.length; i < n; i += 2) { 497 String word = RUBY_DOLLAR_VARIABLES[i]; 498 String desc = RUBY_DOLLAR_VARIABLES[i + 1]; 499 500 KeywordItem item = new KeywordItem(word, desc, anchor); 501 502 if (isSymbol) { 503 item.setSymbol(true); 504 } 505 506 proposals.add(item); 507 } 508 } 509 510 for (String keyword : RUBY_BUILTINS) { 511 if (startsWith(keyword, prefix)) { 512 KeywordItem item = new KeywordItem(keyword, null, anchor); 513 514 if (isSymbol) { 515 item.setSymbol(true); 516 } 517 518 proposals.add(item); 519 } 520 } 521 522 return false; 523 } 524 525 private boolean completeRegexps(List <CompletionProposal> proposals, String prefix) { 526 for (int i = 0, n = RUBY_REGEXP_WORDS.length; i < n; i += 2) { 528 String word = RUBY_REGEXP_WORDS[i]; 529 String desc = RUBY_REGEXP_WORDS[i + 1]; 530 531 if (startsWith(word, prefix)) { 532 KeywordItem item = new KeywordItem(word, desc, anchor); 533 proposals.add(item); 534 } 535 } 536 537 return true; 538 } 539 540 private boolean completePercentWords(List <CompletionProposal> proposals, String prefix) { 541 for (int i = 0, n = RUBY_PERCENT_WORDS.length; i < n; i += 2) { 542 String word = RUBY_PERCENT_WORDS[i]; 543 String desc = RUBY_PERCENT_WORDS[i + 1]; 544 545 if (startsWith(word, prefix)) { 546 KeywordItem item = new KeywordItem(word, desc, anchor); 547 proposals.add(item); 548 } 549 } 550 551 return true; 552 } 553 554 private boolean completeStringBegins(List <CompletionProposal> proposals) { 555 for (int i = 0, n = RUBY_STRING_PAIRS.length; i < n; i += 2) { 556 String word = RUBY_STRING_PAIRS[i]; 557 String desc = RUBY_STRING_PAIRS[i + 1]; 558 559 KeywordItem item = new KeywordItem(word, desc, anchor); 560 proposals.add(item); 561 } 562 563 return true; 564 } 565 566 568 private boolean completeDefMethod(List <CompletionProposal> proposals, RubyIndex index, 569 String prefix, CompilationInfo info, int offset, TokenHierarchy<Document > th, 570 String thisUrl, String fqn, AstPath path, Node node) { 571 TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language()); 572 573 if ((index != null) && (ts != null)) { 574 ts.move(offset); 575 576 if (!ts.moveNext() && !ts.movePrevious()) { 577 return false; 578 } 579 580 if (ts.offset() == offset) { 581 ts.movePrevious(); 585 } 586 587 Token<?extends GsfTokenId> token = ts.token(); 588 589 if (token != null) { 590 TokenId id = token.id(); 591 592 if ((id == RubyTokenId.IDENTIFIER) || id.primaryCategory().equals("keyword")) { 596 if (!ts.movePrevious()) { 597 return false; 598 } 599 600 token = ts.token(); 601 id = token.id(); 602 } 603 604 if (id != RubyTokenId.WHITESPACE) { 606 return false; 607 } 608 609 while (ts.movePrevious()) { 611 token = ts.token(); 612 613 if (token.id() != RubyTokenId.WHITESPACE) { 614 break; 615 } 616 } 617 618 if (token.id() == RubyTokenId.DEF) { 619 Set <IndexedMethod> methods = index.getInheritedMethods(fqn, prefix); 620 621 for (IndexedMethod method : methods) { 622 if ((prefix != null) && (prefix.length() > 0) && 623 !method.getName().startsWith(prefix)) { 624 continue; 625 } 626 627 if ((fqn != null) && fqn.equals(method.getClz())) { 629 continue; 630 } 631 632 MethodItem item = new MethodItem(method, anchor); 636 proposals.add(item); 637 } 638 639 return true; 640 } 641 } 642 } 643 644 return false; 645 } 646 647 654 private boolean completeObjectMethod(List <CompletionProposal> proposals, RubyIndex index, 655 String prefix, CompilationInfo info, int offset, Document doc, TokenHierarchy<Document > th, 656 String thisUrl, String fqn, AstPath path, Node node) { 657 TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language()); 658 659 if ((index != null) && (ts != null)) { 666 Call call = LexUtilities.getCallType(doc, th, offset); 667 boolean skipPrivate = true; 668 669 if ((call == Call.LOCAL) || (call == Call.NONE)) { 670 return false; 671 } 672 673 boolean skipInstanceMethods = call.isStatic(); 674 675 Set <IndexedMethod> methods = Collections.emptySet(); 676 677 String type = call.getType(); 678 String lhs = call.getLhs(); 679 680 if (type == null && lhs != null && node != null && call.isSimpleIdentifier()) { 681 Node method = AstUtilities.findLocalScope(node, path); 682 if (method != null) { 683 TypeAnalyzer analyzer = new TypeAnalyzer(method, offset); 686 type = analyzer.getType(lhs); 687 } 688 } 689 690 if ((type != null) && (type.length() > 0)) { 693 694 if ("self".equals(lhs)) { 695 type = fqn; 696 skipPrivate = false; 697 } else if ("super".equals(lhs)) { 698 skipPrivate = false; 699 700 IndexedClass sc = index.getSuperclass(fqn); 701 702 if (sc != null) { 703 type = sc.getFqn(); 704 } else { 705 ClassNode cls = AstUtilities.findClass(node, path); 706 707 if (cls != null) { 708 type = AstUtilities.getSuperclass(cls); 709 } 710 } 711 } 712 713 if ((type != null) && (type.length() > 0)) { 714 while (methods.size() == 0) { 719 methods = index.getInheritedMethods(fqn + "::" + type, 720 (prefix != null) ? prefix : EMPTY); 721 722 int f = fqn.lastIndexOf("::"); 723 724 if (f == -1) { 725 break; 726 } else { 727 fqn = fqn.substring(0, f); 728 } 729 } 730 731 methods = index.getInheritedMethods(type, (prefix != null) ? prefix : EMPTY); 733 } 734 } 735 736 if ((methods.size() == 0)) { 739 methods = index.getMethods((prefix != null) ? prefix : EMPTY, null, 740 caseSensitive ? NameKind.PREFIX : NameKind.CASE_INSENSITIVE_PREFIX); 741 } 742 743 for (IndexedMethod method : methods) { 744 if (skipPrivate && (method.isPrivate() && !"new".equals(method.getName()))) { 746 continue; 749 } 750 751 if (skipInstanceMethods && !method.isStatic()) { 753 continue; 754 } 755 756 MethodItem methodItem = new MethodItem(method, anchor); 761 proposals.add(methodItem); 762 } 763 764 return true; 765 } 766 767 return false; 768 } 769 770 @SuppressWarnings ("unchecked") 771 private boolean completeStrings(List <CompletionProposal> proposals, RubyIndex index, 772 String prefix, int caretOffset, TokenHierarchy<Document > th) { 773 TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language()); 774 775 if ((index != null) && (ts != null)) { 776 ts.move(caretOffset); 777 778 if (!ts.moveNext() && !ts.movePrevious()) { 779 return false; 780 } 781 782 if (ts.offset() == caretOffset) { 783 ts.movePrevious(); 786 } 787 788 Token<?extends GsfTokenId> token = ts.token(); 789 790 if (token != null) { 791 TokenId id = token.id(); 792 793 if (id == RubyTokenId.EMBEDDED_RUBY) { 796 ts = (TokenSequence)ts.embedded(); 797 assert ts != null; 798 ts.move(caretOffset); 799 800 if (!ts.moveNext() && !ts.movePrevious()) { 801 return false; 802 } 803 804 token = ts.token(); 805 id = token.id(); 806 } 807 808 boolean inString = false; 809 boolean isQuoted = false; 810 boolean inRegexp = false; 811 String tokenText = token.text().toString(); 812 813 if ((id == RubyTokenId.STRING_BEGIN) || (id == RubyTokenId.QUOTED_STRING_BEGIN) || 815 ((id == RubyTokenId.ERROR) && tokenText.equals("%"))) { 816 int offset = ts.offset(); 817 818 if ((offset == (caretOffset - 1)) && (tokenText.length() > 0) && 819 (tokenText.charAt(0) == '%')) { 820 if (completePercentWords(proposals, prefix)) { 821 return true; 822 } 823 } 824 } 825 826 if (((id == RubyTokenId.STRING_BEGIN) || (id == RubyTokenId.QUOTED_STRING_BEGIN) || 828 (id == RubyTokenId.REGEXP_BEGIN)) && 829 ((token.length() == 3) && (caretOffset == (ts.offset() + 2)))) { 830 if (Character.isLetter(tokenText.charAt(1))) { 831 completeStringBegins(proposals); 832 833 return true; 834 } 835 } 836 837 while ((id == RubyTokenId.ERROR) || (id == RubyTokenId.STRING_LITERAL) || 840 (id == RubyTokenId.QUOTED_STRING_LITERAL) || 841 (id == RubyTokenId.REGEXP_LITERAL) || (id == RubyTokenId.EMBEDDED_RUBY)) { 842 if (!ts.movePrevious()) { 843 return false; 844 } 845 846 token = ts.token(); 847 id = token.id(); 848 } 849 850 if (id == RubyTokenId.STRING_BEGIN) { 851 inString = true; 852 } else if (id == RubyTokenId.QUOTED_STRING_BEGIN) { 853 inString = true; 854 isQuoted = true; 855 } else if (id == RubyTokenId.REGEXP_BEGIN) { 856 inRegexp = true; 857 } 858 859 if (inRegexp) { 860 if (completeRegexps(proposals, prefix)) { 861 return true; 862 } 863 } else if (inString) { 864 while (ts.movePrevious()) { 866 token = ts.token(); 867 868 if ((token.id() == RubyTokenId.WHITESPACE) || 869 (token.id() == RubyTokenId.LPAREN) || 870 (token.id() == RubyTokenId.STRING_LITERAL) || 871 (token.id() == RubyTokenId.QUOTED_STRING_LITERAL) || 872 (token.id() == RubyTokenId.STRING_BEGIN) || 873 (token.id() == RubyTokenId.QUOTED_STRING_BEGIN)) { 874 continue; 875 } 876 877 if (token.id() == RubyTokenId.IDENTIFIER) { 878 String text = token.text().toString(); 879 880 if (text.equals("require") || text.equals("load")) { 881 Set <String []> requires = 883 index.getRequires((prefix != null) ? prefix : EMPTY, 884 caseSensitive ? NameKind.PREFIX 885 : NameKind.CASE_INSENSITIVE_PREFIX); 886 887 for (String [] require : requires) { 888 assert require.length == 2; 889 890 KeywordItem item = 894 new KeywordItem(require[0], require[1], anchor); 895 proposals.add(item); 896 } 897 898 return true; 899 } else { 900 break; 901 } 902 } else { 903 break; 904 } 905 } 906 907 if (inString && isQuoted) { 908 for (int i = 0, n = RUBY_QUOTED_STRING_ESCAPES.length; i < n; i += 2) { 909 String word = RUBY_QUOTED_STRING_ESCAPES[i]; 910 String desc = RUBY_QUOTED_STRING_ESCAPES[i + 1]; 911 912 if ((prefix != null) && !word.startsWith(prefix)) { 913 continue; 914 } 915 916 KeywordItem item = new KeywordItem(word, desc, anchor); 917 proposals.add(item); 918 } 919 920 return true; 921 } else if (inString) { 922 return true; 924 } 925 } 926 } 927 } 928 929 return false; 930 } 931 932 public List <CompletionProposal> complete(CompilationInfo info, int caretOffset, String prefix, 934 boolean caseSensitive, HtmlFormatter formatter) { 935 this.caseSensitive = caseSensitive; 936 this.formatter = formatter; 937 938 List <CompletionProposal> proposals = new ArrayList <CompletionProposal>(); 940 941 anchor = caretOffset - ((prefix == null) ? 0 : prefix.length()); 942 943 RubyIndex index = RubyIndex.get(info.getIndex()); 944 945 Document doc = null; 946 947 try { 948 doc = info.getDocument(); 949 } catch (Exception e) { 950 Exceptions.printStackTrace(e); 951 } 952 953 int length = doc.getLength(); 958 959 if (caretOffset > length) { 960 caretOffset = length; 961 } 962 963 TokenHierarchy<Document > th = TokenHierarchy.get(doc); 965 966 if (completeStrings(proposals, index, prefix, caretOffset, th)) { 970 return proposals; 971 } 972 973 boolean showLower = true; 974 boolean showUpper = true; 975 boolean showSymbols = false; 976 char first = 0; 977 978 if ((prefix != null) && (prefix.length() > 0)) { 979 first = prefix.charAt(0); 980 showLower = Character.isLowerCase(first); 981 showUpper = Character.isUpperCase(first); 983 984 if (first == ':') { 985 showSymbols = true; 986 987 if (prefix.length() > 1) { 988 char second = prefix.charAt(1); 989 prefix = prefix.substring(1); 990 showLower = Character.isLowerCase(second); 991 showUpper = Character.isUpperCase(second); 992 } 993 } 994 } 995 996 Node root = AstUtilities.getRoot(info); 999 1000 if (root == null) { 1001 completeKeywords(proposals, prefix, showSymbols); 1002 1003 return proposals; 1004 } 1005 1006 int lineBegin = -1; 1009 int lineEnd = -1; 1010 1011 try { 1012 if (doc instanceof BaseDocument) { 1013 BaseDocument bdoc = (BaseDocument)doc; 1014 lineBegin = Utilities.getRowStart(bdoc, caretOffset); 1015 lineEnd = Utilities.getRowEnd(bdoc, caretOffset); 1016 } 1017 } catch (BadLocationException ble) { 1018 Exceptions.printStackTrace(ble); 1019 } 1020 1021 AstPath path = new AstPath(root, caretOffset); 1022 1023 Map <String , Node> variables = new HashMap <String , Node>(); 1024 Map <String , Node> methods = new HashMap <String , Node>(); 1025 Map <String , Node> fields = new HashMap <String , Node>(); 1026 Map <String , Node> globals = new HashMap <String , Node>(); 1027 Map <String , Node> constants = new HashMap <String , Node>(); 1028 Map <String , Node> classes = new HashMap <String , Node>(); 1029 1030 Node closest = path.leaf(); 1031 1032 if (showLower && (closest != null)) { 1033 Node block = AstUtilities.findDynamicScope(closest, path); 1034 addDynamic(block, variables); 1035 1036 Node method = AstUtilities.findLocalScope(closest, path); 1037 addLocals(method, variables); 1038 } 1039 1040 Node top = path.root(); 1042 1043 assert root == top; 1045 1046 if (showUpper || showSymbols) { 1047 addClasses(top, classes); 1048 addConstants(top, constants); 1049 } 1050 1051 if (showLower || showSymbols) { 1052 addMethods(top, methods); 1053 } 1054 1055 if ((first == '@') || showSymbols) { 1057 addFields(top, fields); 1058 } 1059 1060 if ((first == '$') || showSymbols) { 1061 addGlobals(top, globals); 1062 } 1063 1064 if (index != null) { 1066 String thisUrl = ""; 1067 1068 try { 1069 thisUrl = info.getFileObject().getURL().toExternalForm(); 1070 } catch (FileStateInvalidException fse) { 1071 Exceptions.printStackTrace(fse); 1072 } 1073 1074 if (showLower || showSymbols) { 1075 String fqn = AstUtilities.getFqnName(path); 1076 1077 if ((fqn != null) && 1078 completeDefMethod(proposals, index, prefix, info, caretOffset, th, thisUrl, 1079 fqn, path, closest)) { 1080 return proposals; 1081 } 1082 1083 if ((fqn != null) && 1084 completeObjectMethod(proposals, index, prefix, info, caretOffset, doc, th, 1085 thisUrl, fqn, path, closest)) { 1086 return proposals; 1087 } 1088 1089 Set <IndexedMethod> inheritedMethods = index.getInheritedMethods(fqn, prefix); 1092 1093 for (IndexedMethod method : inheritedMethods) { 1094 if ((prefix != null) && (prefix.length() > 0) && 1095 !method.getName().startsWith(prefix)) { 1096 continue; 1097 } 1098 1099 if (thisUrl.equals(method.getFileUrl())) { 1100 continue; 1101 } 1102 1103 MethodItem item = new MethodItem(method, anchor); 1107 1108 if (showSymbols) { 1109 item.setSymbol(true); 1110 } 1111 1112 proposals.add(item); 1113 } 1114 } 1115 1116 if (showUpper || showSymbols) { 1117 int classAnchor = anchor; 1118 int fqnIndex = (prefix != null) ? prefix.lastIndexOf("::") : (-1); 1119 1120 if (fqnIndex != -1) { 1121 classAnchor += (fqnIndex + 2); 1122 } 1123 1124 for (IndexedClass cls : index.getClasses((prefix != null) ? prefix : EMPTY, 1125 caseSensitive ? NameKind.PREFIX : NameKind.CASE_INSENSITIVE_PREFIX, false, 1126 false, false)) { 1127 if (thisUrl.equals(cls.getFileUrl())) { 1129 continue; 1130 } 1131 1132 ClassItem item = new ClassItem(cls, classAnchor); 1133 1134 if (showSymbols) { 1135 item.setSymbol(true); 1136 } 1137 1138 proposals.add(item); 1139 } 1140 } 1141 } 1142 1143 for (String variable : variables.keySet()) { 1146 if (startsWith(variable, prefix)) { 1147 Node node = variables.get(variable); 1148 1149 if (!overlapsLine(node, lineBegin, lineEnd)) { 1150 AstVariableElement co = new AstVariableElement(node, variable); 1151 PlainItem item = new PlainItem(co, anchor); 1152 1153 if (showSymbols) { 1154 item.setSymbol(true); 1155 } 1156 1157 proposals.add(item); 1158 } 1159 } 1160 } 1161 1162 for (String method : methods.keySet()) { 1163 if (isOperator(method)) { 1164 continue; 1165 } 1166 1167 if (startsWith(method, prefix)) { 1168 Node node = methods.get(method); 1169 1170 if (overlapsLine(node, lineBegin, lineEnd)) { 1171 continue; 1172 } 1173 1174 Element co = AstElement.create(node); 1175 assert co != null; 1176 1177 MethodItem item = new MethodItem(co, anchor); 1178 1179 if (showSymbols) { 1180 item.setSymbol(true); 1181 } 1182 1183 proposals.add(item); 1184 } 1185 } 1186 1187 for (String field : fields.keySet()) { 1188 if (startsWith(field, prefix)) { 1189 Node node = fields.get(field); 1190 1191 if (overlapsLine(node, lineBegin, lineEnd)) { 1192 continue; 1193 } 1194 1195 Element co = new AstFieldElement(node); 1196 FieldItem item = new FieldItem(co, anchor); 1197 1198 if (showSymbols) { 1199 item.setSymbol(true); 1200 } 1201 1202 proposals.add(item); 1203 } 1204 } 1205 1206 for (String variable : globals.keySet()) { 1208 if (startsWith(variable, prefix) || 1209 (showSymbols && startsWith(variable.substring(1), prefix))) { 1210 Node node = globals.get(variable); 1211 1212 if (overlapsLine(node, lineBegin, lineEnd)) { 1213 continue; 1214 } 1215 1216 AstElement co = new AstVariableElement(node, variable); 1217 PlainItem item = new PlainItem(co, anchor); 1218 1219 if (showSymbols) { 1220 item.setSymbol(true); 1221 } 1222 1223 proposals.add(item); 1224 } 1225 } 1226 1227 for (String variable : constants.keySet()) { 1229 if (startsWith(variable, prefix)) { 1230 Node node = classes.get(variable); 1232 1233 if (node != null) { 1234 continue; 1235 } 1236 1237 node = constants.get(variable); 1238 1239 if (overlapsLine(node, lineBegin, lineEnd)) { 1240 continue; 1241 } 1242 1243 AstElement co = new AstVariableElement(node, variable); 1253 PlainItem item = new PlainItem(co, anchor); 1254 1255 if (showSymbols) { 1256 item.setSymbol(true); 1257 } 1258 1259 proposals.add(item); 1260 } 1261 } 1262 1263 for (String clz : classes.keySet()) { 1264 if (startsWith(clz, prefix)) { 1265 Node node = classes.get(clz); 1266 1267 if (overlapsLine(node, lineBegin, lineEnd)) { 1268 continue; 1269 } 1270 1271 ClassItem item; 1272 if (node instanceof ClassNode) { 1273 AstClassElement co = new AstClassElement(node); 1274 item = new ClassItem(co, anchor); 1275 } else { 1276 assert node instanceof ModuleNode; 1277 AstModuleElement co = new AstModuleElement(node); 1278 item = new ClassItem(co, anchor); 1279 } 1280 1281 1282 if (showSymbols) { 1283 item.setSymbol(true); 1284 } 1285 1286 proposals.add(item); 1287 } 1288 } 1289 1290 if (completeKeywords(proposals, prefix, showSymbols)) { 1291 return proposals; 1292 } 1293 1294 return proposals; 1295 } 1296 1297 private boolean overlapsLine(Node node, int lineBegin, int lineEnd) { 1310 ISourcePosition pos = node.getPosition(); 1311 1312 return ((pos.getStartOffset() >= lineBegin) && (pos.getStartOffset() <= lineEnd)); 1316 } 1317 1318 1319 private boolean isOperator(String name) { 1320 int n = name.length(); 1323 1324 if (n > 2) { 1325 return false; 1326 } 1327 1328 for (int i = 0; i < n; i++) { 1329 if (Character.isLetter(name.charAt(i))) { 1330 return false; 1331 } 1332 } 1333 1334 return true; 1335 } 1336 1337 @SuppressWarnings ("unchecked") 1338 private void addLocals(Node node, Map <String , Node> variables) { 1339 if (node instanceof LocalVarNode) { 1340 variables.put(((INameNode)node).getName(), node); 1341 } else if (node instanceof LocalAsgnNode) { 1342 variables.put(((INameNode)node).getName(), node); 1343 } else if (node instanceof ArgsNode) { 1344 ArgsNode an = (ArgsNode)node; 1345 1346 if (an.getArgsCount() > 0) { 1347 List <Node> args = (List <Node>)an.childNodes(); 1348 1349 for (Node arg : args) { 1350 if (arg instanceof ListNode) { 1351 List <Node> args2 = (List <Node>)arg.childNodes(); 1352 1353 for (Node arg2 : args2) { 1354 if (arg2 instanceof ArgumentNode) { 1355 variables.put(((ArgumentNode)arg2).getName(), arg2); 1356 } else if (arg2 instanceof LocalAsgnNode) { 1357 variables.put(((INameNode)arg2).getName(), arg2); 1358 } 1359 } 1360 } 1361 } 1362 } 1363 1364 1369 } 1378 1379 List <Node> list = node.childNodes(); 1380 1381 for (Node child : list) { 1382 addLocals(child, variables); 1383 } 1384 } 1385 1386 private void addDynamic(Node node, Map <String , Node> variables) { 1387 if (node instanceof DVarNode) { 1388 variables.put(((DVarNode)node).getName(), node); 1389 } else if (node instanceof DAsgnNode) { 1390 variables.put(((INameNode)node).getName(), node); 1391 1392 } 1427 1428 @SuppressWarnings ("unchecked") 1429 List <Node> list = node.childNodes(); 1430 1431 for (Node child : list) { 1432 addDynamic(child, variables); 1433 } 1434 } 1435 1436 private void addFields(Node node, Map <String , Node> fields) { 1437 if (node instanceof InstVarNode) { 1438 String name = ((INameNode)node).getName(); 1439 1440 if (!fields.containsKey(name)) { fields.put(name, node); 1442 } 1443 } else if (node instanceof InstAsgnNode) { 1444 fields.put(((INameNode)node).getName(), node); 1445 } else if (node instanceof ClassVarNode) { 1446 String name = ((ClassVarNode)node).getName(); 1448 if (!fields.containsKey(name)) { fields.put(name, node); 1450 } 1451 } else if (node instanceof ClassVarDeclNode) { 1452 fields.put(((INameNode)node).getName(), node); 1453 1454 } 1465 1466 @SuppressWarnings ("unchecked") 1467 List <Node> list = node.childNodes(); 1468 1469 for (Node child : list) { 1470 addFields(child, fields); 1471 } 1472 } 1473 1474 private void addClasses(Node node, Map <String , Node> classes) { 1475 if (node instanceof ClassNode || node instanceof ModuleNode) { 1476 String name = AstUtilities.getClassOrModuleName((IScopingNode)node); 1477 classes.put(name, node); 1478 } 1479 1480 @SuppressWarnings ("unchecked") 1481 List <Node> list = node.childNodes(); 1482 1483 for (Node child : list) { 1484 addClasses(child, classes); 1485 } 1486 } 1487 1488 private void addGlobals(Node node, Map <String , Node> globals) { 1489 1498 if (node instanceof GlobalAsgnNode) { 1499 globals.put(((INameNode)node).getName(), node); 1500 1501 } 1512 1513 @SuppressWarnings ("unchecked") 1514 List <Node> list = node.childNodes(); 1515 1516 for (Node child : list) { 1517 addGlobals(child, globals); 1518 } 1519 } 1520 1521 private void addMethods(Node node, Map <String , Node> methods) { 1522 if (node instanceof DefnNode) { 1524 String name = ((DefnNode)node).getName(); 1526 1527 methods.put(name, node); 1528 } else if (node instanceof DefsNode) { 1529 String name = ((DefsNode)node).getName(); 1531 1532 methods.put(name, node); 1533 1534 } 1561 1562 @SuppressWarnings ("unchecked") 1563 List <Node> list = node.childNodes(); 1564 1565 for (Node child : list) { 1566 addMethods(child, methods); 1567 } 1568 } 1569 1570 private void addConstants(Node node, Map <String , Node> constants) { 1571 if (node instanceof ConstDeclNode) { 1573 constants.put(((INameNode)node).getName(), node); 1574 } 1575 1576 @SuppressWarnings ("unchecked") 1577 List <Node> list = node.childNodes(); 1578 1579 for (Node child : list) { 1580 addConstants(child, constants); 1581 } 1582 } 1583 1584 private String loadResource(String basename) { 1585 InputStream is = 1587 new BufferedInputStream (CodeCompleter.class.getResourceAsStream("resources/" + 1588 basename)); 1589 StringBuilder sb = new StringBuilder (); 1590 1591 try { 1592 while (true) { 1594 int c = is.read(); 1595 1596 if (c == -1) { 1597 break; 1598 } 1599 1600 sb.append((char)c); 1601 } 1602 1603 if (sb.length() > 0) { 1604 return sb.toString(); 1605 } 1606 } catch (IOException ie) { 1607 Exceptions.printStackTrace(ie); 1608 1609 try { 1610 is.close(); 1611 } catch (IOException ie2) { 1612 Exceptions.printStackTrace(ie2); 1613 } 1614 } 1615 1616 return null; 1617 } 1618 1619 private String getKeywordHelp(String keyword) { 1620 if (keyword.equals("if") || keyword.equals("elsif") || keyword.equals("else") || 1623 keyword.equals("then") || keyword.equals("unless")) { 1625 return loadResource("ifelse.html"); } else if (keyword.equals("case") || keyword.equals("when") || keyword.equals("else")) { 1628 return loadResource("case.html"); } else if (keyword.equals("rescue") || keyword.equals("ensure")) { 1631 return loadResource("rescue.html"); } else if (keyword.equals("yield")) { 1634 return loadResource("yield.html"); } 1636 1637 return null; 1638 } 1639 1640 1646 private IndexedElement findDocumentationEntry(Node root, IndexedElement obj) { 1647 String fqn = obj.getSignature(); 1649 Set <?extends IndexedElement> result = obj.getIndex().getDocumented(fqn); 1650 1651 if ((result == null) || (result.size() == 0)) { 1652 return null; 1653 } else if (result.size() == 1) { 1654 return result.iterator().next(); 1655 } 1656 1657 Set <IndexedElement> candidates = new HashSet <IndexedElement>(); 1661 Set <String > requires = AstUtilities.getRequires(root); 1662 1663 for (IndexedElement o : result) { 1664 String require = o.getRequire(); 1665 1666 if (requires.contains(require)) { 1667 candidates.add(o); 1668 } 1669 } 1670 1671 if (candidates.size() == 1) { 1672 return candidates.iterator().next(); 1673 } else if (!candidates.isEmpty()) { 1674 result = candidates; 1675 } 1676 1677 candidates = new HashSet <IndexedElement>(); 1679 1680 for (IndexedElement o : result) { 1681 String url = o.getFileUrl(); 1682 1683 if (url.indexOf("rubystubs") != -1) { 1684 candidates.add(o); 1685 } 1686 } 1687 1688 if (candidates.size() == 1) { 1689 return candidates.iterator().next(); 1690 } else if (!candidates.isEmpty()) { 1691 result = candidates; 1692 } 1693 1694 1697 return result.iterator().next(); 1699 } 1700 1701 public String document(CompilationInfo info, Element element) { 1702 if (element == null) { 1703 return null; 1704 } 1705 1706 Node node = null; 1707 1708 if (element instanceof KeywordElement) { 1709 return getKeywordHelp(((KeywordElement)element).getName()); 1710 } else if (element instanceof AstElement) { 1711 node = ((AstElement)element).getNode(); 1712 } else if (element instanceof IndexedElement) { 1713 IndexedElement com = (IndexedElement)element; 1714 Node root = AstUtilities.getRoot(info); 1715 IndexedElement match = findDocumentationEntry(root, com); 1716 1717 if (match != null) { 1718 com = match; 1719 element = com; 1720 } 1721 1722 node = AstUtilities.getForeignNode(com); 1723 1724 if (node == null) { 1725 return null; 1726 } 1727 } else { 1728 assert false : element; 1729 1730 return null; 1731 } 1732 1733 ParserResult parseResult = info.getParserResult(); 1734 1735 if (parseResult == null) { 1736 return null; 1737 } 1738 1739 Document doc = null; 1749 BaseDocument baseDoc = null; 1750 1751 try { 1752 if (element instanceof IndexedElement) { 1753 doc = ((IndexedElement)element).getDocument(); 1754 } else { 1755 doc = info.getDocument(); 1756 } 1757 1758 if (doc instanceof BaseDocument) { 1759 baseDoc = (BaseDocument)doc; 1760 } else { 1761 return null; 1762 } 1763 } catch (IOException ioe) { 1764 ErrorManager.getDefault().notify(ioe); 1765 1766 return null; 1767 } 1768 1769 List <String > comments = null; 1770 1771 if (node instanceof ClassNode && !(element instanceof IndexedElement)) { 1773 String className = AstUtilities.getClassOrModuleName((ClassNode)node); 1774 List <ClassNode> classes = AstUtilities.getClasses(AstUtilities.getRoot(info)); 1775 1776 for (int i = classes.size() - 1; i >= 0; i--) { 1779 ClassNode clz = classes.get(i); 1780 String name = AstUtilities.getClassOrModuleName(clz); 1781 1782 if (name.equals(className)) { 1783 comments = AstUtilities.gatherDocumentation(baseDoc, clz); 1784 1785 if ((comments != null) && (comments.size() > 0)) { 1786 break; 1787 } 1788 } 1789 } 1790 } else { 1791 comments = AstUtilities.gatherDocumentation(baseDoc, node); 1792 } 1793 1794 if ((comments == null) || (comments.size() == 0)) { 1795 return null; 1796 } 1797 1798 RDocFormatter formatter = new RDocFormatter(); 1799 1800 for (String text : comments) { 1801 formatter.appendLine(text); 1802 } 1803 1804 return getSignature(element) + "<br>" + formatter.toHtml(); 1805 } 1806 1807 private String getSignature(Element element) { 1808 StringBuilder sb = new StringBuilder (); 1809 sb.append("<pre>"); 1811 1812 if (element instanceof MethodElement) { 1813 MethodElement executable = (MethodElement)element; 1814 sb.append(executable.getName()); 1816 1817 Collection <String > parameters = executable.getParameters(); 1818 1819 if ((parameters != null) && (parameters.size() > 0)) { 1820 sb.append("("); 1821 1822 sb.append("<font color=\"#808080\">"); 1823 1824 for (Iterator <String > it = parameters.iterator(); it.hasNext();) { 1825 String ve = it.next(); 1826 sb.append(ve); 1828 1829 if (it.hasNext()) { 1830 sb.append(", "); 1831 } 1832 } 1833 1834 sb.append("</font>"); 1835 1836 sb.append(")"); 1837 } 1838 } else { 1839 sb.append(element.getName()); 1840 } 1841 1842 sb.append("</pre>"); 1843 1844 return sb.toString(); 1845 } 1846 1847 private abstract class RubyCompletionItem implements CompletionProposal { 1848 protected Element element; 1849 protected int anchorOffset; 1850 protected boolean symbol; 1851 1852 private RubyCompletionItem(Element element, int anchorOffset) { 1853 this.element = element; 1854 this.anchorOffset = anchorOffset; 1855 } 1856 1857 public int getAnchorOffset() { 1858 return anchorOffset; 1859 } 1860 1861 public String getName() { 1862 return element.getName(); 1863 } 1864 1865 public void setSymbol(boolean symbol) { 1866 this.symbol = symbol; 1867 } 1868 1869 public String getInsertPrefix() { 1870 if (symbol) { 1871 return ":" + getName(); 1872 } else { 1873 return getName(); 1874 } 1875 } 1876 1877 public String getSortText() { 1878 return getName(); 1879 } 1880 1881 public Element getElement() { 1882 return element; 1883 } 1884 1885 public ElementKind getKind() { 1886 return element.getKind(); 1887 } 1888 1889 public ImageIcon getIcon() { 1890 return null; 1891 } 1892 1893 public String getLhsHtml() { 1894 ElementKind kind = getKind(); 1895 formatter.reset(); 1896 formatter.name(kind, true); 1897 formatter.appendText(getName()); 1898 formatter.name(kind, false); 1899 1900 return formatter.getText(); 1901 } 1902 1903 public Set <Modifier> getModifiers() { 1904 return element.getModifiers(); 1905 } 1906 1907 public String toString() { 1908 String cls = getClass().getName(); 1909 cls = cls.substring(cls.lastIndexOf('.')+1); 1910 return cls + "(" + getKind() + "): " + getName(); 1911 } 1912 } 1913 1914 private class MethodItem extends RubyCompletionItem { 1915 MethodItem(Element element, int anchorOffset) { 1916 super(element, anchorOffset); 1917 } 1918 1919 public String getLhsHtml() { 1920 ElementKind kind = getKind(); 1921 formatter.reset(); 1922 formatter.name(kind, true); 1923 formatter.appendText(getName()); 1924 formatter.name(kind, false); 1925 1926 Collection <String > parameters = ((MethodElement)element).getParameters(); 1927 1928 if ((parameters != null) && (parameters.size() > 0)) { 1929 formatter.appendHtml("("); 1931 Iterator <String > it = parameters.iterator(); 1932 1933 while (it.hasNext()) { formatter.parameters(true); 1935 formatter.appendText(it.next()); 1936 formatter.parameters(false); 1937 1938 if (it.hasNext()) { 1939 formatter.appendText(", "); } 1941 } 1942 1943 formatter.appendHtml(")"); } 1945 1946 return formatter.getText(); 1947 } 1948 1949 public String getRhsHtml() { 1950 formatter.reset(); 1951 1952 String in = ((MethodElement)element).getIn(); 1953 1954 if (in != null) { 1955 formatter.appendText(in); 1956 } else { 1957 return null; 1958 } 1959 1960 return formatter.getText(); 1961 } 1962 } 1963 1964 private class KeywordItem extends RubyCompletionItem { 1965 private static final String RUBY_KEYWORD = "org/netbeans/modules/ruby/jruby.png"; private String keyword; 1967 private String description; 1968 1969 KeywordItem(String keyword, String description, int anchorOffset) { 1970 super(null, anchorOffset); 1971 this.keyword = keyword; 1972 this.description = description; 1973 } 1974 1975 public String getName() { 1976 return keyword; 1977 } 1978 1979 public ElementKind getKind() { 1980 return ElementKind.KEYWORD; 1981 } 1982 1983 public String getRhsHtml() { 1984 if (description != null) { 1985 formatter.reset(); 1986 formatter.appendText(description); 1987 1988 return formatter.getText(); 1989 } else { 1990 return null; 1991 } 1992 } 1993 1994 public ImageIcon getIcon() { 1995 if (keywordIcon == null) { 1996 keywordIcon = new ImageIcon (org.openide.util.Utilities.loadImage(RUBY_KEYWORD)); 1997 } 1998 1999 return keywordIcon; 2000 } 2001 2002 public Element getElement() { 2003 return new KeywordElement(keyword); 2005 } 2006 } 2007 2008 private class ClassItem extends RubyCompletionItem { 2009 ClassItem(Element element, int anchorOffset) { 2010 super(element, anchorOffset); 2011 } 2012 2013 public String getRhsHtml() { 2014 formatter.reset(); 2015 2016 String in = ((ClassElement)element).getIn(); 2017 2018 if (in != null) { 2019 formatter.appendText(in); 2020 } else { 2021 return null; 2022 } 2023 2024 return formatter.getText(); 2025 } 2026 } 2027 2028 private class PlainItem extends RubyCompletionItem { 2029 PlainItem(Element element, int anchorOffset) { 2030 super(element, anchorOffset); 2031 } 2032 2033 public String getRhsHtml() { 2034 return null; 2035 } 2036 } 2037 2038 private class FieldItem extends RubyCompletionItem { 2039 FieldItem(Element element, int anchorOffset) { 2040 super(element, anchorOffset); 2041 } 2042 2043 public String getRhsHtml() { 2044 return null; 2045 } 2046 2047 public String getInsertPrefix() { 2048 if (symbol) { 2049 return ":" + getName(); 2050 } 2051 2052 if (element.getModifiers().contains(Modifier.STATIC)) { 2053 return "@@" + getName(); 2054 } else { 2055 return "@" + getName(); 2056 } 2057 } 2058 } 2059} 2060 | Popular Tags |