1 /*******************************************************************************
2 * Copyright (c) 2000, 2011 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
9 * IBM Corporation - initial API and implementation
10 * Nikolay Metchev - Fixed https://bugs.eclipse.org/bugs/show_bug.cgi?id=29909
11 * Tom Eicher (Avaloq Evolution AG) - block selection mode
12 *******************************************************************************/
13 package org.eclipse.jdt.internal.ui.text.java;
15 import org.eclipse.core.runtime.Assert;
17 import org.eclipse.jface.preference.IPreferenceStore;
19 import org.eclipse.jface.text.BadLocationException;
20 import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
21 import org.eclipse.jface.text.Document;
22 import org.eclipse.jface.text.DocumentCommand;
23 import org.eclipse.jface.text.DocumentRewriteSession;
24 import org.eclipse.jface.text.DocumentRewriteSessionType;
25 import org.eclipse.jface.text.IDocument;
26 import org.eclipse.jface.text.IRegion;
27 import org.eclipse.jface.text.ITypedRegion;
28 import org.eclipse.jface.text.Region;
29 import org.eclipse.jface.text.TextUtilities;
30 import org.eclipse.jface.text.rules.FastPartitioner;
31 import org.eclipse.jface.text.source.ISourceViewer;
33 import org.eclipse.ui.IEditorPart;
34 import org.eclipse.ui.IWorkbenchPage;
36 import org.eclipse.ui.texteditor.ITextEditorExtension3;
38 import org.eclipse.jdt.core.IJavaProject;
39 import org.eclipse.jdt.core.JavaCore;
40 import org.eclipse.jdt.core.ToolFactory;
41 import org.eclipse.jdt.core.compiler.IProblem;
42 import org.eclipse.jdt.core.compiler.IScanner;
43 import org.eclipse.jdt.core.compiler.ITerminalSymbols;
44 import org.eclipse.jdt.core.compiler.InvalidInputException;
45 import org.eclipse.jdt.core.dom.ASTNode;
46 import org.eclipse.jdt.core.dom.ASTParser;
47 import org.eclipse.jdt.core.dom.CompilationUnit;
48 import org.eclipse.jdt.core.dom.DoStatement;
49 import org.eclipse.jdt.core.dom.Expression;
50 import org.eclipse.jdt.core.dom.ForStatement;
51 import org.eclipse.jdt.core.dom.IfStatement;
52 import org.eclipse.jdt.core.dom.NodeFinder;
53 import org.eclipse.jdt.core.dom.Statement;
54 import org.eclipse.jdt.core.dom.WhileStatement;
55 import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
57 import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
59 import org.eclipse.jdt.ui.PreferenceConstants;
60 import org.eclipse.jdt.ui.text.IJavaPartitions;
62 import org.eclipse.jdt.internal.ui.JavaPlugin;
63 import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider;
64 import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
65 import org.eclipse.jdt.internal.ui.text.FastJavaPartitionScanner;
66 import org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner;
67 import org.eclipse.jdt.internal.ui.text.JavaIndenter;
68 import org.eclipse.jdt.internal.ui.text.Symbols;
72 * Auto indent strategy sensitive to brackets.
74 public class JavaAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy {
76 /** The line comment introducer. Value is "{@value}" */
77 private static final String LINE_COMMENT= "//"; //$NON-NLS-1$
79 private static class CompilationUnitInfo {
84 CompilationUnitInfo(char[] buffer, int delta) {
91 private boolean fCloseBrace;
92 private boolean fIsSmartMode;
93 private boolean fIsSmartTab;
94 private boolean fIsSmartIndentAfterNewline;
96 private String fPartitioning;
97 private final IJavaProject fProject;
98 private static IScanner fgScanner= ToolFactory.createScanner(false, false, false, false);
103 private final ISourceViewer fViewer;
106 * Creates a new Java auto indent strategy for the given document partitioning.
108 * @param partitioning the document partitioning
109 * @param project the project to get formatting preferences from, or null to use default preferences
110 * @param viewer the source viewer that this strategy is attached to
112 public JavaAutoIndentStrategy(String partitioning, IJavaProject project, ISourceViewer viewer) {
113 fPartitioning= partitioning;
118 private int getBracketCount(IDocument d, int startOffset, int endOffset, boolean ignoreCloseBrackets) throws BadLocationException {
121 while (startOffset < endOffset) {
122 char curr= d.getChar(startOffset);
126 if (startOffset < endOffset) {
127 char next= d.getChar(startOffset);
129 // a comment starts, advance to the comment end
130 startOffset= getCommentEnd(d, startOffset + 1, endOffset);
131 } else if (next == '/') {
132 // '//'-comment: nothing to do anymore on this line
133 startOffset= endOffset;
138 if (startOffset < endOffset) {
139 char next= d.getChar(startOffset);
141 // we have been in a comment: forget what we read before
149 ignoreCloseBrackets= false;
152 if (!ignoreCloseBrackets) {
158 startOffset= getStringEnd(d, startOffset, endOffset, curr);
166 // ----------- bracket counting ------------------------------------------------------
168 private int getCommentEnd(IDocument d, int offset, int endOffset) throws BadLocationException {
169 while (offset < endOffset) {
170 char curr= d.getChar(offset);
173 if (offset < endOffset && d.getChar(offset) == '/') {
181 private String getIndentOfLine(IDocument d, int line) throws BadLocationException {
183 int start= d.getLineOffset(line);
184 int end= start + d.getLineLength(line) - 1;
185 int whiteEnd= findEndOfWhiteSpace(d, start, end);
186 return d.get(start, whiteEnd - start);
188 return ""; //$NON-NLS-1$
192 private int getStringEnd(IDocument d, int offset, int endOffset, char ch) throws BadLocationException {
193 while (offset < endOffset) {
194 char curr= d.getChar(offset);
197 // ignore escaped characters
199 } else if (curr == ch) {
206 private void smartIndentAfterClosingBracket(IDocument d, DocumentCommand c) {
207 if (c.offset == -1 || d.getLength() == 0)
211 int p= (c.offset == d.getLength() ? c.offset - 1 : c.offset);
212 int line= d.getLineOfOffset(p);
213 int start= d.getLineOffset(line);
214 int whiteend= findEndOfWhiteSpace(d, start, c.offset);
216 JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
217 JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
219 // shift only when line does not contain any text up to the closing bracket
220 if (whiteend == c.offset) {
221 // evaluate the line with the opening bracket that matches out closing bracket
222 int reference= indenter.findReferencePosition(c.offset, false, true, false, false);
223 int indLine= d.getLineOfOffset(reference);
224 if (indLine != -1 && indLine != line) {
225 // take the indent of the found line
226 StringBuffer replaceText= new StringBuffer(getIndentOfLine(d, indLine));
227 // add the rest of the current line including the just added close bracket
228 replaceText.append(d.get(whiteend, c.offset - whiteend));
229 replaceText.append(c.text);
230 // modify document command
231 c.length += c.offset - start;
233 c.text= replaceText.toString();
236 } catch (BadLocationException e) {
241 private void smartIndentAfterOpeningBracket(IDocument d, DocumentCommand c) {
242 if (c.offset < 1 || d.getLength() == 0)
245 JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
247 int p= (c.offset == d.getLength() ? c.offset - 1 : c.offset);
251 int line= d.getLineOfOffset(p);
252 int lineOffset= d.getLineOffset(line);
254 // make sure we don't have any leading comments etc.
255 if (d.get(lineOffset, p - lineOffset).trim().length() != 0)
258 // line of last Java code
259 int pos= scanner.findNonWhitespaceBackward(p, JavaHeuristicScanner.UNBOUND);
262 int lastLine= d.getLineOfOffset(pos);
264 // only shift if the last java line is further up and is a braceless block candidate
265 if (lastLine < line) {
267 JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
268 StringBuffer indent= indenter.computeIndentation(p, true);
269 String toDelete= d.get(lineOffset, c.offset - lineOffset);
270 if (indent != null && !indent.toString().equals(toDelete)) {
271 c.text= indent.append(c.text).toString();
272 c.length += c.offset - lineOffset;
273 c.offset= lineOffset;
277 } catch (BadLocationException e) {
283 private void smartIndentAfterNewLine(IDocument d, DocumentCommand c) {
284 JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
285 JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
286 StringBuffer indent= indenter.computeIndentation(c.offset);
288 indent= new StringBuffer();
290 int docLength= d.getLength();
291 if (c.offset == -1 || docLength == 0)
295 int p= (c.offset == docLength ? c.offset - 1 : c.offset);
296 int line= d.getLineOfOffset(p);
298 StringBuffer buf= new StringBuffer(c.text + indent);
301 IRegion reg= d.getLineInformation(line);
302 int lineEnd= reg.getOffset() + reg.getLength();
304 int contentStart= findEndOfWhiteSpace(d, c.offset, lineEnd);
305 c.length= Math.max(contentStart - c.offset, 0);
307 int start= reg.getOffset();
308 ITypedRegion region= TextUtilities.getPartition(d, fPartitioning, start, true);
309 if (IJavaPartitions.JAVA_DOC.equals(region.getType()))
310 start= d.getLineInformationOfOffset(region.getOffset()).getOffset();
312 // insert closing brace on new line after an unclosed opening brace
313 if (getBracketCount(d, start, c.offset, true) > 0 && closeBrace() && !isClosed(d, c.offset, c.length)) {
314 c.caretOffset= c.offset + buf.length();
315 c.shiftsCaret= false;
317 // copy old content of line behind insertion point to new line
318 // unless we think we are inserting an anonymous type definition
320 if (c.offset == 0 || computeAnonymousPosition(d, c.offset - 1, fPartitioning, lineEnd) == -1) {
321 if (lineEnd - contentStart > 0) {
322 c.length= lineEnd - c.offset;
323 buf.append(d.get(contentStart, lineEnd - contentStart).toCharArray());
327 buf.append(TextUtilities.getDefaultLineDelimiter(d));
328 StringBuffer reference= null;
329 int nonWS= findEndOfWhiteSpace(d, start, lineEnd);
330 if (nonWS < c.offset && d.getChar(nonWS) == '{')
331 reference= new StringBuffer(d.get(start, nonWS - start));
333 reference= indenter.getReferenceIndentation(c.offset);
334 if (reference != null)
335 buf.append(reference);
338 // insert extra line upon new line between two braces
339 else if (c.offset > start && contentStart < lineEnd && d.getChar(contentStart) == '}') {
340 int firstCharPos= scanner.findNonWhitespaceBackward(c.offset - 1, start);
341 if (firstCharPos != JavaHeuristicScanner.NOT_FOUND && d.getChar(firstCharPos) == '{') {
342 c.caretOffset= c.offset + buf.length();
343 c.shiftsCaret= false;
345 StringBuffer reference= null;
346 int nonWS= findEndOfWhiteSpace(d, start, lineEnd);
347 if (nonWS < c.offset && d.getChar(nonWS) == '{')
348 reference= new StringBuffer(d.get(start, nonWS - start));
350 reference= indenter.getReferenceIndentation(c.offset);
352 buf.append(TextUtilities.getDefaultLineDelimiter(d));
354 if (reference != null)
355 buf.append(reference);
358 c.text= buf.toString();
360 } catch (BadLocationException e) {
366 * Computes an insert position for an opening brace if <code>offset</code> maps to a position in
367 * <code>document</code> with a expression in parenthesis that will take a block after the closing parenthesis.
369 * @param document the document being modified
370 * @param offset the offset of the caret position, relative to the line start.
371 * @param partitioning the document partitioning
372 * @param max the max position
373 * @return an insert position relative to the line start if <code>line</code> contains a parenthesized expression that can be followed by a block, -1 otherwise
375 private static int computeAnonymousPosition(IDocument document, int offset, String partitioning, int max) {
376 // find the opening parenthesis for every closing parenthesis on the current line after offset
377 // return the position behind the closing parenthesis if it looks like a method declaration
378 // or an expression for an if, while, for, catch statement
380 JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
383 int scanTo= scanner.scanForward(pos, length, '}');
387 int closingParen= findClosingParenToLeft(scanner, pos) - 1;
388 boolean hasNewToken= looksLikeAnonymousClassDef(document, partitioning, scanner, pos);
389 int openingParen= -1;
391 int startScan= closingParen + 1;
392 closingParen= scanner.scanForward(startScan, scanTo, ')');
393 if (closingParen == -1) {
394 if (hasNewToken && openingParen != -1)
395 return openingParen + 1;
399 openingParen= scanner.findOpeningPeer(closingParen - 1, '(', ')');
401 // no way an expression at the beginning of the document can mean anything
402 if (openingParen < 1)
405 // only select insert positions for parenthesis currently embracing the caret
406 if (openingParen > pos)
409 if (looksLikeAnonymousClassDef(document, partitioning, scanner, openingParen - 1))
410 return closingParen + 1;
418 * Finds a closing parenthesis to the left of <code>position</code> in document, where that parenthesis is only
419 * separated by whitespace from <code>position</code>. If no such parenthesis can be found, <code>position</code> is returned.
421 * @param scanner the java heuristic scanner set up on the document
422 * @param position the first character position in <code>document</code> to be considered
423 * @return the position of a closing parenthesis left to <code>position</code> separated only by whitespace, or <code>position</code> if no parenthesis can be found
425 private static int findClosingParenToLeft(JavaHeuristicScanner scanner, int position) {
429 if (scanner.previousToken(position - 1, JavaHeuristicScanner.UNBOUND) == Symbols.TokenRPAREN)
430 return scanner.getPosition() + 1;
435 * Checks whether the content of <code>document</code> in the range (<code>offset</code>, <code>length</code>)
436 * contains the <code>new</code> keyword.
438 * @param document the document being modified
439 * @param offset the first character position in <code>document</code> to be considered
440 * @param length the length of the character range to be considered
441 * @param partitioning the document partitioning
442 * @return <code>true</code> if the specified character range contains a <code>new</code> keyword, <code>false</code> otherwise.
444 private static boolean isNewMatch(IDocument document, int offset, int length, String partitioning) {
445 Assert.isTrue(length >= 0);
446 Assert.isTrue(offset >= 0);
447 Assert.isTrue(offset + length < document.getLength() + 1);
450 String text= document.get(offset, length);
451 int pos= text.indexOf("new"); //$NON-NLS-1$
453 while (pos != -1 && !isDefaultPartition(document, pos + offset, partitioning))
454 pos= text.indexOf("new", pos + 2); //$NON-NLS-1$
459 if (pos != 0 && Character.isJavaIdentifierPart(text.charAt(pos - 1)))
462 if (pos + 3 < length && Character.isJavaIdentifierPart(text.charAt(pos + 3)))
467 } catch (BadLocationException e) {
473 * Checks whether the content of <code>document</code> at <code>position</code> looks like an
474 * anonymous class definition. <code>position</code> must be to the left of the opening
475 * parenthesis of the definition's parameter list.
477 * @param document the document being modified
478 * @param partitioning the document partitioning
479 * @param scanner the scanner
480 * @param position the first character position in <code>document</code> to be considered
481 * @return <code>true</code> if the content of <code>document</code> looks like an anonymous class definition, <code>false</code> otherwise
483 private static boolean looksLikeAnonymousClassDef(IDocument document, String partitioning, JavaHeuristicScanner scanner, int position) {
484 int previousCommaParenEqual= scanner.scanBackward(position - 1, JavaHeuristicScanner.UNBOUND, new char[] {',', '(', '='});
485 if (previousCommaParenEqual == -1 || position < previousCommaParenEqual + 5) // 2 for borders, 3 for "new"
488 if (isNewMatch(document, previousCommaParenEqual + 1, position - previousCommaParenEqual - 2, partitioning))
495 * Checks whether <code>position</code> resides in a default (Java) partition of <code>document</code>.
497 * @param document the document being modified
498 * @param position the position to be checked
499 * @param partitioning the document partitioning
500 * @return <code>true</code> if <code>position</code> is in the default partition of <code>document</code>, <code>false</code> otherwise
502 private static boolean isDefaultPartition(IDocument document, int position, String partitioning) {
503 Assert.isTrue(position >= 0);
504 Assert.isTrue(position <= document.getLength());
507 ITypedRegion region= TextUtilities.getPartition(document, partitioning, position, false);
508 return region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE);
510 } catch (BadLocationException e) {
516 private boolean isClosed(IDocument document, int offset, int length) {
518 CompilationUnitInfo info= getCompilationUnitForMethod(document, offset);
522 CompilationUnit compilationUnit= null;
524 ASTParser parser= ASTParser.newParser(ASTProvider.SHARED_AST_LEVEL);
525 parser.setSource(info.buffer);
526 compilationUnit= (CompilationUnit) parser.createAST(null);
527 } catch (ArrayIndexOutOfBoundsException x) {
528 // work around for parser problem
532 IProblem[] problems= compilationUnit.getProblems();
533 for (int i= 0; i != problems.length; ++i) {
534 if (problems[i].getID() == IProblem.UnmatchedBracket)
538 final int relativeOffset= offset - info.delta;
540 ASTNode node= NodeFinder.perform(compilationUnit, relativeOffset, length);
543 while (node != null && (relativeOffset == node.getStartPosition() || relativeOffset == node.getStartPosition() + node.getLength()))
544 node= node.getParent();
550 switch (node.getNodeType()) {
552 return getBlockBalance(document, offset, fPartitioning) <= 0;
554 case ASTNode.IF_STATEMENT:
556 IfStatement ifStatement= (IfStatement) node;
557 Expression expression= ifStatement.getExpression();
558 IRegion expressionRegion= createRegion(expression, info.delta);
559 Statement thenStatement= ifStatement.getThenStatement();
560 IRegion thenRegion= createRegion(thenStatement, info.delta);
562 // between expression and then statement
563 if (expressionRegion.getOffset() + expressionRegion.getLength() <= offset && offset + length <= thenRegion.getOffset())
564 return thenStatement != null;
566 Statement elseStatement= ifStatement.getElseStatement();
567 IRegion elseRegion= createRegion(elseStatement, info.delta);
569 if (elseStatement != null) {
570 int sourceOffset= thenRegion.getOffset() + thenRegion.getLength();
571 int sourceLength= elseRegion.getOffset() - sourceOffset;
572 IRegion elseToken= getToken(document, new Region(sourceOffset, sourceLength), ITerminalSymbols.TokenNameelse);
573 return elseToken != null && elseToken.getOffset() + elseToken.getLength() <= offset && offset + length < elseRegion.getOffset();
578 case ASTNode.WHILE_STATEMENT:
579 case ASTNode.FOR_STATEMENT:
581 Expression expression= node.getNodeType() == ASTNode.WHILE_STATEMENT ? ((WhileStatement) node).getExpression() : ((ForStatement) node).getExpression();
582 IRegion expressionRegion= createRegion(expression, info.delta);
583 Statement body= node.getNodeType() == ASTNode.WHILE_STATEMENT ? ((WhileStatement) node).getBody() : ((ForStatement) node).getBody();
584 IRegion bodyRegion= createRegion(body, info.delta);
586 // between expression and body statement
587 if (expressionRegion.getOffset() + expressionRegion.getLength() <= offset && offset + length <= bodyRegion.getOffset())
592 case ASTNode.DO_STATEMENT:
594 DoStatement doStatement= (DoStatement) node;
595 IRegion doRegion= createRegion(doStatement, info.delta);
596 Statement body= doStatement.getBody();
597 IRegion bodyRegion= createRegion(body, info.delta);
599 if (doRegion.getOffset() + doRegion.getLength() <= offset && offset + length <= bodyRegion.getOffset())
609 * Installs a java partitioner with <code>document</code>.
611 * @param document the document
613 private static void installJavaStuff(Document document) {
614 String[] types= new String[] {
615 IJavaPartitions.JAVA_DOC,
616 IJavaPartitions.JAVA_MULTI_LINE_COMMENT,
617 IJavaPartitions.JAVA_SINGLE_LINE_COMMENT,
618 IJavaPartitions.JAVA_STRING,
619 IJavaPartitions.JAVA_CHARACTER,
620 IDocument.DEFAULT_CONTENT_TYPE
622 FastPartitioner partitioner= new FastPartitioner(new FastJavaPartitionScanner(), types);
623 partitioner.connect(document);
624 document.setDocumentPartitioner(IJavaPartitions.JAVA_PARTITIONING, partitioner);
628 * Installs a java partitioner with <code>document</code>.
630 * @param document the document
632 private static void removeJavaStuff(Document document) {
633 document.setDocumentPartitioner(IJavaPartitions.JAVA_PARTITIONING, null);
636 private void smartPaste(IDocument document, DocumentCommand command) {
637 int newOffset= command.offset;
638 int newLength= command.length;
639 String newText= command.text;
642 JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
643 JavaIndenter indenter= new JavaIndenter(document, scanner, fProject);
644 int offset= newOffset;
646 // reference position to get the indent from
647 int refOffset= indenter.findReferencePosition(offset);
648 if (refOffset == JavaHeuristicScanner.NOT_FOUND)
650 int peerOffset= getPeerPosition(document, command);
651 peerOffset= indenter.findReferencePosition(peerOffset);
652 if (peerOffset != JavaHeuristicScanner.NOT_FOUND)
653 refOffset= Math.min(refOffset, peerOffset);
655 // eat any WS before the insertion to the beginning of the line
656 int firstLine= 1; // don't format the first line per default, as it has other content before it
657 IRegion line= document.getLineInformationOfOffset(offset);
658 String notSelected= document.get(line.getOffset(), offset - line.getOffset());
659 if (notSelected.trim().length() == 0) {
660 newLength += notSelected.length();
661 newOffset= line.getOffset();
665 // prefix: the part we need for formatting but won't paste
666 IRegion refLine= document.getLineInformationOfOffset(refOffset);
667 String prefix= document.get(refLine.getOffset(), newOffset - refLine.getOffset());
669 // handle the indentation computation inside a temporary document
670 Document temp= new Document(prefix + newText);
671 DocumentRewriteSession session= temp.startRewriteSession(DocumentRewriteSessionType.STRICTLY_SEQUENTIAL);
672 scanner= new JavaHeuristicScanner(temp);
673 indenter= new JavaIndenter(temp, scanner, fProject);
674 installJavaStuff(temp);
676 // indent the first and second line
677 // compute the relative indentation difference from the second line
678 // (as the first might be partially selected) and use the value to
679 // indent all other lines.
680 boolean isIndentDetected= false;
681 StringBuffer addition= new StringBuffer();
683 int firstLineInsertLength= 0;
684 int firstLineIndent= 0;
685 int first= document.computeNumberOfLines(prefix) + firstLine; // don't format first line
686 int lines= temp.getNumberOfLines();
687 int tabLength= getVisualTabLengthPreference();
688 boolean changed= false;
689 for (int l= first; l < lines; l++) { // we don't change the number of lines while adding indents
691 IRegion r= temp.getLineInformation(l);
692 int lineOffset= r.getOffset();
693 int lineLength= r.getLength();
695 if (lineLength == 0) // don't modify empty lines
698 if (!isIndentDetected) {
700 // indent the first pasted line
701 String current= getCurrentIndent(temp, l);
702 StringBuffer correct= indenter.computeIndentation(lineOffset);
706 insertLength= subtractIndent(correct, current, addition, tabLength);
708 firstLineInsertLength= insertLength;
709 firstLineIndent= current.length();
711 if (l != first && temp.get(lineOffset, lineLength).trim().length() != 0) {
712 isIndentDetected= true;
713 if (firstLineIndent >= current.length())
714 insertLength= firstLineInsertLength;
715 if (insertLength == 0) {
716 // no adjustment needed, bail out
717 if (firstLine == 0) {
718 // but we still need to adjust the first line
719 command.offset= newOffset;
720 command.length= newLength;
722 break; // still need to get the leading indent of the first line
727 changed= insertLength != 0;
731 // relatively indent all pasted lines
732 if (insertLength > 0)
733 addIndent(temp, l, addition, tabLength);
734 else if (insertLength < 0)
735 cutIndent(temp, l, -insertLength, tabLength);
739 removeJavaStuff(temp);
740 temp.stopRewriteSession(session);
741 newText= temp.get(prefix.length(), temp.getLength() - prefix.length());
743 command.offset= newOffset;
744 command.length= newLength;
745 command.text= newText;
747 } catch (BadLocationException e) {
754 * Returns the indentation of the line <code>line</code> in <code>document</code>.
755 * The returned string may contain pairs of leading slashes that are considered
756 * part of the indentation. The space before the asterisk in a javadoc-like
757 * comment is not considered part of the indentation.
759 * @param document the document
760 * @param line the line
761 * @return the indentation of <code>line</code> in <code>document</code>
762 * @throws BadLocationException if the document is changed concurrently
764 private static String getCurrentIndent(Document document, int line) throws BadLocationException {
765 IRegion region= document.getLineInformation(line);
766 int from= region.getOffset();
767 int endOffset= region.getOffset() + region.getLength();
769 // go behind line comments
771 while (to < endOffset - 2 && document.get(to, 2).equals(LINE_COMMENT))
774 while (to < endOffset) {
775 char ch= document.getChar(to);
776 if (!Character.isWhitespace(ch))
781 // don't count the space before javadoc like, asterisk-style comment lines
782 if (to > from && to < endOffset - 1 && document.get(to - 1, 2).equals(" *")) { //$NON-NLS-1$
783 String type= TextUtilities.getContentType(document, IJavaPartitions.JAVA_PARTITIONING, to, true);
784 if (type.equals(IJavaPartitions.JAVA_DOC) || type.equals(IJavaPartitions.JAVA_MULTI_LINE_COMMENT))
788 return document.get(from, to - from);
792 * Computes the difference of two indentations and returns the difference in
793 * length of current and correct. If the return value is positive, <code>addition</code>
794 * is initialized with a substring of that length of <code>correct</code>.
796 * @param correct the correct indentation
797 * @param current the current indentation (might contain non-whitespace)
798 * @param difference a string buffer - if the return value is positive, it will be cleared and set to the substring of <code>current</code> of that length
799 * @param tabLength the length of a tab
800 * @return the difference in length of <code>correct</code> and <code>current</code>
802 private int subtractIndent(CharSequence correct, CharSequence current, StringBuffer difference, int tabLength) {
803 int c1= computeVisualLength(correct, tabLength);
804 int c2= computeVisualLength(current, tabLength);
809 difference.setLength(0);
812 char c= correct.charAt(i++);
813 difference.append(c);
814 len += computeVisualLength(c, tabLength);
822 * Indents line <code>line</code> in <code>document</code> with <code>indent</code>.
823 * Leaves leading comment signs alone.
825 * @param document the document
826 * @param line the line
827 * @param indent the indentation to insert
828 * @param tabLength the length of a tab
829 * @throws BadLocationException on concurrent document modification
831 private void addIndent(Document document, int line, CharSequence indent, int tabLength) throws BadLocationException {
832 IRegion region= document.getLineInformation(line);
833 int insert= region.getOffset();
834 int endOffset= region.getOffset() + region.getLength();
836 // Compute insert after all leading line comment markers
837 int newInsert= insert;
838 while (newInsert < endOffset - 2 && document.get(newInsert, 2).equals(LINE_COMMENT))
841 // Heuristic to check whether it is commented code or just a comment
842 if (newInsert > insert) {
843 int whitespaceCount= 0;
845 while (i < endOffset - 1) {
846 char ch= document.get(i, 1).charAt(0);
847 if (!Character.isWhitespace(ch))
849 whitespaceCount= whitespaceCount + computeVisualLength(ch, tabLength);
853 if (whitespaceCount != 0 && whitespaceCount >= CodeFormatterUtil.getIndentWidth(fProject))
858 document.replace(insert, 0, indent.toString());
862 * Cuts the visual equivalent of <code>toDelete</code> characters out of the
863 * indentation of line <code>line</code> in <code>document</code>. Leaves
864 * leading comment signs alone.
866 * @param document the document
867 * @param line the line
868 * @param toDelete the number of space equivalents to delete
869 * @param tabLength the length of a tab
870 * @throws BadLocationException on concurrent document modification
872 private void cutIndent(Document document, int line, int toDelete, int tabLength) throws BadLocationException {
873 IRegion region= document.getLineInformation(line);
874 int from= region.getOffset();
875 int endOffset= region.getOffset() + region.getLength();
877 // go behind line comments
878 while (from < endOffset - 2 && document.get(from, 2).equals(LINE_COMMENT))
882 while (toDelete > 0 && to < endOffset) {
883 char ch= document.getChar(to);
884 if (!Character.isWhitespace(ch))
886 toDelete -= computeVisualLength(ch, tabLength);
893 document.replace(from, to - from, ""); //$NON-NLS-1$
897 * Returns the visual length of a given <code>CharSequence</code> taking into
898 * account the visual tabulator length.
900 * @param seq the string to measure
901 * @param tabLength the length of a tab
902 * @return the visual length of <code>seq</code>
904 private int computeVisualLength(CharSequence seq, int tabLength) {
907 for (int i= 0; i < seq.length(); i++) {
908 char ch= seq.charAt(i);
911 size += tabLength - size % tabLength;
912 // else: size stays the same
921 * Returns the visual length of a given character taking into
922 * account the visual tabulator length.
924 * @param ch the character to measure
925 * @param tabLength the length of a tab
926 * @return the visual length of <code>ch</code>
928 private int computeVisualLength(char ch, int tabLength) {
936 * The preference setting for the visual tabulator display.
938 * @return the number of spaces displayed for a tabulator in the editor
940 private int getVisualTabLengthPreference() {
941 return CodeFormatterUtil.getTabWidth(fProject);
945 * The preference setting that tells whether to insert spaces when pressing the Tab key.
947 * @return <code>true</code> if spaces are inserted when pressing the Tab key
950 private boolean isInsertingSpacesForTab() {
951 return JavaCore.SPACE.equals(getCoreOption(fProject, DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR));
955 * Returns the possibly <code>project</code>-specific core preference defined under
958 * @param project the project to get the preference from, or <code>null</code> to get the global
960 * @param key the key of the preference
961 * @return the value of the preference
964 private static String getCoreOption(IJavaProject project, String key) {
966 return JavaCore.getOption(key);
967 return project.getOption(key, true);
970 private int getPeerPosition(IDocument document, DocumentCommand command) {
971 if (document.getLength() == 0)
974 * Search for scope closers in the pasted text and find their opening peers
977 Document pasted= new Document(command.text);
978 installJavaStuff(pasted);
979 int firstPeer= command.offset;
981 JavaHeuristicScanner pScanner= new JavaHeuristicScanner(pasted);
982 JavaHeuristicScanner dScanner= new JavaHeuristicScanner(document);
984 // add scope relevant after context to peer search
985 int afterToken= dScanner.nextToken(command.offset + command.length, JavaHeuristicScanner.UNBOUND);
987 switch (afterToken) {
988 case Symbols.TokenRBRACE:
989 pasted.replace(pasted.getLength(), 0, "}"); //$NON-NLS-1$
991 case Symbols.TokenRPAREN:
992 pasted.replace(pasted.getLength(), 0, ")"); //$NON-NLS-1$
994 case Symbols.TokenRBRACKET:
995 pasted.replace(pasted.getLength(), 0, "]"); //$NON-NLS-1$
998 } catch (BadLocationException e) {
1000 Assert.isTrue(false);
1003 int pPos= 0; // paste text position (increasing from 0)
1004 int dPos= Math.max(0, command.offset - 1); // document position (decreasing from paste offset)
1006 int token= pScanner.nextToken(pPos, JavaHeuristicScanner.UNBOUND);
1007 pPos= pScanner.getPosition();
1009 case Symbols.TokenLBRACE:
1010 case Symbols.TokenLBRACKET:
1011 case Symbols.TokenLPAREN:
1012 pPos= skipScope(pScanner, pPos, token);
1013 if (pPos == JavaHeuristicScanner.NOT_FOUND)
1015 break; // closed scope -> keep searching
1016 case Symbols.TokenRBRACE:
1017 int peer= dScanner.findOpeningPeer(dPos, '{', '}');
1019 if (peer == JavaHeuristicScanner.NOT_FOUND)
1022 break; // keep searching
1023 case Symbols.TokenRBRACKET:
1024 peer= dScanner.findOpeningPeer(dPos, '[', ']');
1026 if (peer == JavaHeuristicScanner.NOT_FOUND)
1029 break; // keep searching
1030 case Symbols.TokenRPAREN:
1031 peer= dScanner.findOpeningPeer(dPos, '(', ')');
1033 if (peer == JavaHeuristicScanner.NOT_FOUND)
1036 break; // keep searching
1037 case Symbols.TokenCASE:
1038 case Symbols.TokenDEFAULT:
1039 JavaIndenter indenter= new JavaIndenter(document, dScanner, fProject);
1040 peer= indenter.findReferencePosition(dPos, false, false, false, true);
1041 if (peer == JavaHeuristicScanner.NOT_FOUND)
1044 break; // keep searching
1046 case Symbols.TokenEOF:
1055 * Skips the scope opened by <code>token</code>.
1057 * @param scanner the scanner
1058 * @param start the start position
1059 * @param token the token
1060 * @return the position after the scope or <code>JavaHeuristicScanner.NOT_FOUND</code>
1062 private static int skipScope(JavaHeuristicScanner scanner, int start, int token) {
1063 int openToken= token;
1066 case Symbols.TokenLPAREN:
1067 closeToken= Symbols.TokenRPAREN;
1069 case Symbols.TokenLBRACKET:
1070 closeToken= Symbols.TokenRBRACKET;
1072 case Symbols.TokenLBRACE:
1073 closeToken= Symbols.TokenRBRACE;
1076 Assert.isTrue(false);
1084 int tok= scanner.nextToken(p, JavaHeuristicScanner.UNBOUND);
1085 p= scanner.getPosition();
1087 if (tok == openToken) {
1089 } else if (tok == closeToken) {
1093 } else if (tok == Symbols.TokenEOF) {
1094 return JavaHeuristicScanner.NOT_FOUND;
1099 private boolean isLineDelimiter(IDocument document, String text) {
1100 String[] delimiters= document.getLegalLineDelimiters();
1101 if (delimiters != null)
1102 return TextUtilities.equals(delimiters, text) > -1;
1106 private void smartIndentOnKeypress(IDocument document, DocumentCommand command) {
1107 switch (command.text.charAt(0)) {
1109 smartIndentAfterClosingBracket(document, command);
1112 smartIndentAfterOpeningBracket(document, command);
1115 smartIndentUponE(document, command);
1120 private void smartIndentUponE(IDocument d, DocumentCommand c) {
1121 if (c.offset < 4 || d.getLength() == 0)
1125 String content= d.get(c.offset - 3, 3);
1126 if (content.equals("els")) { //$NON-NLS-1$
1127 JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
1128 int p= c.offset - 3;
1131 int line= d.getLineOfOffset(p);
1132 int lineOffset= d.getLineOffset(line);
1134 // make sure we don't have any leading comments etc.
1135 if (d.get(lineOffset, p - lineOffset).trim().length() != 0)
1138 // line of last Java code
1139 int pos= scanner.findNonWhitespaceBackward(p - 1, JavaHeuristicScanner.UNBOUND);
1142 int lastLine= d.getLineOfOffset(pos);
1144 // only shift if the last java line is further up and is a braceless block candidate
1145 if (lastLine < line) {
1147 JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
1148 int ref= indenter.findReferencePosition(p, true, false, false, false);
1149 if (ref == JavaHeuristicScanner.NOT_FOUND)
1151 int refLine= d.getLineOfOffset(ref);
1152 String indent= getIndentOfLine(d, refLine);
1154 if (indent != null) {
1155 c.text= indent.toString() + "else"; //$NON-NLS-1$
1156 c.length += c.offset - lineOffset;
1157 c.offset= lineOffset;
1164 if (content.equals("cas")) { //$NON-NLS-1$
1165 JavaHeuristicScanner scanner= new JavaHeuristicScanner(d);
1166 int p= c.offset - 3;
1169 int line= d.getLineOfOffset(p);
1170 int lineOffset= d.getLineOffset(line);
1172 // make sure we don't have any leading comments etc.
1173 if (d.get(lineOffset, p - lineOffset).trim().length() != 0)
1176 // line of last Java code
1177 int pos= scanner.findNonWhitespaceBackward(p - 1, JavaHeuristicScanner.UNBOUND);
1180 int lastLine= d.getLineOfOffset(pos);
1182 // only shift if the last java line is further up and is a braceless block candidate
1183 if (lastLine < line) {
1185 JavaIndenter indenter= new JavaIndenter(d, scanner, fProject);
1186 int ref= indenter.findReferencePosition(p, false, false, false, true);
1187 if (ref == JavaHeuristicScanner.NOT_FOUND)
1189 int refLine= d.getLineOfOffset(ref);
1190 int nextToken= scanner.nextToken(ref, JavaHeuristicScanner.UNBOUND);
1192 if (nextToken == Symbols.TokenCASE || nextToken == Symbols.TokenDEFAULT)
1193 indent= getIndentOfLine(d, refLine);
1194 else // at the brace of the switch
1195 indent= indenter.computeIndentation(p).toString();
1197 if (indent != null) {
1198 c.text= indent.toString() + "case"; //$NON-NLS-1$
1199 c.length += c.offset - lineOffset;
1200 c.offset= lineOffset;
1207 } catch (BadLocationException e) {
1213 * @see org.eclipse.jface.text.IAutoIndentStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.DocumentCommand)
1216 public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
1217 if (c.doit == false)
1220 clearCachedValues();
1222 if (!fIsSmartMode) {
1223 super.customizeDocumentCommand(d, c);
1227 if (!fIsSmartTab && isRepresentingTab(c.text))
1230 if (c.length == 0 && c.text != null && isLineDelimiter(d, c.text)) {
1231 if (fIsSmartIndentAfterNewline)
1232 smartIndentAfterNewLine(d, c);
1234 super.customizeDocumentCommand(d, c);
1236 else if (c.text.length() == 1)
1237 smartIndentOnKeypress(d, c);
1238 else if (c.text.length() > 1 && getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SMART_PASTE))
1239 if (fViewer == null || fViewer.getTextWidget() == null || !fViewer.getTextWidget().getBlockSelection())
1240 smartPaste(d, c); // no smart backspace for paste
1245 * Tells whether the given inserted string represents hitting the Tab key.
1247 * @param text the text to check
1248 * @return <code>true</code> if the text represents hitting the Tab key
1251 private boolean isRepresentingTab(String text) {
1255 if (isInsertingSpacesForTab()) {
1256 if (text.length() == 0 || text.length() > getVisualTabLengthPreference())
1258 for (int i= 0; i < text.length(); i++) {
1259 if (text.charAt(i) != ' ')
1264 return text.length() == 1 && text.charAt(0) == '\t';
1267 private static IPreferenceStore getPreferenceStore() {
1268 return JavaPlugin.getDefault().getCombinedPreferenceStore();
1271 private boolean closeBrace() {
1275 private void clearCachedValues() {
1276 IPreferenceStore preferenceStore= getPreferenceStore();
1277 fCloseBrace= preferenceStore.getBoolean(PreferenceConstants.EDITOR_CLOSE_BRACES);
1278 fIsSmartTab= preferenceStore.getBoolean(PreferenceConstants.EDITOR_SMART_TAB);
1279 fIsSmartIndentAfterNewline= preferenceStore.getBoolean(PreferenceConstants.EDITOR_SMART_INDENT_AFTER_NEWLINE);
1280 fIsSmartMode= computeSmartMode();
1283 private boolean computeSmartMode() {
1284 IWorkbenchPage page= JavaPlugin.getActivePage();
1286 IEditorPart part= page.getActiveEditor();
1287 if (part instanceof ITextEditorExtension3) {
1288 ITextEditorExtension3 extension= (ITextEditorExtension3) part;
1289 return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT;
1290 } else if (part != null && EditorUtility.isCompareEditorInput(part.getEditorInput())) {
1291 ITextEditorExtension3 extension = (ITextEditorExtension3)part.getAdapter(ITextEditorExtension3.class);
1292 if (extension != null)
1293 return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT;
1299 private static CompilationUnitInfo getCompilationUnitForMethod(IDocument document, int offset) {
1301 JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
1303 IRegion sourceRange= scanner.findSurroundingBlock(offset);
1304 if (sourceRange == null)
1306 String source= document.get(sourceRange.getOffset(), sourceRange.getLength());
1308 StringBuffer contents= new StringBuffer();
1309 contents.append("class ____C{void ____m()"); //$NON-NLS-1$
1310 final int methodOffset= contents.length();
1311 contents.append(source);
1312 contents.append('}');
1314 char[] buffer= contents.toString().toCharArray();
1316 return new CompilationUnitInfo(buffer, sourceRange.getOffset() - methodOffset);
1318 } catch (BadLocationException e) {
1326 * Returns the block balance, i.e. zero if the blocks are balanced at <code>offset</code>, a
1327 * negative number if there are more closing than opening braces, and a positive number if there
1328 * are more opening than closing braces.
1330 * @param document the document
1331 * @param offset the offset
1332 * @param partitioning the partitioning
1333 * @return the block balance
1335 private static int getBlockBalance(IDocument document, int offset, String partitioning) {
1338 if (offset >= document.getLength())
1342 int end= offset - 1;
1344 JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
1347 begin= scanner.findOpeningPeer(begin - 1, '{', '}');
1348 end= scanner.findClosingPeer(end + 1, '{', '}');
1349 if (begin == -1 && end == -1)
1358 private static IRegion createRegion(ASTNode node, int delta) {
1359 return node == null ? null : new Region(node.getStartPosition() + delta, node.getLength());
1362 private static IRegion getToken(IDocument document, IRegion scanRegion, int tokenId) {
1366 final String source= document.get(scanRegion.getOffset(), scanRegion.getLength());
1368 fgScanner.setSource(source.toCharArray());
1370 int id= fgScanner.getNextToken();
1371 while (id != ITerminalSymbols.TokenNameEOF && id != tokenId)
1372 id= fgScanner.getNextToken();
1374 if (id == ITerminalSymbols.TokenNameEOF)
1377 int tokenOffset= fgScanner.getCurrentTokenStartPosition();
1378 int tokenLength= fgScanner.getCurrentTokenEndPosition() + 1 - tokenOffset; // inclusive end
1379 return new Region(tokenOffset + scanRegion.getOffset(), tokenLength);
1381 } catch (InvalidInputException x) {
1383 } catch (BadLocationException x) {