1 /*******************************************************************************
2 * Copyright (c) 2000, 2004 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.text;
13 import net.sourceforge.phpdt.core.JavaCore;
14 import net.sourceforge.phpdt.core.formatter.DefaultCodeFormatterConstants;
15 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
17 import org.eclipse.core.runtime.Plugin;
18 import org.eclipse.jface.text.Assert;
19 import org.eclipse.jface.text.BadLocationException;
20 import org.eclipse.jface.text.IDocument;
21 import org.eclipse.jface.text.IRegion;
22 import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
26 * Uses the {@link net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner}to
27 * get the indentation level for a certain position in a document.
30 * An instance holds some internal position in the document and is therefore
36 public class JavaIndenter {
38 /** The document being scanned. */
39 private IDocument fDocument;
40 /** The indentation accumulated by <code>findPreviousIndenationUnit</code>. */
43 * The absolute (character-counted) indentation offset for special cases
44 * (method defs, array initializers)
47 /** The stateful scanposition for the indentation methods. */
48 private int fPosition;
49 /** The previous position. */
50 private int fPreviousPos;
51 /** The most recent token. */
53 /** The line of <code>fPosition</code>. */
56 * The scanner we will use to scan the document. It has to be installed
57 * on the same document as the one we get.
59 private JavaHeuristicScanner fScanner;
62 * Creates a new instance.
64 * @param document the document to scan
65 * @param scanner the {@link JavaHeuristicScanner} to be used for scanning
66 * the document. It must be installed on the same <code>IDocument</code>.
68 public JavaIndenter(IDocument document, JavaHeuristicScanner scanner) {
69 Assert.isNotNull(document);
70 Assert.isNotNull(scanner);
76 * Computes the indentation at the reference point of <code>position</code>.
78 * @param offset the offset in the document
79 * @return a String which reflects the indentation at the line in which the
80 * reference position to <code>offset</code> resides, or <code>null</code>
81 * if it cannot be determined
83 public StringBuffer getReferenceIndentation(int offset) {
84 return getReferenceIndentation(offset, false);
88 * Computes the indentation at the reference point of <code>position</code>.
90 * @param offset the offset in the document
91 * @param assumeOpeningBrace <code>true</code> if an opening brace should be assumed
92 * @return a String which reflects the indentation at the line in which the
93 * reference position to <code>offset</code> resides, or <code>null</code>
94 * if it cannot be determined
96 private StringBuffer getReferenceIndentation(int offset, boolean assumeOpeningBrace) {
99 if (assumeOpeningBrace)
100 unit= findReferencePosition(offset, Symbols.TokenLBRACE);
102 unit= findReferencePosition(offset, peekChar(offset));
104 // if we were unable to find anything, return null
105 if (unit == JavaHeuristicScanner.NOT_FOUND)
108 return getLeadingWhitespace(unit);
113 * Computes the indentation at <code>offset</code>.
115 * @param offset the offset in the document
116 * @return a String which reflects the correct indentation for the line in
117 * which offset resides, or <code>null</code> if it cannot be
120 public StringBuffer computeIndentation(int offset) {
121 return computeIndentation(offset, false);
125 * Computes the indentation at <code>offset</code>.
127 * @param offset the offset in the document
128 * @param assumeOpeningBrace <code>true</code> if an opening brace should be assumed
129 * @return a String which reflects the correct indentation for the line in
130 * which offset resides, or <code>null</code> if it cannot be
133 public StringBuffer computeIndentation(int offset, boolean assumeOpeningBrace) {
135 StringBuffer indent= getReferenceIndentation(offset, assumeOpeningBrace);
137 // handle special alignment
138 if (fAlign != JavaHeuristicScanner.NOT_FOUND) {
140 // a special case has been detected.
141 IRegion line= fDocument.getLineInformationOfOffset(fAlign);
142 int lineOffset= line.getOffset();
143 return createIndent(lineOffset, fAlign);
144 } catch (BadLocationException e) {
152 // add additional indent
153 indent.append(createIndent(fIndent));
161 * Returns the indentation of the line at <code>offset</code> as a
162 * <code>StringBuffer</code>. If the offset is not valid, the empty string
165 * @param offset the offset in the document
166 * @return the indentation (leading whitespace) of the line in which
167 * <code>offset</code> is located
169 private StringBuffer getLeadingWhitespace(int offset) {
170 StringBuffer indent= new StringBuffer();
172 IRegion line= fDocument.getLineInformationOfOffset(offset);
173 int lineOffset= line.getOffset();
174 int nonWS= fScanner.findNonWhitespaceForwardInAnyPartition(lineOffset, lineOffset + line.getLength());
175 indent.append(fDocument.get(lineOffset, nonWS - lineOffset));
177 } catch (BadLocationException e) {
183 * Reduces indentation in <code>indent</code> by one indentation unit.
185 * @param indent the indentation to be modified
187 private void unindent(StringBuffer indent) {
188 CharSequence oneIndent= createIndent();
189 int i= indent.lastIndexOf(oneIndent.toString()); //$NON-NLS-1$
191 indent.delete(i, i + oneIndent.length());
196 * Creates an indentation string of the length indent - start + 1,
197 * consisting of the content in <code>fDocument</code> in the range
198 * [start, indent), with every character replaced by a space except for
199 * tabs, which are kept as such.
201 * <p>Every run of the number of spaces that make up a tab are replaced
202 * by a tab character.</p>
204 * @return the indentation corresponding to the document content specified
205 * by <code>start</code> and <code>indent</code>
207 private StringBuffer createIndent(int start, int indent) {
208 final int tabLen= prefTabLength();
209 StringBuffer ret= new StringBuffer();
212 while (start < indent) {
214 char ch= fDocument.getChar(start);
218 } else if (tabLen == -1){
222 if (spaces == tabLen) {
231 if (spaces == tabLen)
237 } catch (BadLocationException e) {
244 * Creates a string that represents the given number of indents (can be
247 * @param indent the requested indentation level.
249 * @return the indentation specified by <code>indent</code>
251 private StringBuffer createIndent(int indent) {
252 StringBuffer oneIndent= createIndent();
254 StringBuffer ret= new StringBuffer();
256 ret.append(oneIndent);
262 * Creates a string that represents one indent (can be
265 * @return one indentation
267 private StringBuffer createIndent() {
268 // get a sensible default when running without the infrastructure for testing
269 StringBuffer oneIndent= new StringBuffer();
270 // JavaCore plugin= JavaCore.getJavaCore();
271 PHPeclipsePlugin plugin = PHPeclipsePlugin.getDefault();
272 if (plugin == null) {
273 oneIndent.append('\t');
275 if (JavaCore.SPACE.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) {
276 int tabLen= Integer.parseInt(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE));
277 for (int i= 0; i < tabLen; i++)
278 oneIndent.append(' ');
279 } else if (JavaCore.TAB.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
280 oneIndent.append('\t');
282 oneIndent.append('\t'); // default
288 * Returns the reference position regarding to indentation for <code>offset</code>,
289 * or <code>NOT_FOUND</code>. This method calls
290 * {@link #findReferencePosition(int, int) findReferencePosition(offset, nextChar)} where
291 * <code>nextChar</code> is the next character after <code>offset</code>.
293 * @param offset the offset for which the reference is computed
294 * @return the reference statement relative to which <code>offset</code>
295 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
297 public int findReferencePosition(int offset) {
298 return findReferencePosition(offset, peekChar(offset));
302 * Peeks the next char in the document that comes after <code>offset</code>
303 * on the same line as <code>offset</code>.
305 * @param offset the offset into document
306 * @return the token symbol of the next element, or TokenEOF if there is none
308 private int peekChar(int offset) {
309 if (offset < fDocument.getLength()) {
311 IRegion line= fDocument.getLineInformationOfOffset(offset);
312 int lineOffset= line.getOffset();
313 int next= fScanner.nextToken(offset, lineOffset + line.getLength());
315 } catch (BadLocationException e) {
318 return Symbols.TokenEOF;
322 * Returns the reference position regarding to indentation for <code>position</code>,
323 * or <code>NOT_FOUND</code>.
325 * <p>If <code>peekNextChar</code> is <code>true</code>, the next token after
326 * <code>offset</code> is read and taken into account when computing the
327 * indentation. Currently, if the next token is the first token on the line
328 * (i.e. only preceded by whitespace), the following tokens are specially
331 * <li><code>switch</code> labels are indented relative to the switch block</li>
332 * <li>opening curly braces are aligned correctly with the introducing code</li>
333 * <li>closing curly braces are aligned properly with the introducing code of
334 * the matching opening brace</li>
335 * <li>closing parenthesis' are aligned with their opening peer</li>
336 * <li>the <code>else</code> keyword is aligned with its <code>if</code>, anything
337 * else is aligned normally (i.e. with the base of any introducing statements).</li>
338 * <li>if there is no token on the same line after <code>offset</code>, the indentation
339 * is the same as for an <code>else</code> keyword</li>
342 * @param offset the offset for which the reference is computed
343 * @param nextToken the next token to assume in the document
344 * @return the reference statement relative to which <code>offset</code>
345 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
347 public int findReferencePosition(int offset, int nextToken) {
348 boolean danglingElse= false;
349 boolean unindent= false;
350 boolean indent= false;
351 boolean matchBrace= false;
352 boolean matchParen= false;
353 boolean matchCase= false;
355 // account for unindenation characters already typed in, but after position
356 // if they are on a line by themselves, the indentation gets adjusted
359 // also account for a dangling else
360 if (offset < fDocument.getLength()) {
362 IRegion line= fDocument.getLineInformationOfOffset(offset);
363 int lineOffset= line.getOffset();
364 int prevPos= Math.max(offset - 1, 0);
365 boolean isFirstTokenOnLine= fDocument.get(lineOffset, prevPos + 1 - lineOffset).trim().length() == 0;
366 int prevToken= fScanner.previousToken(prevPos, JavaHeuristicScanner.UNBOUND);
367 boolean bracelessBlockStart= fScanner.isBracelessBlockStart(prevPos, JavaHeuristicScanner.UNBOUND);
370 case Symbols.TokenEOF:
371 case Symbols.TokenELSE:
374 case Symbols.TokenCASE:
375 case Symbols.TokenDEFAULT:
376 if (isFirstTokenOnLine)
379 case Symbols.TokenLBRACE: // for opening-brace-on-new-line style
380 // if (bracelessBlockStart && !prefIndentBracesForBlocks())
382 // else if ((prevToken == Symbols.TokenCOLON || prevToken == Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET) && !prefIndentBracesForArrays())
384 // else if (!bracelessBlockStart && prefIndentBracesForMethods())
387 if (bracelessBlockStart )
389 else if ((prevToken == Symbols.TokenCOLON || prevToken == Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET) )
391 else if (!bracelessBlockStart)
394 case Symbols.TokenRBRACE: // closing braces get unindented
395 if (isFirstTokenOnLine)
398 case Symbols.TokenRPAREN:
399 if (isFirstTokenOnLine)
403 } catch (BadLocationException e) {
406 // assume an else could come if we are at the end of file
410 int ref= findReferencePosition(offset, danglingElse, matchBrace, matchParen, matchCase);
419 * Returns the reference position regarding to indentation for <code>position</code>,
420 * or <code>NOT_FOUND</code>.<code>fIndent</code> will contain the
421 * relative indentation (in indentation units, not characters) after the
422 * call. If there is a special alignment (e.g. for a method declaration
423 * where parameters should be aligned), <code>fAlign</code> will contain
424 * the absolute position of the alignment reference in <code>fDocument</code>,
425 * otherwise <code>fAlign</code> is set to <code>JavaHeuristicScanner.NOT_FOUND</code>.
427 * @param offset the offset for which the reference is computed
428 * @param danglingElse whether a dangling else should be assumed at <code>position</code>
429 * @param matchBrace whether the position of the matching brace should be
430 * returned instead of doing code analysis
431 * @param matchParen whether the position of the matching parenthesis
432 * should be returned instead of doing code analysis
433 * @param matchCase whether the position of a switch statement reference
434 * should be returned (either an earlier case statement or the
435 * switch block brace)
436 * @return the reference statement relative to which <code>position</code>
437 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
439 public int findReferencePosition(int offset, boolean danglingElse, boolean matchBrace, boolean matchParen, boolean matchCase) {
440 fIndent= 0; // the indentation modification
441 fAlign= JavaHeuristicScanner.NOT_FOUND;
445 // an unindentation happens sometimes if the next token is special, namely on braces, parens and case labels
446 // align braces, but handle the case where we align with the method declaration start instead of
447 // the opening brace.
449 // if (skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE)) {
451 // // align with the opening brace that is on a line by its own
452 // int lineOffset= fDocument.getLineOffset(fLine);
453 // if (lineOffset <= fPosition && fDocument.get(lineOffset, fPosition - lineOffset).trim().length() == 0)
455 // } catch (BadLocationException e) {
456 // // concurrent modification - walk default path
458 // // if the opening brace is not on the start of the line, skip to the start
459 // int pos= skipToStatementStart(true, true);
460 // fIndent= 0; // indent is aligned with reference position
463 // // if we can't find the matching brace, the heuristic is to unindent
464 // // by one against the normal position
465 // int pos= findReferencePosition(offset, danglingElse, false, matchParen, matchCase);
471 // align parenthesis'
473 if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN))
476 // if we can't find the matching paren, the heuristic is to unindent
477 // by one against the normal position
478 int pos= findReferencePosition(offset, danglingElse, matchBrace, false, matchCase);
484 // the only reliable way to get case labels aligned (due to many different styles of using braces in a block)
485 // is to go for another case statement, or the scope opening brace
487 // return matchCaseAlignment();
492 case Symbols.TokenRBRACE:
493 // skip the block and fall through
494 // if we can't complete the scope, reset the scan position
498 case Symbols.TokenSEMICOLON:
499 // this is the 90% case: after a statement block
500 // the end of the previous statement / block previous.end
501 // search to the end of the statement / block before the previous; the token just after that is previous.start
502 return skipToStatementStart(danglingElse, false);
504 // scope introduction: special treat who special is
505 case Symbols.TokenLPAREN:
506 case Symbols.TokenLBRACE:
507 case Symbols.TokenLBRACKET:
508 return handleScopeIntroduction(offset + 1);
510 case Symbols.TokenEOF:
511 // trap when hitting start of document
514 case Symbols.TokenEQUAL:
515 // indent assignments
516 fIndent= prefAssignmentIndent();
519 case Symbols.TokenCOLON:
520 // TODO handle ternary deep indentation
521 fIndent= prefCaseBlockIndent();
524 case Symbols.TokenQUESTIONMARK:
525 if (prefTernaryDeepAlign()) {
526 setFirstElementAlignment(fPosition, offset + 1);
529 fIndent= prefTernaryIndent();
533 // indentation for blockless introducers:
534 case Symbols.TokenDO:
535 case Symbols.TokenWHILE:
536 case Symbols.TokenELSE:
537 fIndent= prefSimpleIndent();
539 case Symbols.TokenRPAREN:
541 if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN)) {
542 int scope= fPosition;
544 if (fToken == Symbols.TokenIF || fToken == Symbols.TokenWHILE || fToken == Symbols.TokenFOR) {
545 fIndent= prefSimpleIndent();
549 if (looksLikeMethodDecl()) {
550 return skipToStatementStart(danglingElse, false);
556 // else: fall through to default
558 case Symbols.TokenCOMMA:
559 // inside a list of some type
560 // easy if there is already a list item before with its own indentation - we just align
561 // if not: take the start of the list ( LPAREN, LBRACE, LBRACKET ) and either align or
562 // indent by list-indent
564 // inside whatever we don't know about: similar to the list case:
565 // if we are inside a continued expression, then either align with a previous line that has indentation
566 // or indent from the expression start line (either a scope introducer or the start of the expr).
567 return skipToPreviousListItemOrListStart();
573 * Skips to the start of a statement that ends at the current position.
575 * @param danglingElse whether to indent aligned with the last <code>if</code>
576 * @param isInBlock whether the current position is inside a block, which limits the search scope to the next scope introducer
577 * @return the reference offset of the start of the statement
579 private int skipToStatementStart(boolean danglingElse, boolean isInBlock) {
585 // exit on all block introducers
586 case Symbols.TokenIF:
587 case Symbols.TokenELSE:
588 case Symbols.TokenSYNCHRONIZED:
589 case Symbols.TokenCOLON:
590 case Symbols.TokenSTATIC:
591 case Symbols.TokenCATCH:
592 case Symbols.TokenDO:
593 case Symbols.TokenWHILE:
594 case Symbols.TokenFINALLY:
595 case Symbols.TokenFOR:
596 case Symbols.TokenTRY:
599 case Symbols.TokenSWITCH:
600 fIndent= prefCaseIndent();
606 // scope introduction through: LPAREN, LBRACE, LBRACKET
607 // search stop on SEMICOLON, RBRACE, COLON, EOF
608 // -> the next token is the start of the statement (i.e. previousPos when backward scanning)
609 case Symbols.TokenLPAREN:
610 case Symbols.TokenLBRACE:
611 case Symbols.TokenLBRACKET:
612 case Symbols.TokenSEMICOLON:
613 case Symbols.TokenEOF:
616 case Symbols.TokenCOLON:
617 int pos= fPreviousPos;
618 if (!isConditional())
622 case Symbols.TokenRBRACE:
623 // RBRACE is a little tricky: it can be the end of an array definition, but
624 // usually it is the end of a previous block
625 pos= fPreviousPos; // store state
626 if (skipScope() && looksLikeArrayInitializerIntro())
627 continue; // it's an array
629 return pos; // it's not - do as with all the above
632 case Symbols.TokenRPAREN:
633 case Symbols.TokenRBRACKET:
640 // IF / ELSE: align the position after the conditional block with the if
641 // so we are ready for an else, except if danglingElse is false
642 // in order for this to work, we must skip an else to its if
643 case Symbols.TokenIF:
648 case Symbols.TokenELSE:
649 // skip behind the next if, as we have that one covered
656 case Symbols.TokenDO:
657 // align the WHILE position with its do
660 case Symbols.TokenWHILE:
661 // this one is tricky: while can be the start of a while loop
662 // or the end of a do - while
664 if (hasMatchingDo()) {
665 // continue searching from the DO on
668 // continue searching from the WHILE on
681 * Returns true if the colon at the current position is part of a conditional
682 * (ternary) expression, false otherwise.
684 * @return true if the colon at the current position is part of a conditional
686 private boolean isConditional() {
691 // search for case, otherwise return true
692 case Symbols.TokenIDENT:
694 case Symbols.TokenCASE:
704 * Returns as a reference any previous <code>switch</code> labels (<code>case</code>
705 * or <code>default</code>) or the offset of the brace that scopes the switch
706 * statement. Sets <code>fIndent</code> to <code>prefCaseIndent</code> upon
709 * @return the reference offset for a <code>switch</code> label
711 // private int matchCaseAlignment() {
715 // // invalid cases: another case label or an LBRACE must come before a case
716 // // -> bail out with the current position
717 // case Symbols.TokenLPAREN:
718 // case Symbols.TokenLBRACKET:
719 // case Symbols.TokenEOF:
721 // case Symbols.TokenLBRACE:
722 // // opening brace of switch statement
723 // fIndent= prefCaseIndent();
725 // case Symbols.TokenCASE:
726 // case Symbols.TokenDEFAULT:
727 // // align with previous label
731 // // scopes: skip them
732 // case Symbols.TokenRPAREN:
733 // case Symbols.TokenRBRACKET:
734 // case Symbols.TokenRBRACE:
747 * Returns the reference position for a list element. The algorithm
748 * tries to match any previous indentation on the same list. If there is none,
749 * the reference position returned is determined depending on the type of list:
750 * The indentation will either match the list scope introducer (e.g. for
751 * method declarations), so called deep indents, or simply increase the
752 * indentation by a number of standard indents. See also {@link #handleScopeIntroduction(int)}.
754 * @return the reference position for a list item: either a previous list item
755 * that has its own indentation, or the list introduction start.
757 private int skipToPreviousListItemOrListStart() {
758 int startLine= fLine;
759 int startPosition= fPosition;
763 // if any line item comes with its own indentation, adapt to it
764 if (fLine < startLine) {
766 int lineOffset= fDocument.getLineOffset(startLine);
767 int bound= Math.min(fDocument.getLength(), startPosition + 1);
768 fAlign= fScanner.findNonWhitespaceForwardInAnyPartition(lineOffset, bound);
769 } catch (BadLocationException e) {
770 // ignore and return just the position
772 return startPosition;
777 case Symbols.TokenRPAREN:
778 case Symbols.TokenRBRACKET:
779 case Symbols.TokenRBRACE:
783 // scope introduction: special treat who special is
784 case Symbols.TokenLPAREN:
785 case Symbols.TokenLBRACE:
786 case Symbols.TokenLBRACKET:
787 return handleScopeIntroduction(startPosition + 1);
789 case Symbols.TokenSEMICOLON:
791 case Symbols.TokenQUESTIONMARK:
792 if (prefTernaryDeepAlign()) {
793 setFirstElementAlignment(fPosition - 1, fPosition + 1);
796 fIndent= prefTernaryIndent();
799 case Symbols.TokenEOF:
807 * Skips a scope and positions the cursor (<code>fPosition</code>) on the
808 * token that opens the scope. Returns <code>true</code> if a matching peer
809 * could be found, <code>false</code> otherwise. The current token when calling
810 * must be one out of <code>Symbols.TokenRPAREN</code>, <code>Symbols.TokenRBRACE</code>,
811 * and <code>Symbols.TokenRBRACKET</code>.
813 * @return <code>true</code> if a matching peer was found, <code>false</code> otherwise
815 private boolean skipScope() {
817 case Symbols.TokenRPAREN:
818 return skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN);
819 case Symbols.TokenRBRACKET:
820 return skipScope(Symbols.TokenLBRACKET, Symbols.TokenRBRACKET);
821 case Symbols.TokenRBRACE:
822 return skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE);
824 Assert.isTrue(false);
830 * Handles the introduction of a new scope. The current token must be one out
831 * of <code>Symbols.TokenLPAREN</code>, <code>Symbols.TokenLBRACE</code>,
832 * and <code>Symbols.TokenLBRACKET</code>. Returns as the reference position
833 * either the token introducing the scope or - if available - the first
834 * java token after that.
836 * <p>Depending on the type of scope introduction, the indentation will align
837 * (deep indenting) with the reference position (<code>fAlign</code> will be
838 * set to the reference position) or <code>fIndent</code> will be set to
839 * the number of indentation units.
842 * @param bound the bound for the search for the first token after the scope
846 private int handleScopeIntroduction(int bound) {
848 // scope introduction: special treat who special is
849 case Symbols.TokenLPAREN:
850 int pos= fPosition; // store
852 // special: method declaration deep indentation
853 if (looksLikeMethodDecl()) {
854 if (prefMethodDeclDeepIndent())
855 return setFirstElementAlignment(pos, bound);
857 fIndent= prefMethodDeclIndent();
862 if (looksLikeMethodCall()) {
863 if (prefMethodCallDeepIndent())
864 return setFirstElementAlignment(pos, bound);
866 fIndent= prefMethodCallIndent();
869 } else if (prefParenthesisDeepIndent())
870 return setFirstElementAlignment(pos, bound);
873 // normal: return the parenthesis as reference
874 fIndent= prefParenthesisIndent();
877 case Symbols.TokenLBRACE:
878 pos= fPosition; // store
880 // special: array initializer
881 if (looksLikeArrayInitializerIntro())
882 if (prefArrayDeepIndent())
883 return setFirstElementAlignment(pos, bound);
885 fIndent= prefArrayIndent();
887 fIndent= prefBlockIndent();
889 // normal: skip to the statement start before the scope introducer
890 // opening braces are often on differently ending indents than e.g. a method definition
891 fPosition= pos; // restore
892 return skipToStatementStart(true, true); // set to true to match the first if
894 case Symbols.TokenLBRACKET:
895 pos= fPosition; // store
897 // special: method declaration deep indentation
898 if (prefArrayDimensionsDeepIndent()) {
899 return setFirstElementAlignment(pos, bound);
902 // normal: return the bracket as reference
903 fIndent= prefBracketIndent();
904 return pos; // restore
907 Assert.isTrue(false);
913 * Sets the deep indent offset (<code>fAlign</code>) to either the offset
914 * right after <code>scopeIntroducerOffset</code> or - if available - the
915 * first Java token after <code>scopeIntroducerOffset</code>, but before
916 * <code>bound</code>.
918 * @param scopeIntroducerOffset the offset of the scope introducer
919 * @param bound the bound for the search for another element
920 * @return the reference position
922 private int setFirstElementAlignment(int scopeIntroducerOffset, int bound) {
923 int firstPossible= scopeIntroducerOffset + 1; // align with the first position after the scope intro
924 fAlign= fScanner.findNonWhitespaceForwardInAnyPartition(firstPossible, bound);
925 if (fAlign == JavaHeuristicScanner.NOT_FOUND)
926 fAlign= firstPossible;
932 * Returns <code>true</code> if the next token received after calling
933 * <code>nextToken</code> is either an equal sign or an array designator ('[]').
935 * @return <code>true</code> if the next elements look like the start of an array definition
937 private boolean looksLikeArrayInitializerIntro() {
939 if (fToken == Symbols.TokenEQUAL || skipBrackets()) {
946 * Skips over the next <code>if</code> keyword. The current token when calling
947 * this method must be an <code>else</code> keyword. Returns <code>true</code>
948 * if a matching <code>if</code> could be found, <code>false</code> otherwise.
949 * The cursor (<code>fPosition</code>) is set to the offset of the <code>if</code>
952 * @return <code>true</code> if a matching <code>if</code> token was found, <code>false</code> otherwise
954 private boolean skipNextIF() {
955 Assert.isTrue(fToken == Symbols.TokenELSE);
961 case Symbols.TokenRPAREN:
962 case Symbols.TokenRBRACKET:
963 case Symbols.TokenRBRACE:
967 case Symbols.TokenIF:
970 case Symbols.TokenELSE:
971 // recursively skip else-if blocks
975 // shortcut scope starts
976 case Symbols.TokenLPAREN:
977 case Symbols.TokenLBRACE:
978 case Symbols.TokenLBRACKET:
979 case Symbols.TokenEOF:
987 * while(condition); is ambiguous when parsed backwardly, as it is a valid
988 * statement by its own, so we have to check whether there is a matching
989 * do. A <code>do</code> can either be separated from the while by a
990 * block, or by a single statement, which limits our search distance.
992 * @return <code>true</code> if the <code>while</code> currently in
993 * <code>fToken</code> has a matching <code>do</code>.
995 private boolean hasMatchingDo() {
996 Assert.isTrue(fToken == Symbols.TokenWHILE);
999 case Symbols.TokenRBRACE:
1000 skipScope(); // and fall thru
1001 case Symbols.TokenSEMICOLON:
1002 skipToStatementStart(false, false);
1003 return fToken == Symbols.TokenDO;
1009 * Skips brackets if the current token is a RBRACKET. There can be nothing
1010 * but whitespace in between, this is only to be used for <code>[]</code> elements.
1012 * @return <code>true</code> if a <code>[]</code> could be scanned, the
1013 * current token is left at the LBRACKET.
1015 private boolean skipBrackets() {
1016 if (fToken == Symbols.TokenRBRACKET) {
1018 if (fToken == Symbols.TokenLBRACKET) {
1026 * Reads the next token in backward direction from the heuristic scanner
1027 * and sets the fields <code>fToken, fPreviousPosition</code> and <code>fPosition</code>
1030 private void nextToken() {
1031 nextToken(fPosition);
1035 * Reads the next token in backward direction of <code>start</code> from
1036 * the heuristic scanner and sets the fields <code>fToken, fPreviousPosition</code>
1037 * and <code>fPosition</code> accordingly.
1039 private void nextToken(int start) {
1040 fToken= fScanner.previousToken(start - 1, JavaHeuristicScanner.UNBOUND);
1041 fPreviousPos= start;
1042 fPosition= fScanner.getPosition() + 1;
1044 fLine= fDocument.getLineOfOffset(fPosition);
1045 } catch (BadLocationException e) {
1051 * Returns <code>true</code> if the current tokens look like a method
1052 * declaration header (i.e. only the return type and method name). The
1053 * heuristic calls <code>nextToken</code> and expects an identifier
1054 * (method name) and a type declaration (an identifier with optional
1055 * brackets) which also covers the visibility modifier of constructors; it
1056 * does not recognize package visible constructors.
1058 * @return <code>true</code> if the current position looks like a method
1059 * declaration header.
1061 private boolean looksLikeMethodDecl() {
1063 * TODO This heuristic does not recognize package private constructors
1064 * since those do have neither type nor visibility keywords.
1065 * One option would be to go over the parameter list, but that might
1066 * be empty as well - hard to do without an AST...
1070 if (fToken == Symbols.TokenIDENT) { // method name
1072 while (skipBrackets()); // optional brackets for array valued return types
1073 return fToken == Symbols.TokenIDENT; // type name
1080 * Returns <code>true</code> if the current tokens look like a method
1081 * call header (i.e. an identifier as opposed to a keyword taking parenthesized
1082 * parameters such as <code>if</code>).
1083 * <p>The heuristic calls <code>nextToken</code> and expects an identifier
1086 * @return <code>true</code> if the current position looks like a method call
1089 private boolean looksLikeMethodCall() {
1091 return fToken == Symbols.TokenIDENT; // method name
1095 * Scans tokens for the matching opening peer. The internal cursor
1096 * (<code>fPosition</code>) is set to the offset of the opening peer if found.
1098 * @return <code>true</code> if a matching token was found, <code>false</code>
1101 private boolean skipScope(int openToken, int closeToken) {
1108 if (fToken == closeToken) {
1110 } else if (fToken == openToken) {
1114 } else if (fToken == Symbols.TokenEOF) {
1120 // TODO adjust once there are per-project settings
1122 private int prefTabLength() {
1124 // JavaCore core= JavaCore.getJavaCore();
1125 PHPeclipsePlugin plugin= PHPeclipsePlugin.getDefault();
1126 // if (core != null && plugin != null)
1128 if (JavaCore.SPACE.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
1129 // if the formatter uses chars to mark indentation, then don't substitute any chars
1130 tabLen= -1; // results in no tabs being substituted for space runs
1132 // if the formatter uses tabs to mark indentations, use the visual setting from the editor
1133 // to get nicely aligned indentations
1134 tabLen= plugin.getPreferenceStore().getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
1136 tabLen= 4; // sensible default for testing
1141 private boolean prefArrayDimensionsDeepIndent() {
1142 return true; // sensible default
1145 private int prefArrayIndent() {
1146 Plugin plugin= JavaCore.getPlugin();
1147 if (plugin != null) {
1148 String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
1150 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1152 } catch (IllegalArgumentException e) {
1153 // ignore and return default
1157 return prefContinuationIndent(); // default
1160 private boolean prefArrayDeepIndent() {
1161 Plugin plugin= JavaCore.getPlugin();
1162 if (plugin != null) {
1163 String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
1165 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1166 } catch (IllegalArgumentException e) {
1167 // ignore and return default
1174 private boolean prefTernaryDeepAlign() {
1175 Plugin plugin= JavaCore.getPlugin();
1176 if (plugin != null) {
1177 String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
1179 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1180 } catch (IllegalArgumentException e) {
1181 // ignore and return default
1187 private int prefTernaryIndent() {
1188 Plugin plugin= JavaCore.getPlugin();
1189 if (plugin != null) {
1190 String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
1192 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1195 return prefContinuationIndent();
1196 } catch (IllegalArgumentException e) {
1197 // ignore and return default
1201 return prefContinuationIndent();
1204 private int prefCaseIndent() {
1205 Plugin plugin= JavaCore.getPlugin();
1206 if (plugin != null) {
1207 if (DefaultCodeFormatterConstants.TRUE.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_SWITCH)))
1208 return prefBlockIndent();
1213 return 0; // sun standard
1216 private int prefAssignmentIndent() {
1217 return prefBlockIndent();
1220 private int prefCaseBlockIndent() {
1222 return prefBlockIndent();
1224 Plugin plugin= JavaCore.getPlugin();
1225 if (plugin != null) {
1226 if (DefaultCodeFormatterConstants.TRUE.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_CASES)))
1227 return prefBlockIndent();
1231 return prefBlockIndent(); // sun standard
1234 private int prefSimpleIndent() {
1235 return prefBlockIndent();
1238 private int prefBracketIndent() {
1239 return prefBlockIndent();
1242 private boolean prefMethodDeclDeepIndent() {
1243 Plugin plugin= JavaCore.getPlugin();
1244 if (plugin != null) {
1245 String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
1247 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1248 } catch (IllegalArgumentException e) {
1249 // ignore and return default
1256 private int prefMethodDeclIndent() {
1257 Plugin plugin= JavaCore.getPlugin();
1258 if (plugin != null) {
1259 String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
1261 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1264 return prefContinuationIndent();
1265 } catch (IllegalArgumentException e) {
1266 // ignore and return default
1272 private boolean prefMethodCallDeepIndent() {
1273 Plugin plugin= JavaCore.getPlugin();
1274 if (plugin != null) {
1275 String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
1277 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1278 } catch (IllegalArgumentException e) {
1279 // ignore and return default
1282 return false; // sensible default
1285 private int prefMethodCallIndent() {
1286 Plugin plugin= JavaCore.getPlugin();
1287 if (plugin != null) {
1288 String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
1290 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1293 return prefContinuationIndent();
1294 } catch (IllegalArgumentException e) {
1295 // ignore and return default
1299 return 1; // sensible default
1302 private boolean prefParenthesisDeepIndent() {
1304 if (true) // don't do parenthesis deep indentation
1307 Plugin plugin= JavaCore.getPlugin();
1308 if (plugin != null) {
1309 String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
1311 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1312 } catch (IllegalArgumentException e) {
1313 // ignore and return default
1317 return false; // sensible default
1320 private int prefParenthesisIndent() {
1321 return prefContinuationIndent();
1324 private int prefBlockIndent() {
1325 return 1; // sensible default
1328 private boolean prefIndentBracesForBlocks() {
1329 Plugin plugin= JavaCore.getPlugin();
1330 if (plugin != null) {
1331 String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_BLOCK);
1332 return option.equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1335 return false; // sensible default
1338 private boolean prefIndentBracesForArrays() {
1339 Plugin plugin= JavaCore.getPlugin();
1340 if (plugin != null) {
1341 String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_ARRAY_INITIALIZER);
1342 return option.equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1345 return false; // sensible default
1348 private boolean prefIndentBracesForMethods() {
1349 Plugin plugin= JavaCore.getPlugin();
1350 if (plugin != null) {
1351 String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_METHOD_DECLARATION);
1352 return option.equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1355 return false; // sensible default
1358 private int prefContinuationIndent() {
1359 Plugin plugin= JavaCore.getPlugin();
1360 if (plugin != null) {
1361 String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
1363 return Integer.parseInt(option);
1364 } catch (NumberFormatException e) {
1365 // ignore and return default
1369 return 2; // sensible default