1 11 package org.eclipse.jdt.internal.ui.text.java; 12 13 import java.util.Arrays ; 14 15 import org.eclipse.text.edits.DeleteEdit; 16 import org.eclipse.text.edits.MalformedTreeException; 17 import org.eclipse.text.edits.ReplaceEdit; 18 import org.eclipse.text.edits.TextEdit; 19 20 import org.eclipse.core.runtime.Assert; 21 22 import org.eclipse.jface.preference.IPreferenceStore; 23 24 import org.eclipse.jface.text.BadLocationException; 25 import org.eclipse.jface.text.DocumentCommand; 26 import org.eclipse.jface.text.IAutoEditStrategy; 27 import org.eclipse.jface.text.IDocument; 28 import org.eclipse.jface.text.IRegion; 29 import org.eclipse.jface.text.ITextSelection; 30 import org.eclipse.jface.text.ITypedRegion; 31 import org.eclipse.jface.text.Region; 32 import org.eclipse.jface.text.TextSelection; 33 import org.eclipse.jface.text.TextUtilities; 34 35 import org.eclipse.ui.IEditorPart; 36 import org.eclipse.ui.IWorkbenchPage; 37 import org.eclipse.ui.texteditor.ITextEditorExtension2; 38 import org.eclipse.ui.texteditor.ITextEditorExtension3; 39 40 import org.eclipse.jdt.ui.PreferenceConstants; 41 import org.eclipse.jdt.ui.text.IJavaPartitions; 42 43 import org.eclipse.jdt.internal.ui.JavaPlugin; 44 import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor; 45 import org.eclipse.jdt.internal.ui.text.SmartBackspaceManager; 46 import org.eclipse.jdt.internal.ui.text.SmartBackspaceManager.UndoSpec; 47 48 59 public class SmartSemicolonAutoEditStrategy implements IAutoEditStrategy { 60 61 62 private static final String SEMICOLON= ";"; 64 private static final char SEMICHAR= ';'; 65 66 private static final String BRACE= "{"; 68 private static final char BRACECHAR= '{'; 69 70 private char fCharacter; 71 private String fPartitioning; 72 73 78 public SmartSemicolonAutoEditStrategy(String partitioning) { 79 fPartitioning= partitioning; 80 } 81 82 85 public void customizeDocumentCommand(IDocument document, DocumentCommand command) { 86 91 if (command.text == null) 92 return; 93 94 if (command.text.equals(SEMICOLON)) 95 fCharacter= SEMICHAR; 96 else if (command.text.equals(BRACE)) 97 fCharacter= BRACECHAR; 98 else 99 return; 100 101 IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore(); 102 if (fCharacter == SEMICHAR && !store.getBoolean(PreferenceConstants.EDITOR_SMART_SEMICOLON)) 103 return; 104 if (fCharacter == BRACECHAR && !store.getBoolean(PreferenceConstants.EDITOR_SMART_OPENING_BRACE)) 105 return; 106 107 IWorkbenchPage page= JavaPlugin.getActivePage(); 108 if (page == null) 109 return; 110 IEditorPart part= page.getActiveEditor(); 111 if (!(part instanceof CompilationUnitEditor)) 112 return; 113 CompilationUnitEditor editor= (CompilationUnitEditor)part; 114 if (editor.getInsertMode() != ITextEditorExtension3.SMART_INSERT || !editor.isEditable()) 115 return; 116 ITextEditorExtension2 extension= (ITextEditorExtension2)editor.getAdapter(ITextEditorExtension2.class); 117 if (extension != null && !extension.validateEditorInputState()) 118 return; 119 if (isMultilineSelection(document, command)) 120 return; 121 122 int pos= command.offset; 124 ITextSelection line; 125 try { 126 IRegion l= document.getLineInformationOfOffset(pos); 127 line= new TextSelection(document, l.getOffset(), l.getLength()); 128 } catch (BadLocationException e) { 129 return; 130 } 131 132 int positionInLine= computeCharacterPosition(document, line, pos - line.getOffset(), fCharacter, fPartitioning); 135 int position= positionInLine + line.getOffset(); 136 137 if (position < pos) 139 return; 140 141 if (alreadyPresent(document, fCharacter, position)) 143 return; 144 145 String insertion= adjustSpacing(document, position, fCharacter); 147 if (command.offset == position && insertion.equals(command.text)) 148 return; 149 150 try { 151 152 final SmartBackspaceManager manager= (SmartBackspaceManager) editor.getAdapter(SmartBackspaceManager.class); 153 if (manager != null && JavaPlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SMART_BACKSPACE)) { 154 TextEdit e1= new ReplaceEdit(command.offset, command.text.length(), document.get(command.offset, command.length)); 155 UndoSpec s1= new UndoSpec(command.offset + command.text.length(), 156 new Region(command.offset, 0), 157 new TextEdit[] {e1}, 158 0, 159 null); 160 161 DeleteEdit smart= new DeleteEdit(position, insertion.length()); 162 ReplaceEdit raw= new ReplaceEdit(command.offset, command.length, command.text); 163 UndoSpec s2= new UndoSpec(position + insertion.length(), 164 new Region(command.offset + command.text.length(), 0), 165 new TextEdit[] {smart, raw}, 166 2, 167 s1); 168 manager.register(s2); 169 } 170 171 command.offset= position; 173 command.length= 0; 174 command.caretOffset= position; 175 command.text= insertion; 176 command.doit= true; 177 command.owner= null; 178 } catch (MalformedTreeException e) { 179 JavaPlugin.log(e); 180 } catch (BadLocationException e) { 181 JavaPlugin.log(e); 182 } 183 184 185 } 186 187 195 private boolean isMultilineSelection(IDocument document, DocumentCommand command) { 196 try { 197 return document.getNumberOfLines(command.offset, command.length) > 1; 198 } catch (BadLocationException e) { 199 return false; 201 } 202 } 203 204 213 private String adjustSpacing(IDocument doc, int position, char character) { 214 if (character == BRACECHAR) { 215 if (position > 0 && position <= doc.getLength()) { 216 int pos= position - 1; 217 if (looksLike(doc, pos, ")") || looksLike(doc, pos, "=") || looksLike(doc, pos, "]") || looksLike(doc, pos, "try") || looksLike(doc, pos, "else") || looksLike(doc, pos, "synchronized") || looksLike(doc, pos, "static") || looksLike(doc, pos, "finally") || looksLike(doc, pos, "do")) return new String (new char[] { ' ', character }); 227 } 228 } 229 230 return new String (new char[] { character }); 231 } 232 233 242 private boolean alreadyPresent(IDocument document, char ch, int position) { 243 int pos= firstNonWhitespaceForward(document, position, fPartitioning, document.getLength()); 244 try { 245 if (pos != -1 && document.getChar(pos) == ch) 246 return true; 247 } catch (BadLocationException e) { 248 } 249 250 return false; 251 } 252 253 263 protected static int computeCharacterPosition(IDocument document, ITextSelection line, int offset, char character, String partitioning) { 264 String text= line.getText(); 265 if (text == null) 266 return 0; 267 268 int insertPos; 269 if (character == BRACECHAR) { 270 271 insertPos= computeArrayInitializationPos(document, line, offset, partitioning); 272 273 if (insertPos == -1) { 274 insertPos= computeAfterTryDoElse(document, line, offset); 275 } 276 277 if (insertPos == -1) { 278 insertPos= computeAfterParenthesis(document, line, offset, partitioning); 279 } 280 281 } else if (character == SEMICHAR) { 282 283 if (isForStatement(text, offset)) { 284 insertPos= -1; } else { 286 int nextPartitionPos= nextPartitionOrLineEnd(document, line, offset, partitioning); 287 insertPos= startOfWhitespaceBeforeOffset(text, nextPartitionPos); 288 if (insertPos > 0 && text.charAt(insertPos - 1) == character) 290 insertPos= insertPos - 1; 291 else if (insertPos > 0 && text.charAt(insertPos - 1) == '}') { 292 int opening= scanBackward(document, insertPos - 1 + line.getOffset(), partitioning, -1, new char[] { '{' }); 293 if (opening > -1 && opening < offset + line.getOffset()) { 294 if (computeArrayInitializationPos(document, line, opening - line.getOffset(), partitioning) == -1) { 295 insertPos= offset; 296 } 297 } 298 } 299 } 300 301 } else { 302 Assert.isTrue(false); 303 return -1; 304 } 305 306 return insertPos; 307 } 308 309 319 private static int computeArrayInitializationPos(IDocument document, ITextSelection line, int offset, String partitioning) { 320 int pos= offset + line.getOffset(); 322 323 if (pos == 0) 324 return -1; 325 326 int p= firstNonWhitespaceBackward(document, pos - 1, partitioning, -1); 327 328 if (p == -1) 329 return -1; 330 331 try { 332 333 char ch= document.getChar(p); 334 if (ch != '=' && ch != ']') 335 return -1; 336 337 if (p == 0) 338 return offset; 339 340 p= firstNonWhitespaceBackward(document, p - 1, partitioning, -1); 341 if (p == -1) 342 return -1; 343 344 ch= document.getChar(p); 345 if (Character.isJavaIdentifierPart(ch) || ch == ']' || ch == '[') 346 return offset; 347 348 } catch (BadLocationException e) { 349 } 350 return -1; 351 } 352 353 363 private static int computeAfterTryDoElse(IDocument doc, ITextSelection line, int offset) { 364 int p= offset + line.getOffset(); 366 p= firstWhitespaceToRight(doc, p); 367 if (p == -1) 368 return -1; 369 p--; 370 371 if (looksLike(doc, p, "try") || looksLike(doc, p, "do") || looksLike(doc, p, "synchronized") || looksLike(doc, p, "static") || looksLike(doc, p, "finally") || looksLike(doc, p, "else")) return p + 1 - line.getOffset(); 378 379 return -1; 380 } 381 382 392 private static int computeAfterParenthesis(IDocument document, ITextSelection line, int offset, String partitioning) { 393 int pos= offset + line.getOffset(); 397 int length= line.getOffset() + line.getLength(); 398 int scanTo= scanForward(document, pos, partitioning, length, '}'); 399 if (scanTo == -1) 400 scanTo= length; 401 402 int closingParen= findClosingParenToLeft(document, pos, partitioning) - 1; 403 404 while (true) { 405 int startScan= closingParen + 1; 406 closingParen= scanForward(document, startScan, partitioning, scanTo, ')'); 407 if (closingParen == -1) 408 break; 409 410 int openingParen= findOpeningParenMatch(document, closingParen, partitioning); 411 412 if (openingParen < 1) 414 break; 415 416 if (openingParen > pos) 418 continue; 419 420 if (looksLikeAnonymousClassDef(document, openingParen - 1, partitioning)) 421 return closingParen + 1 - line.getOffset(); 422 423 if (looksLikeIfWhileForCatch(document, openingParen - 1, partitioning)) 424 return closingParen + 1 - line.getOffset(); 425 426 if (looksLikeMethodDecl(document, openingParen - 1, partitioning)) 427 return closingParen + 1 - line.getOffset(); 428 429 } 430 431 return -1; 432 } 433 434 443 private static int findClosingParenToLeft(IDocument document, int position, String partitioning) { 444 final char CLOSING_PAREN= ')'; 445 try { 446 if (position < 1) 447 return position; 448 449 int nonWS= firstNonWhitespaceBackward(document, position - 1, partitioning, -1); 450 if (nonWS != -1 && document.getChar(nonWS) == CLOSING_PAREN) 451 return nonWS; 452 } catch (BadLocationException e1) { 453 } 454 return position; 455 } 456 457 464 private static int firstWhitespaceToRight(IDocument document, int position) { 465 int length= document.getLength(); 466 Assert.isTrue(position >= 0); 467 Assert.isTrue(position <= length); 468 469 try { 470 while (position < length) { 471 char ch= document.getChar(position); 472 if (Character.isWhitespace(ch)) 473 return position; 474 position++; 475 } 476 return position; 477 } catch (BadLocationException e) { 478 } 479 return -1; 480 } 481 482 493 private static int firstNonWhitespaceBackward(IDocument document, int position, String partitioning, int bound) { 494 Assert.isTrue(position < document.getLength()); 495 Assert.isTrue(bound >= -1); 496 497 try { 498 while (position > bound) { 499 char ch= document.getChar(position); 500 if (!Character.isWhitespace(ch) && isDefaultPartition(document, position, partitioning)) 501 return position; 502 position--; 503 } 504 } catch (BadLocationException e) { 505 } 506 return -1; 507 } 508 509 520 private static int firstNonWhitespaceForward(IDocument document, int position, String partitioning, int bound) { 521 Assert.isTrue(position >= 0); 522 Assert.isTrue(bound <= document.getLength()); 523 524 try { 525 while (position < bound) { 526 char ch= document.getChar(position); 527 if (!Character.isWhitespace(ch) && isDefaultPartition(document, position, partitioning)) 528 return position; 529 position++; 530 } 531 } catch (BadLocationException e) { 532 } 533 return -1; 534 } 535 536 548 private static int scanBackward(IDocument document, int position, String partitioning, int bound, char[] chars) { 549 Assert.isTrue(bound >= -1); 550 Assert.isTrue(position < document.getLength() ); 551 552 Arrays.sort(chars); 553 554 try { 555 while (position > bound) { 556 557 if (Arrays.binarySearch(chars, document.getChar(position)) >= 0 && isDefaultPartition(document, position, partitioning)) 558 return position; 559 560 position--; 561 } 562 } catch (BadLocationException e) { 563 } 564 return -1; 565 } 566 567 594 private static int scanForward(IDocument document, int position, String partitioning, int bound, char[] chars) { 595 Assert.isTrue(position >= 0); 596 Assert.isTrue(bound <= document.getLength()); 597 598 Arrays.sort(chars); 599 600 try { 601 while (position < bound) { 602 603 if (Arrays.binarySearch(chars, document.getChar(position)) >= 0 && isDefaultPartition(document, position, partitioning)) 604 return position; 605 606 position++; 607 } 608 } catch (BadLocationException e) { 609 } 610 return -1; 611 } 612 613 625 private static int scanForward(IDocument document, int position, String partitioning, int bound, char ch) { 626 return scanForward(document, position, partitioning, bound, new char[] {ch}); 627 } 628 629 639 private static boolean isNewMatch(IDocument document, int offset, int length, String partitioning) { 640 Assert.isTrue(length >= 0); 641 Assert.isTrue(offset >= 0); 642 Assert.isTrue(offset + length < document.getLength() + 1); 643 644 try { 645 String text= document.get(offset, length); 646 int pos= text.indexOf("new"); 648 while (pos != -1 && !isDefaultPartition(document, pos + offset, partitioning)) 649 pos= text.indexOf("new", pos + 2); 651 if (pos < 0) 652 return false; 653 654 if (pos != 0 && Character.isJavaIdentifierPart(text.charAt(pos - 1))) 655 return false; 656 657 if (pos + 3 < length && Character.isJavaIdentifierPart(text.charAt(pos + 3))) 658 return false; 659 660 return true; 661 662 } catch (BadLocationException e) { 663 } 664 return false; 665 } 666 667 677 private static boolean looksLikeAnonymousClassDef(IDocument document, int position, String partitioning) { 678 int previousCommaParenEqual= scanBackward(document, position - 1, partitioning, -1, new char[] {',', '(', '='}); 679 if (previousCommaParenEqual == -1 || position < previousCommaParenEqual + 5) return false; 681 682 if (isNewMatch(document, previousCommaParenEqual + 1, position - previousCommaParenEqual - 2, partitioning)) 683 return true; 684 685 return false; 686 } 687 688 696 private static boolean isDefaultPartition(IDocument document, int position, String partitioning) { 697 Assert.isTrue(position >= 0); 698 Assert.isTrue(position <= document.getLength()); 699 700 try { 701 ITypedRegion region= TextUtilities.getPartition(document, partitioning, position, false); 703 return region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE); 704 705 } catch (BadLocationException e) { 706 } 707 708 return false; 709 } 710 711 719 private static int findOpeningParenMatch(IDocument document, int position, String partitioning) { 720 final char CLOSING_PAREN= ')'; 721 final char OPENING_PAREN= '('; 722 723 Assert.isTrue(position < document.getLength()); 724 Assert.isTrue(position >= 0); 725 Assert.isTrue(isDefaultPartition(document, position, partitioning)); 726 727 try { 728 729 Assert.isTrue(document.getChar(position) == CLOSING_PAREN); 730 731 int depth= 1; 732 while (true) { 733 position= scanBackward(document, position - 1, partitioning, -1, new char[] {CLOSING_PAREN, OPENING_PAREN}); 734 if (position == -1) 735 return -1; 736 737 if (document.getChar(position) == CLOSING_PAREN) 738 depth++; 739 else 740 depth--; 741 742 if (depth == 0) 743 return position; 744 } 745 746 } catch (BadLocationException e) { 747 return -1; 748 } 749 } 750 751 761 private static boolean looksLikeIfWhileForCatch(IDocument document, int position, String partitioning) { 762 position= firstNonWhitespaceBackward(document, position, partitioning, -1); 763 if (position == -1) 764 return false; 765 766 return looksLike(document, position, "if") || looksLike(document, position, "while") || looksLike(document, position, "catch") || looksLike(document, position, "synchronized") || looksLike(document, position, "switch") || looksLike(document, position, "for"); } 773 774 786 private static boolean looksLike(IDocument document, int position, String like) { 787 int length= like.length(); 788 if (position < length - 1) 789 return false; 790 791 try { 792 if (!like.equals(document.get(position - length + 1, length))) 793 return false; 794 795 if (position >= length && Character.isJavaIdentifierPart(like.charAt(0)) && Character.isJavaIdentifierPart(document.getChar(position - length))) 796 return false; 797 798 } catch (BadLocationException e) { 799 return false; 800 } 801 802 return true; 803 } 804 805 815 private static boolean looksLikeMethodDecl(IDocument document, int position, String partitioning) { 816 817 position= eatIdentToLeft(document, position, partitioning); 819 if (position < 1) 820 return false; 821 822 position= eatBrackets(document, position - 1, partitioning); 823 if (position < 1) 824 return false; 825 826 position= eatIdentToLeft(document, position - 1, partitioning); 827 828 return position != -1; 829 } 830 831 842 private static int eatBrackets(IDocument document, int position, String partitioning) { 843 int pos= firstNonWhitespaceBackward(document, position, partitioning, -1); 845 try { 846 if (pos > 1 && document.getChar(pos) == ']') { 847 pos= firstNonWhitespaceBackward(document, pos - 1, partitioning, -1); 848 if (pos > 0 && document.getChar(pos) == '[') 849 return pos; 850 } 851 } catch (BadLocationException e) { 852 } 854 return position; 855 } 856 857 869 private static int eatIdentToLeft(IDocument document, int position, String partitioning) { 870 if (position < 0) 871 return -1; 872 Assert.isTrue(position < document.getLength()); 873 874 int p= firstNonWhitespaceBackward(document, position, partitioning, -1); 875 if (p == -1) 876 return -1; 877 878 try { 879 while (p >= 0) { 880 881 char ch= document.getChar(p); 882 if (Character.isJavaIdentifierPart(ch)) { 883 p--; 884 continue; 885 } 886 887 if (Character.isWhitespace(ch) && p != position) 889 return p + 1; 890 else 891 return -1; 892 893 } 894 895 return 0; 897 898 } catch (BadLocationException e) { 899 } 900 return -1; 901 } 902 903 913 private static int nextPartitionOrLineEnd(IDocument document, ITextSelection line, int offset, String partitioning) { 914 final int docOffset= offset + line.getOffset(); 916 final int eol= line.getOffset() + line.getLength(); 917 int nextPartitionPos= eol; int validPosition= docOffset; 919 920 try { 921 ITypedRegion partition= TextUtilities.getPartition(document, partitioning, nextPartitionPos, true); 922 validPosition= getValidPositionForPartition(document, partition, eol); 923 while (validPosition == -1) { 924 nextPartitionPos= partition.getOffset() - 1; 925 if (nextPartitionPos < docOffset) { 926 validPosition= docOffset; 927 break; 928 } 929 partition= TextUtilities.getPartition(document, partitioning, nextPartitionPos, false); 930 validPosition= getValidPositionForPartition(document, partition, eol); 931 } 932 } catch (BadLocationException e) { 933 } 934 935 validPosition= Math.max(validPosition, docOffset); 936 validPosition -= line.getOffset(); 938 return validPosition; 939 } 940 941 953 private static int getValidPositionForPartition(IDocument doc, ITypedRegion partition, int maxOffset) { 954 final int INVALID= -1; 955 956 if (IJavaPartitions.JAVA_DOC.equals(partition.getType())) 957 return INVALID; 958 if (IJavaPartitions.JAVA_MULTI_LINE_COMMENT.equals(partition.getType())) 959 return INVALID; 960 if (IJavaPartitions.JAVA_SINGLE_LINE_COMMENT.equals(partition.getType())) 961 return INVALID; 962 963 int endOffset= Math.min(maxOffset, partition.getOffset() + partition.getLength()); 964 965 if (IJavaPartitions.JAVA_CHARACTER.equals(partition.getType())) 966 return endOffset; 967 if (IJavaPartitions.JAVA_STRING.equals(partition.getType())) 968 return endOffset; 969 if (IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType())) { 970 try { 971 if (doc.get(partition.getOffset(), endOffset - partition.getOffset()).trim().length() == 0) 972 return INVALID; 973 else 974 return endOffset; 975 } catch (BadLocationException e) { 976 return INVALID; 977 } 978 } 979 return endOffset; 981 } 982 983 992 private static boolean isForStatement(String line, int offset) { 993 994 int forPos= line.indexOf("for"); if (forPos != -1) { 996 if ((forPos == 0 || !Character.isJavaIdentifierPart(line.charAt(forPos - 1))) && (line.length() == forPos + 3 || !Character.isJavaIdentifierPart(line.charAt(forPos + 3)))) 997 return true; 998 } 999 return false; 1000 } 1001 1002 1010 private static int startOfWhitespaceBeforeOffset(String text, int offset) { 1011 int i= Math.min(offset, text.length()); 1012 for (; i >= 1; i--) { 1013 if (!Character.isWhitespace(text.charAt(i - 1))) 1014 break; 1015 } 1016 return i; 1017 } 1018} 1019 | Popular Tags |