1 19 package org.netbeans.modules.ruby.lexer; 20 21 import java.util.HashSet ; 22 import java.util.Set ; 23 24 import javax.swing.text.BadLocationException ; 25 import javax.swing.text.Document ; 26 27 import org.netbeans.api.gsf.GsfTokenId; 28 import org.netbeans.api.gsf.OffsetRange; 29 import org.netbeans.api.gsf.annotations.NonNull; 30 import org.netbeans.api.lexer.Token; 31 import org.netbeans.api.lexer.TokenHierarchy; 32 import org.netbeans.api.lexer.TokenId; 33 import org.netbeans.api.lexer.TokenSequence; 34 import org.netbeans.editor.BaseDocument; 35 import org.netbeans.editor.BaseDocument; 36 import org.netbeans.editor.Utilities; 37 import org.netbeans.modules.ruby.lexer.RubyTokenId; 38 import org.openide.util.Exceptions; 39 40 41 48 public class LexUtilities { 49 53 private static final Set <TokenId> END_PAIRS = new HashSet <TokenId>(); 54 55 61 private static final Set <TokenId> INDENT_WORDS = new HashSet <TokenId>(); 62 63 static { 64 END_PAIRS.add(RubyTokenId.BEGIN); 65 END_PAIRS.add(RubyTokenId.FOR); 66 END_PAIRS.add(RubyTokenId.CLASS); 67 END_PAIRS.add(RubyTokenId.DEF); 68 END_PAIRS.add(RubyTokenId.DO); 69 END_PAIRS.add(RubyTokenId.WHILE); 70 END_PAIRS.add(RubyTokenId.IF); 71 END_PAIRS.add(RubyTokenId.CLASS); 72 END_PAIRS.add(RubyTokenId.MODULE); 73 END_PAIRS.add(RubyTokenId.CASE); 74 END_PAIRS.add(RubyTokenId.LOOP); 75 END_PAIRS.add(RubyTokenId.UNTIL); 76 END_PAIRS.add(RubyTokenId.UNLESS); 77 78 INDENT_WORDS.addAll(END_PAIRS); 79 INDENT_WORDS.add(RubyTokenId.ELSE); 84 INDENT_WORDS.add(RubyTokenId.ELSIF); 85 INDENT_WORDS.add(RubyTokenId.ENSURE); 86 INDENT_WORDS.add(RubyTokenId.WHEN); 87 INDENT_WORDS.add(RubyTokenId.RESCUE); 88 89 } 91 92 private LexUtilities() { 93 } 94 95 public static TokenSequence<?extends GsfTokenId> getTokenSequence(BaseDocument doc) { 96 TokenHierarchy<Document > th = TokenHierarchy.get((Document )doc); 97 98 TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language()); 99 100 return ts; 101 } 102 103 public static Token<?extends GsfTokenId> getToken(BaseDocument doc, int offset) { 104 TokenSequence<?extends GsfTokenId> ts = getTokenSequence(doc); 105 106 if (ts != null) { 107 ts.move(offset); 108 109 if (!ts.moveNext() && !ts.movePrevious()) { 110 return null; 111 } 112 113 Token<?extends GsfTokenId> token = ts.token(); 114 115 return token; 116 } 117 118 return null; 119 } 120 121 public static char getTokenChar(BaseDocument doc, int offset) { 122 Token<?extends GsfTokenId> token = getToken(doc, offset); 123 124 if (token != null) { 125 String text = token.text().toString(); 126 127 if (text.length() > 0) { 129 return text.charAt(0); 130 } 131 } 132 133 return 0; 134 } 135 136 137 public static OffsetRange findFwd(TokenSequence<?extends GsfTokenId> ts, TokenId up, 138 TokenId down) { 139 int balance = 0; 140 141 while (ts.moveNext()) { 142 Token<?extends GsfTokenId> token = ts.token(); 143 144 if (token.id() == up) { 145 balance++; 146 } else if (token.id() == down) { 147 if (balance == 0) { 148 return new OffsetRange(ts.offset(), ts.offset() + token.length()); 149 } 150 151 balance--; 152 } 153 } 154 155 return OffsetRange.NONE; 156 } 157 158 159 public static OffsetRange findBwd(TokenSequence<?extends GsfTokenId> ts, TokenId up, 160 TokenId down) { 161 int balance = 0; 162 163 while (ts.movePrevious()) { 164 Token<?extends GsfTokenId> token = ts.token(); 165 166 if (token.id() == up) { 167 if (balance == 0) { 168 return new OffsetRange(ts.offset(), ts.offset() + token.length()); 169 } 170 171 balance++; 172 } else if (token.id() == down) { 173 balance--; 174 } 175 } 176 177 return OffsetRange.NONE; 178 } 179 180 185 public static OffsetRange findBegin(TokenSequence<?extends GsfTokenId> ts) { 186 int balance = 0; 187 188 while (ts.movePrevious()) { 189 Token<?extends GsfTokenId> token = ts.token(); 190 TokenId id = token.id(); 191 192 if (isBeginToken(id)) { 193 if (balance == 0) { 194 return new OffsetRange(ts.offset(), ts.offset() + token.length()); 195 } 196 197 balance--; 198 } else if (id == RubyTokenId.END) { 199 balance++; 200 } 201 } 202 203 return OffsetRange.NONE; 204 } 205 206 public static OffsetRange findEnd(TokenSequence<?extends GsfTokenId> ts) { 207 int balance = 0; 208 209 while (ts.moveNext()) { 210 Token<?extends GsfTokenId> token = ts.token(); 211 TokenId id = token.id(); 212 213 if (isBeginToken(id)) { 214 balance--; 215 } else if (id == RubyTokenId.END) { 216 if (balance == 0) { 217 return new OffsetRange(ts.offset(), ts.offset() + token.length()); 218 } 219 220 balance++; 221 } 222 } 223 224 return OffsetRange.NONE; 225 } 226 227 232 public static boolean isBeginToken(TokenId id) { 233 return END_PAIRS.contains(id); 234 } 235 236 240 public static boolean isIndentToken(TokenId id) { 241 return INDENT_WORDS.contains(id); 242 } 243 244 245 public static int getBeginEndLineBalance(BaseDocument doc, int offset) { 246 TokenSequence<?extends GsfTokenId> ts = LexUtilities.getTokenSequence(doc); 247 assert ts != null; 248 249 try { 250 int begin = Utilities.getRowStart(doc, offset); 251 int end = Utilities.getRowEnd(doc, offset); 252 253 ts.move(begin); 254 255 if (!ts.moveNext()) { 256 return 0; 257 } 258 259 int balance = 0; 260 261 do { 262 Token<?extends GsfTokenId> token = ts.token(); 263 TokenId id = token.id(); 264 265 if (isBeginToken(id)) { 266 balance++; 267 } else if (id == RubyTokenId.END) { 268 balance--; 269 } 270 } while (ts.moveNext() && (ts.offset() <= end)); 271 272 return balance; 273 } catch (BadLocationException ble) { 274 Exceptions.printStackTrace(ble); 275 276 return 0; 277 } 278 } 279 280 281 public static int getLineBalance(BaseDocument doc, int offset, TokenId up, TokenId down) { 282 TokenSequence<?extends GsfTokenId> ts = LexUtilities.getTokenSequence(doc); 283 assert ts != null; 284 285 try { 286 int begin = Utilities.getRowStart(doc, offset); 287 int end = Utilities.getRowEnd(doc, offset); 288 289 ts.move(begin); 290 291 if (!ts.moveNext()) { 292 return 0; 293 } 294 295 int balance = 0; 296 297 do { 298 Token<?extends GsfTokenId> token = ts.token(); 299 TokenId id = token.id(); 300 301 if (id == up) { 302 balance++; 303 } else if (id == down) { 304 balance--; 305 } 306 } while (ts.moveNext() && (ts.offset() <= end)); 307 308 return balance; 309 } catch (BadLocationException ble) { 310 Exceptions.printStackTrace(ble); 311 312 return 0; 313 } 314 } 315 316 322 public static int getTokenBalance(BaseDocument doc, TokenId open, TokenId close) 323 throws BadLocationException { 324 TokenSequence<?extends GsfTokenId> ts = LexUtilities.getTokenSequence(doc); 325 326 ts.moveIndex(0); 327 328 if (!ts.moveNext()) { 329 return 0; 330 } 331 332 int balance = 0; 333 334 do { 335 Token t = ts.token(); 336 337 if (t.id() == open) { 338 balance++; 339 } else if (t.id() == close) { 340 balance--; 341 } 342 } while (ts.moveNext()); 343 344 return balance; 345 } 346 347 public static int getLineIndent(BaseDocument doc, int offset) { 348 try { 349 int start = Utilities.getRowStart(doc, offset); 350 int end; 351 352 if (Utilities.isRowWhite(doc, start)) { 353 end = Utilities.getRowEnd(doc, offset); 354 } else { 355 end = Utilities.getRowFirstNonWhite(doc, start); 356 } 357 358 int indent = end - start; 359 360 return indent; 361 } catch (BadLocationException ble) { 362 Exceptions.printStackTrace(ble); 363 364 return 0; 365 } 366 } 367 368 public static void indent(StringBuilder sb, int indent) { 369 for (int i = 0; i < indent; i++) { 370 sb.append(' '); 371 } 372 } 373 374 public static String getIndentString(int indent) { 375 StringBuilder sb = new StringBuilder (indent); 376 indent(sb, indent); 377 378 return sb.toString(); 379 } 380 381 386 public static boolean isCommentOnlyLine(BaseDocument doc, int offset) 387 throws BadLocationException { 388 int begin = Utilities.getRowFirstNonWhite(doc, offset); 389 390 if (begin == -1) { 391 return false; } 393 394 if (begin == doc.getLength()) { 395 return false; 396 } 397 398 return doc.getText(begin, 1).equals("#"); 399 } 400 401 public static void adjustLineIndentation(BaseDocument doc, int offset, int adjustment) { 402 try { 403 int lineBegin = Utilities.getRowStart(doc, offset); 404 405 if (adjustment > 0) { 406 doc.remove(lineBegin, adjustment); 407 } else if (adjustment < 0) { 408 doc.insertString(adjustment, LexUtilities.getIndentString(adjustment), null); 409 } 410 } catch (BadLocationException ble) { 411 Exceptions.printStackTrace(ble); 412 } 413 } 414 415 418 public static int setLineIndentation(BaseDocument doc, int offset, int indent) { 419 int currentIndent = getLineIndent(doc, offset); 420 421 try { 422 int lineBegin = Utilities.getRowStart(doc, offset); 423 424 if (lineBegin == -1) { 425 return currentIndent; 426 } 427 428 int adjust = currentIndent - indent; 429 430 if (adjust > 0) { 431 String text = doc.getText(lineBegin, adjust); 433 434 for (int i = 0; i < text.length(); i++) { 435 if (!Character.isWhitespace(text.charAt(i))) { 436 throw new RuntimeException ( 437 "Illegal indentation adjustment: Deleting non-whitespace chars: " + 438 text); 439 } 440 } 441 442 doc.remove(lineBegin, adjust); 443 } else if (adjust < 0) { 444 adjust = -adjust; 445 doc.insertString(lineBegin, getIndentString(adjust), null); 446 } 447 448 return indent; 449 } catch (BadLocationException ble) { 450 Exceptions.printStackTrace(ble); 451 452 return currentIndent; 453 } 454 } 455 456 459 @SuppressWarnings ("unchecked") 460 public static String getStringAt(int caretOffset, TokenHierarchy<Document > th) { 461 TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language()); 462 463 if (ts == null) { 464 return null; 465 } 466 467 ts.move(caretOffset); 468 469 if (!ts.moveNext() && !ts.movePrevious()) { 470 return null; 471 } 472 473 if (ts.offset() == caretOffset) { 474 ts.movePrevious(); 477 } 478 479 Token<?extends GsfTokenId> token = ts.token(); 480 481 if (token != null) { 482 TokenId id = token.id(); 483 484 if (id == RubyTokenId.EMBEDDED_RUBY) { 487 ts = (TokenSequence)ts.embedded(); 488 assert ts != null; 489 ts.move(caretOffset); 490 491 if (!ts.moveNext() && !ts.movePrevious()) { 492 return null; 493 } 494 495 token = ts.token(); 496 id = token.id(); 497 } 498 499 String string = null; 500 501 int segments = 0; 503 504 while ((id == RubyTokenId.ERROR) || (id == RubyTokenId.STRING_LITERAL) || 505 (id == RubyTokenId.QUOTED_STRING_LITERAL) || (id == RubyTokenId.EMBEDDED_RUBY)) { 506 string = token.text().toString(); 507 segments++; 508 ts.movePrevious(); 509 token = ts.token(); 510 id = token.id(); 511 } 512 513 if ((id == RubyTokenId.STRING_BEGIN) || (id == RubyTokenId.QUOTED_STRING_BEGIN)) { 514 if (segments == 1) { 515 return string; 516 } else { 517 StringBuilder sb = new StringBuilder (); 519 520 while (ts.moveNext()) { 521 token = ts.token(); 522 id = token.id(); 523 524 if ((id == RubyTokenId.ERROR) || (id == RubyTokenId.STRING_LITERAL) || 525 (id == RubyTokenId.QUOTED_STRING_LITERAL) || 526 (id == RubyTokenId.EMBEDDED_RUBY)) { 527 sb.append(token.text()); 528 } else { 529 break; 530 } 531 } 532 533 return sb.toString(); 534 } 535 } 536 } 537 538 return null; 539 } 540 541 548 public static int getRequireStringOffset(int caretOffset, TokenHierarchy<Document > th) { 549 TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language()); 550 551 if (ts == null) { 552 return -1; 553 } 554 555 ts.move(caretOffset); 556 557 if (!ts.moveNext() && !ts.movePrevious()) { 558 return -1; 559 } 560 561 if (ts.offset() == caretOffset) { 562 ts.movePrevious(); 565 } 566 567 Token<?extends GsfTokenId> token = ts.token(); 568 569 if (token != null) { 570 TokenId id = token.id(); 571 572 while ((id == RubyTokenId.ERROR) || (id == RubyTokenId.STRING_LITERAL) || 574 (id == RubyTokenId.QUOTED_STRING_LITERAL) || (id == RubyTokenId.EMBEDDED_RUBY)) { 575 ts.movePrevious(); 576 token = ts.token(); 577 id = token.id(); 578 } 579 580 int stringStart = ts.offset() + token.length(); 581 582 if ((id == RubyTokenId.STRING_BEGIN) || (id == RubyTokenId.QUOTED_STRING_BEGIN)) { 583 while (ts.movePrevious()) { 585 token = ts.token(); 586 587 id = token.id(); 588 589 if ((id == RubyTokenId.WHITESPACE) || (id == RubyTokenId.LPAREN) || 590 (id == RubyTokenId.STRING_LITERAL) || 591 (id == RubyTokenId.QUOTED_STRING_LITERAL)) { 592 continue; 593 } 594 595 if (id == RubyTokenId.IDENTIFIER) { 596 String text = token.text().toString(); 597 598 if (text.equals("require") || text.equals("load")) { 599 return stringStart; 600 } else { 601 return -1; 602 } 603 } else { 604 return -1; 605 } 606 } 607 } 608 } 609 610 return -1; 611 } 612 613 public static int getSingleQuotedStringOffset(int caretOffset, TokenHierarchy<Document > th) { 614 return getLiteralStringOffset(caretOffset, th, RubyTokenId.STRING_BEGIN); 615 } 616 617 public static int getDoubleQuotedStringOffset(int caretOffset, TokenHierarchy<Document > th) { 618 return getLiteralStringOffset(caretOffset, th, RubyTokenId.QUOTED_STRING_BEGIN); 619 } 620 621 public static int getRegexpOffset(int caretOffset, TokenHierarchy<Document > th) { 622 return getLiteralStringOffset(caretOffset, th, RubyTokenId.REGEXP_BEGIN); 623 } 624 625 629 @SuppressWarnings ("unchecked") 630 private static int getLiteralStringOffset(int caretOffset, TokenHierarchy<Document > th, 631 GsfTokenId begin) { 632 TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language()); 633 634 if (ts == null) { 635 return -1; 636 } 637 638 ts.move(caretOffset); 639 640 if (!ts.moveNext() && !ts.movePrevious()) { 641 return -1; 642 } 643 644 if (ts.offset() == caretOffset) { 645 ts.movePrevious(); 648 } 649 650 Token<?extends GsfTokenId> token = ts.token(); 651 652 if (token != null) { 653 TokenId id = token.id(); 654 655 if (id == RubyTokenId.EMBEDDED_RUBY) { 658 ts = (TokenSequence)ts.embedded(); 659 assert ts != null; 660 ts.move(caretOffset); 661 662 if (!ts.moveNext() && !ts.movePrevious()) { 663 return -1; 664 } 665 666 token = ts.token(); 667 id = token.id(); 668 } 669 670 while ((id == RubyTokenId.ERROR) || (id == RubyTokenId.STRING_LITERAL) || 672 (id == RubyTokenId.QUOTED_STRING_LITERAL) || 673 (id == RubyTokenId.REGEXP_LITERAL) || (id == RubyTokenId.EMBEDDED_RUBY)) { 674 ts.movePrevious(); 675 token = ts.token(); 676 id = token.id(); 677 } 678 679 if (id == begin) { 680 if (!ts.moveNext()) { 681 return -1; 682 } 683 684 return ts.offset(); 685 } 686 } 687 688 return -1; 689 } 690 691 707 @NonNull 708 public static Call getCallType(Document doc, TokenHierarchy<Document > th, int offset) { 709 TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language()); 710 ts.move(offset); 711 712 if (!ts.moveNext() && !ts.movePrevious()) { 713 return Call.NONE; 714 } 715 716 if (ts.offset() == offset) { 717 ts.movePrevious(); 721 } 722 723 Token<?extends GsfTokenId> token = ts.token(); 724 725 if (token != null) { 726 TokenId id = token.id(); 727 728 if ((id == RubyTokenId.IDENTIFIER) || id.primaryCategory().equals("keyword")) { 734 if (!".".equals(token.text().toString()) && !ts.movePrevious()) { 735 return Call.NONE; 736 } 737 738 token = ts.token(); 739 id = token.id(); 740 } 741 742 if ((id != RubyTokenId.DOT) && (id != RubyTokenId.COLON3) && 746 !((id == RubyTokenId.IDENTIFIER) && 747 (token.text().toString().equals(".") || token.text().toString().equals("::")))) { 748 return Call.LOCAL; 749 } 750 751 int lastSeparatorOffset = ts.offset(); 752 int beginOffset = lastSeparatorOffset; 753 754 while (ts.movePrevious()) { 757 token = ts.token(); 758 id = token.id(); 759 760 if (id == RubyTokenId.WHITESPACE) { 761 break; 762 } else if (id == RubyTokenId.RBRACKET) { 763 return new Call("Array", null, false); 766 } else if (id == RubyTokenId.RBRACE) { 770 return new Call("Hash", null, false); 771 } else if ((id == RubyTokenId.STRING_END) || (id == RubyTokenId.QUOTED_STRING_END)) { 772 return new Call("String", null, false); 773 } else if (id == RubyTokenId.REGEXP_END) { 774 return new Call("Regexp", null, false); 775 } else if (id == RubyTokenId.INT_LITERAL) { 776 return new Call("Fixnum", null, false); } else if (id == RubyTokenId.FLOAT_LITERAL) { 778 return new Call("Float", null, false); 779 } else if (id == RubyTokenId.TYPE_SYMBOL) { 780 return new Call("Symbol", null, false); 781 } else if (id == RubyTokenId.RANGE) { 782 return new Call("Range", null, false); 783 } else if ((id == RubyTokenId.ANY_KEYWORD) && (token.text().equals("nil"))) { 784 return new Call("NilClass", null, false); 785 } else if ((id == RubyTokenId.ANY_KEYWORD) && (token.text().equals("true"))) { 786 return new Call("TrueClass", null, false); 787 } else if ((id == RubyTokenId.ANY_KEYWORD) && (token.text().equals("false"))) { 788 return new Call("FalseClass", null, false); 789 } else if ((id == RubyTokenId.GLOBAL_VAR || id == RubyTokenId.INSTANCE_VAR || id == RubyTokenId.IDENTIFIER) || 790 id.primaryCategory().equals("keyword") || (id == RubyTokenId.DOT) || 791 (id == RubyTokenId.COLON3) || (id == RubyTokenId.CONSTANT) || 792 (id == RubyTokenId.SUPER) || (id == RubyTokenId.SELF)) { 793 beginOffset = ts.offset(); 795 796 continue; 797 } else if ((id == RubyTokenId.LPAREN) || (id == RubyTokenId.LBRACE) || 798 (id == RubyTokenId.LBRACKET)) { 799 break; 804 } else { 805 return Call.UNKNOWN; 808 } 809 } 810 811 if (beginOffset < lastSeparatorOffset) { 812 try { 813 String lhs = doc.getText(beginOffset, lastSeparatorOffset - beginOffset); 814 815 if (lhs.equals("super") || lhs.equals("self")) { 816 return new Call(lhs, lhs, false); 817 } else if (Character.isUpperCase(lhs.charAt(0))) { 818 return new Call(lhs, lhs, true); 819 } else { 820 return new Call(null, lhs, false); 821 } 822 } catch (BadLocationException ble) { 823 Exceptions.printStackTrace(ble); 824 } 825 } else { 826 return Call.UNKNOWN; 827 } 828 } 829 830 return Call.LOCAL; 831 } 832 833 public static class Call { 834 838 public static final Call LOCAL = new Call(null, null, false); 839 840 843 public static final Call NONE = new Call(null, null, false); 844 845 849 public static final Call UNKNOWN = new Call(null, null, false); 850 private String type; 851 private String lhs; 852 private boolean isStatic; 853 854 public Call(String type, String lhs, boolean isStatic) { 855 this.type = type; 856 this.lhs = lhs; 857 858 if (lhs == null) { 859 lhs = type; 860 } 861 862 this.isStatic = isStatic; 863 } 864 865 public String getType() { 866 return type; 867 } 868 869 public String getLhs() { 870 return lhs; 871 } 872 873 public boolean isStatic() { 874 return isStatic; 875 } 876 877 878 public boolean isSimpleIdentifier() { 879 if (lhs == null) { 880 return false; 881 } 882 883 for (int i = 0, n = lhs.length(); i < n; i++) { 884 char c = lhs.charAt(i); 885 if (Character.isJavaIdentifierPart(c)) { 886 continue; 887 } 888 if (c == '@' || c == '$') { 889 continue; 890 } 891 return false; 892 } 893 894 return true; 895 } 896 897 public String toString() { 898 if (this == LOCAL) { 899 return "LOCAL"; 900 } else if (this == NONE) { 901 return "NONE"; 902 } else if (this == UNKNOWN) { 903 return "UNKNOWN"; 904 } else { 905 return "Call(" + type + "," + lhs + "," + isStatic + ")"; 906 } 907 } 908 } 909 } 910 | Popular Tags |