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.ui.WebUI;
16 //import net.sourceforge.phpeclipse.PHPeclipsePlugin;
18 import org.eclipse.core.runtime.Plugin;
19 import org.eclipse.jface.text.Assert;
20 import org.eclipse.jface.text.BadLocationException;
21 import org.eclipse.jface.text.IDocument;
22 import org.eclipse.jface.text.IRegion;
23 import org.eclipse.jface.text.ITypedRegion;
24 import org.eclipse.jface.text.TextUtilities;
25 import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
28 * Uses the {@link net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner}to
29 * get the indentation level for a certain position in a document.
32 * An instance holds some internal position in the document and is therefore not
38 public class JavaIndenter {
40 /** The document being scanned. */
41 private IDocument fDocument;
43 /** The indentation accumulated by <code>findPreviousIndenationUnit</code>. */
47 * The absolute (character-counted) indentation offset for special cases
48 * (method defs, array initializers)
52 /** The stateful scanposition for the indentation methods. */
53 private int fPosition;
55 /** The previous position. */
56 private int fPreviousPos;
58 /** The most recent token. */
61 /** The line of <code>fPosition</code>. */
65 * The scanner we will use to scan the document. It has to be installed on
66 * the same document as the one we get.
68 private JavaHeuristicScanner fScanner;
71 * Creates a new instance.
74 * the document to scan
76 * the {@link JavaHeuristicScanner} to be used for scanning the
77 * document. It must be installed on the same
78 * <code>IDocument</code>.
80 public JavaIndenter(IDocument document, JavaHeuristicScanner scanner) {
81 Assert.isNotNull(document);
82 Assert.isNotNull(scanner);
88 * Computes the indentation at the reference point of <code>position</code>.
91 * the offset in the document
92 * @return a String which reflects the indentation at the line in which the
93 * reference position to <code>offset</code> resides, or
94 * <code>null</code> if it cannot be determined
96 public StringBuffer getReferenceIndentation(int offset) {
97 return getReferenceIndentation(offset, false);
101 * Computes the indentation at the reference point of <code>position</code>.
104 * the offset in the document
105 * @param assumeOpeningBrace
106 * <code>true</code> if an opening brace should be assumed
107 * @return a String which reflects the indentation at the line in which the
108 * reference position to <code>offset</code> resides, or
109 * <code>null</code> if it cannot be determined
111 private StringBuffer getReferenceIndentation(int offset,
112 boolean assumeOpeningBrace) {
115 if (assumeOpeningBrace)
116 unit = findReferencePosition(offset, Symbols.TokenLBRACE);
118 unit = findReferencePosition(offset, peekChar(offset));
120 // if we were unable to find anything, return null
121 if (unit == JavaHeuristicScanner.NOT_FOUND)
124 return getLeadingWhitespace(unit);
129 * Computes the indentation at <code>offset</code>.
132 * the offset in the document
133 * @return a String which reflects the correct indentation for the line in
134 * which offset resides, or <code>null</code> if it cannot be
137 public StringBuffer computeIndentation(int offset) {
138 return computeIndentation(offset, false);
142 * Computes the indentation at <code>offset</code>.
145 * the offset in the document
146 * @param assumeOpeningBrace
147 * <code>true</code> if an opening brace should be assumed
148 * @return a String which reflects the correct indentation for the line in
149 * which offset resides, or <code>null</code> if it cannot be
152 public StringBuffer computeIndentation(int offset,
153 boolean assumeOpeningBrace) {
155 StringBuffer indent = getReferenceIndentation(offset,
158 // handle special alignment
159 if (fAlign != JavaHeuristicScanner.NOT_FOUND) {
161 // a special case has been detected.
162 IRegion line = fDocument.getLineInformationOfOffset(fAlign);
163 int lineOffset = line.getOffset();
164 return createIndent(lineOffset, fAlign);
165 } catch (BadLocationException e) {
173 // add additional indent
174 //indent.append(createIndent(fIndent));
175 indent.insert(0, createIndent(fIndent));
183 * Returns the indentation of the line at <code>offset</code> as a
184 * <code>StringBuffer</code>. If the offset is not valid, the empty
185 * string is returned.
188 * the offset in the document
189 * @return the indentation (leading whitespace) of the line in which
190 * <code>offset</code> is located
192 private StringBuffer getLeadingWhitespace(int offset) {
193 StringBuffer indent = new StringBuffer();
195 IRegion line = fDocument.getLineInformationOfOffset(offset);
196 int lineOffset = line.getOffset();
197 int nonWS = fScanner.findNonWhitespaceForwardInAnyPartition(
198 lineOffset, lineOffset + line.getLength());
199 indent.append(fDocument.get(lineOffset, nonWS - lineOffset));
201 } catch (BadLocationException e) {
207 * Reduces indentation in <code>indent</code> by one indentation unit.
210 * the indentation to be modified
212 private void unindent(StringBuffer indent) {
213 CharSequence oneIndent = createIndent();
214 int i = indent.lastIndexOf(oneIndent.toString()); //$NON-NLS-1$
216 indent.delete(i, i + oneIndent.length());
221 * Creates an indentation string of the length indent - start + 1,
222 * consisting of the content in <code>fDocument</code> in the range
223 * [start, indent), with every character replaced by a space except for
224 * tabs, which are kept as such.
227 * Every run of the number of spaces that make up a tab are replaced by a
231 * @return the indentation corresponding to the document content specified
232 * by <code>start</code> and <code>indent</code>
234 private StringBuffer createIndent(int start, int indent) {
235 final int tabLen = prefTabLength();
236 StringBuffer ret = new StringBuffer();
239 while (start < indent) {
241 char ch = fDocument.getChar(start);
245 } else if (tabLen == -1) {
249 if (spaces == tabLen) {
258 if (spaces == tabLen)
264 } catch (BadLocationException e) {
271 * Creates a string that represents the given number of indents (can be
275 * the requested indentation level.
277 * @return the indentation specified by <code>indent</code>
279 public StringBuffer createIndent(int indent) {
280 StringBuffer oneIndent = createIndent();
282 StringBuffer ret = new StringBuffer();
284 ret.append(oneIndent);
290 * Creates a string that represents one indent (can be spaces or tabs..)
292 * @return one indentation
294 private StringBuffer createIndent() {
295 // get a sensible default when running without the infrastructure for
297 StringBuffer oneIndent = new StringBuffer();
298 // JavaCore plugin= JavaCore.getJavaCore();
299 WebUI plugin = WebUI.getDefault();
300 if (plugin == null) {
301 oneIndent.append('\t');
305 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) {
308 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE));
309 for (int i = 0; i < tabLen; i++)
310 oneIndent.append(' ');
311 } else if (JavaCore.TAB
313 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
314 oneIndent.append('\t');
316 oneIndent.append('\t'); // default
322 * Returns the reference position regarding to indentation for
323 * <code>offset</code>, or <code>NOT_FOUND</code>. This method calls
324 * {@link #findReferencePosition(int, int) findReferencePosition(offset, nextChar)}
325 * where <code>nextChar</code> is the next character after
326 * <code>offset</code>.
329 * the offset for which the reference is computed
330 * @return the reference statement relative to which <code>offset</code>
331 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
333 public int findReferencePosition(int offset) {
334 return findReferencePosition(offset, peekChar(offset));
338 * Peeks the next char in the document that comes after <code>offset</code>
339 * on the same line as <code>offset</code>.
342 * the offset into document
343 * @return the token symbol of the next element, or TokenEOF if there is
346 private int peekChar(int offset) {
347 if (offset < fDocument.getLength()) {
349 IRegion line = fDocument.getLineInformationOfOffset(offset);
350 int lineOffset = line.getOffset();
351 int next = fScanner.nextToken(offset, lineOffset
354 } catch (BadLocationException e) {
357 return Symbols.TokenEOF;
361 * Returns the reference position regarding to indentation for
362 * <code>position</code>, or <code>NOT_FOUND</code>.
365 * If <code>peekNextChar</code> is <code>true</code>, the next token
366 * after <code>offset</code> is read and taken into account when computing
367 * the indentation. Currently, if the next token is the first token on the
368 * line (i.e. only preceded by whitespace), the following tokens are
371 * <li><code>switch</code> labels are indented relative to the switch
373 * <li>opening curly braces are aligned correctly with the introducing code</li>
374 * <li>closing curly braces are aligned properly with the introducing code
375 * of the matching opening brace</li>
376 * <li>closing parenthesis' are aligned with their opening peer</li>
377 * <li>the <code>else</code> keyword is aligned with its <code>if</code>,
378 * anything else is aligned normally (i.e. with the base of any introducing
380 * <li>if there is no token on the same line after <code>offset</code>,
381 * the indentation is the same as for an <code>else</code> keyword</li>
385 * the offset for which the reference is computed
387 * the next token to assume in the document
388 * @return the reference statement relative to which <code>offset</code>
389 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
391 public int findReferencePosition(int offset, int nextToken) {
392 boolean danglingElse = false;
393 boolean unindent = false;
394 boolean indent = false;
395 boolean matchBrace = false;
396 boolean matchParen = false;
397 boolean matchCase = false;
399 // account for unindenation characters already typed in, but after
401 // if they are on a line by themselves, the indentation gets adjusted
404 // also account for a dangling else
405 if (offset < fDocument.getLength()) {
407 IRegion line = fDocument.getLineInformationOfOffset(offset);
408 int lineOffset = line.getOffset();
409 int prevPos = Math.max(offset - 1, 0);
410 boolean isFirstTokenOnLine = fDocument.get(lineOffset,
411 prevPos + 1 - lineOffset).trim().length() == 0;
412 int prevToken = fScanner.previousToken(prevPos,
413 JavaHeuristicScanner.UNBOUND);
414 if (prevToken == Symbols.TokenEOF && nextToken == Symbols.TokenEOF) {
415 ITypedRegion partition = TextUtilities.getPartition(fDocument, IPHPPartitions.PHP_PARTITIONING, offset, true);
416 if (partition.getType().equals(IPHPPartitions.PHP_SINGLELINE_COMMENT)) {
417 fAlign = fScanner.getPosition();
419 fAlign = JavaHeuristicScanner.NOT_FOUND;
421 return JavaHeuristicScanner.NOT_FOUND;
423 boolean bracelessBlockStart = fScanner.isBracelessBlockStart(
424 prevPos, JavaHeuristicScanner.UNBOUND);
427 case Symbols.TokenEOF:
428 case Symbols.TokenELSE:
431 case Symbols.TokenCASE:
432 case Symbols.TokenDEFAULT:
433 if (isFirstTokenOnLine)
436 case Symbols.TokenLBRACE: // for opening-brace-on-new-line
438 // if (bracelessBlockStart && !prefIndentBracesForBlocks())
440 // else if ((prevToken == Symbols.TokenCOLON || prevToken ==
441 // Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET) &&
442 // !prefIndentBracesForArrays())
444 // else if (!bracelessBlockStart &&
445 // prefIndentBracesForMethods())
448 if (bracelessBlockStart)
450 else if ((prevToken == Symbols.TokenCOLON
451 || prevToken == Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET))
453 else if (!bracelessBlockStart)
456 case Symbols.TokenRBRACE: // closing braces get unindented
457 if (isFirstTokenOnLine)
460 case Symbols.TokenRPAREN:
461 if (isFirstTokenOnLine)
465 } catch (BadLocationException e) {
468 // assume an else could come if we are at the end of file
472 int ref = findReferencePosition(offset, danglingElse, matchBrace,
473 matchParen, matchCase);
482 * Returns the reference position regarding to indentation for
483 * <code>position</code>, or <code>NOT_FOUND</code>.<code>fIndent</code>
484 * will contain the relative indentation (in indentation units, not
485 * characters) after the call. If there is a special alignment (e.g. for a
486 * method declaration where parameters should be aligned),
487 * <code>fAlign</code> will contain the absolute position of the alignment
488 * reference in <code>fDocument</code>, otherwise <code>fAlign</code>
489 * is set to <code>JavaHeuristicScanner.NOT_FOUND</code>.
492 * the offset for which the reference is computed
493 * @param danglingElse
494 * whether a dangling else should be assumed at
495 * <code>position</code>
497 * whether the position of the matching brace should be returned
498 * instead of doing code analysis
500 * whether the position of the matching parenthesis should be
501 * returned instead of doing code analysis
503 * whether the position of a switch statement reference should be
504 * returned (either an earlier case statement or the switch block
506 * @return the reference statement relative to which <code>position</code>
507 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
509 public int findReferencePosition(int offset, boolean danglingElse,
510 boolean matchBrace, boolean matchParen, boolean matchCase) {
511 fIndent = 0; // the indentation modification
512 fAlign = JavaHeuristicScanner.NOT_FOUND;
516 // an unindentation happens sometimes if the next token is special,
517 // namely on braces, parens and case labels
518 // align braces, but handle the case where we align with the method
519 // declaration start instead of
520 // the opening brace.
522 if (skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE)) {
524 // align with the opening brace that is on a line by its own
525 int lineOffset = fDocument.getLineOffset(fLine);
526 if (lineOffset <= fPosition
528 .get(lineOffset, fPosition - lineOffset)
529 .trim().length() == 0)
531 } catch (BadLocationException e) {
532 // concurrent modification - walk default path
534 // if the opening brace is not on the start of the line, skip to
536 int pos = skipToStatementStart(true, true);
537 fIndent = 0; // indent is aligned with reference position
540 // if we can't find the matching brace, the heuristic is to
542 // by one against the normal position
543 int pos = findReferencePosition(offset, danglingElse, false,
544 matchParen, matchCase);
550 // align parenthesis'
552 if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN))
555 // if we can't find the matching paren, the heuristic is to
557 // by one against the normal position
558 int pos = findReferencePosition(offset, danglingElse,
559 matchBrace, false, matchCase);
565 // the only reliable way to get case labels aligned (due to many
566 // different styles of using braces in a block)
567 // is to go for another case statement, or the scope opening brace
569 return matchCaseAlignment();
574 case Symbols.TokenRBRACE:
575 // skip the block and fall through
576 // if we can't complete the scope, reset the scan position
580 case Symbols.TokenSEMICOLON:
581 // this is the 90% case: after a statement block
582 // the end of the previous statement / block previous.end
583 // search to the end of the statement / block before the previous;
584 // the token just after that is previous.start
585 return skipToStatementStart(danglingElse, false);
587 // scope introduction: special treat who special is
588 case Symbols.TokenLPAREN:
589 case Symbols.TokenLBRACE:
590 case Symbols.TokenLBRACKET:
591 return handleScopeIntroduction(offset + 1);
593 case Symbols.TokenEOF:
594 // trap when hitting start of document
597 case Symbols.TokenEQUAL:
598 // indent assignments
599 fIndent = prefAssignmentIndent();
602 case Symbols.TokenCOLON:
603 // TODO handle ternary deep indentation
604 fIndent = prefCaseBlockIndent();
607 case Symbols.TokenQUESTIONMARK:
608 if (prefTernaryDeepAlign()) {
609 setFirstElementAlignment(fPosition, offset + 1);
612 fIndent = prefTernaryIndent();
616 // indentation for blockless introducers:
617 case Symbols.TokenDO:
618 case Symbols.TokenWHILE:
619 case Symbols.TokenELSE:
620 fIndent = prefSimpleIndent();
622 case Symbols.TokenRPAREN:
624 if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN)) {
625 int scope = fPosition;
627 if (fToken == Symbols.TokenIF || fToken == Symbols.TokenWHILE
628 || fToken == Symbols.TokenFOR) {
629 fIndent = prefSimpleIndent();
633 if (looksLikeMethodDecl()) {
634 return skipToStatementStart(danglingElse, false);
640 // else: fall through to default
642 case Symbols.TokenCOMMA:
643 // inside a list of some type
644 // easy if there is already a list item before with its own
645 // indentation - we just align
646 // if not: take the start of the list ( LPAREN, LBRACE, LBRACKET )
647 // and either align or
648 // indent by list-indent
650 // inside whatever we don't know about: similar to the list case:
651 // if we are inside a continued expression, then either align with a
652 // previous line that has indentation
653 // or indent from the expression start line (either a scope
654 // introducer or the start of the expr).
655 return skipToPreviousListItemOrListStart();
661 * Skips to the start of a statement that ends at the current position.
663 * @param danglingElse
664 * whether to indent aligned with the last <code>if</code>
666 * whether the current position is inside a block, which limits
667 * the search scope to the next scope introducer
668 * @return the reference offset of the start of the statement
670 private int skipToStatementStart(boolean danglingElse, boolean isInBlock) {
676 // exit on all block introducers
677 case Symbols.TokenIF:
678 case Symbols.TokenELSE:
679 case Symbols.TokenSYNCHRONIZED:
680 case Symbols.TokenCOLON:
681 case Symbols.TokenSTATIC:
682 case Symbols.TokenCATCH:
683 case Symbols.TokenDO:
684 case Symbols.TokenWHILE:
685 case Symbols.TokenFINALLY:
686 case Symbols.TokenFOR:
687 case Symbols.TokenTRY:
690 case Symbols.TokenSWITCH:
691 fIndent = prefCaseIndent();
697 // scope introduction through: LPAREN, LBRACE, LBRACKET
698 // search stop on SEMICOLON, RBRACE, COLON, EOF
699 // -> the next token is the start of the statement (i.e. previousPos
700 // when backward scanning)
701 case Symbols.TokenLPAREN:
702 case Symbols.TokenLBRACE:
703 case Symbols.TokenLBRACKET:
704 case Symbols.TokenSEMICOLON:
705 case Symbols.TokenEOF:
708 case Symbols.TokenCOLON:
709 int pos = fPreviousPos;
710 if (!isConditional())
714 case Symbols.TokenRBRACE:
715 // RBRACE is a little tricky: it can be the end of an array
717 // usually it is the end of a previous block
718 pos = fPreviousPos; // store state
719 if (skipScope() && looksLikeArrayInitializerIntro())
720 continue; // it's an array
722 return pos; // it's not - do as with all the above
725 case Symbols.TokenRPAREN:
726 case Symbols.TokenRBRACKET:
733 // IF / ELSE: align the position after the conditional block
735 // so we are ready for an else, except if danglingElse is false
736 // in order for this to work, we must skip an else to its if
737 case Symbols.TokenIF:
742 case Symbols.TokenELSE:
743 // skip behind the next if, as we have that one covered
750 case Symbols.TokenDO:
751 // align the WHILE position with its do
754 case Symbols.TokenWHILE:
755 // this one is tricky: while can be the start of a while loop
756 // or the end of a do - while
758 if (hasMatchingDo()) {
759 // continue searching from the DO on
762 // continue searching from the WHILE on
775 * Returns true if the colon at the current position is part of a
776 * conditional (ternary) expression, false otherwise.
778 * @return true if the colon at the current position is part of a
781 private boolean isConditional() {
786 // search for case, otherwise return true
787 case Symbols.TokenIDENT:
789 case Symbols.TokenCASE:
799 * Returns as a reference any previous <code>switch</code> labels (<code>case</code>
800 * or <code>default</code>) or the offset of the brace that scopes the
801 * switch statement. Sets <code>fIndent</code> to
802 * <code>prefCaseIndent</code> upon a match.
804 * @return the reference offset for a <code>switch</code> label
806 private int matchCaseAlignment() {
810 // invalid cases: another case label or an LBRACE must come before a
812 // -> bail out with the current position
813 case Symbols.TokenLPAREN:
814 case Symbols.TokenLBRACKET:
815 case Symbols.TokenEOF:
817 case Symbols.TokenLBRACE:
818 // opening brace of switch statement
819 fIndent = 1; //prefCaseIndent() is for Java
821 case Symbols.TokenCASE:
822 case Symbols.TokenDEFAULT:
823 // align with previous label
827 case Symbols.TokenRPAREN:
828 case Symbols.TokenRBRACKET:
829 case Symbols.TokenRBRACE:
840 * Returns the reference position for a list element. The algorithm tries to
841 * match any previous indentation on the same list. If there is none, the
842 * reference position returned is determined depending on the type of list:
843 * The indentation will either match the list scope introducer (e.g. for
844 * method declarations), so called deep indents, or simply increase the
845 * indentation by a number of standard indents. See also
846 * {@link #handleScopeIntroduction(int)}.
848 * @return the reference position for a list item: either a previous list
849 * item that has its own indentation, or the list introduction
852 private int skipToPreviousListItemOrListStart() {
853 int startLine = fLine;
854 int startPosition = fPosition;
858 // if any line item comes with its own indentation, adapt to it
859 if (fLine < startLine) {
861 int lineOffset = fDocument.getLineOffset(startLine);
862 int bound = Math.min(fDocument.getLength(),
864 fAlign = fScanner.findNonWhitespaceForwardInAnyPartition(
866 } catch (BadLocationException e) {
867 // ignore and return just the position
869 return startPosition;
874 case Symbols.TokenRPAREN:
875 case Symbols.TokenRBRACKET:
876 case Symbols.TokenRBRACE:
880 // scope introduction: special treat who special is
881 case Symbols.TokenLPAREN:
882 case Symbols.TokenLBRACE:
883 case Symbols.TokenLBRACKET:
884 return handleScopeIntroduction(startPosition + 1);
886 case Symbols.TokenSEMICOLON:
888 case Symbols.TokenQUESTIONMARK:
889 if (prefTernaryDeepAlign()) {
890 setFirstElementAlignment(fPosition - 1, fPosition + 1);
892 fIndent = prefTernaryIndent();
895 case Symbols.TokenEOF:
898 case Symbols.TokenEQUAL:
899 // indent assignments
900 fIndent= prefAssignmentIndent();
907 * Skips a scope and positions the cursor (<code>fPosition</code>) on
908 * the token that opens the scope. Returns <code>true</code> if a matching
909 * peer could be found, <code>false</code> otherwise. The current token
910 * when calling must be one out of <code>Symbols.TokenRPAREN</code>,
911 * <code>Symbols.TokenRBRACE</code>, and
912 * <code>Symbols.TokenRBRACKET</code>.
914 * @return <code>true</code> if a matching peer was found,
915 * <code>false</code> otherwise
917 private boolean skipScope() {
919 case Symbols.TokenRPAREN:
920 return skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN);
921 case Symbols.TokenRBRACKET:
922 return skipScope(Symbols.TokenLBRACKET, Symbols.TokenRBRACKET);
923 case Symbols.TokenRBRACE:
924 return skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE);
926 Assert.isTrue(false);
932 * Handles the introduction of a new scope. The current token must be one
933 * out of <code>Symbols.TokenLPAREN</code>,
934 * <code>Symbols.TokenLBRACE</code>, and
935 * <code>Symbols.TokenLBRACKET</code>. Returns as the reference position
936 * either the token introducing the scope or - if available - the first java
940 * Depending on the type of scope introduction, the indentation will align
941 * (deep indenting) with the reference position (<code>fAlign</code> will
942 * be set to the reference position) or <code>fIndent</code> will be set
943 * to the number of indentation units.
947 * the bound for the search for the first token after the scope
951 private int handleScopeIntroduction(int bound) {
953 // scope introduction: special treat who special is
954 case Symbols.TokenLPAREN:
955 int pos = fPosition; // store
957 // special: method declaration deep indentation
958 if (looksLikeMethodDecl()) {
959 if (prefMethodDeclDeepIndent())
960 return setFirstElementAlignment(pos, bound);
962 fIndent = prefMethodDeclIndent();
967 if (looksLikeMethodCall()) {
968 if (prefMethodCallDeepIndent())
969 return setFirstElementAlignment(pos, bound);
971 fIndent = prefMethodCallIndent();
974 } else if (prefParenthesisDeepIndent())
975 return setFirstElementAlignment(pos, bound);
978 // normal: return the parenthesis as reference
979 fIndent = prefParenthesisIndent();
982 case Symbols.TokenLBRACE:
983 pos = fPosition; // store
985 // special: array initializer
986 if (looksLikeArrayInitializerIntro())
987 if (prefArrayDeepIndent())
988 return setFirstElementAlignment(pos, bound);
990 fIndent = prefArrayIndent();
992 fIndent = prefBlockIndent();
994 // normal: skip to the statement start before the scope introducer
995 // opening braces are often on differently ending indents than e.g.
996 // a method definition
997 fPosition = pos; // restore
998 return skipToStatementStart(true, true); // set to true to match
1001 case Symbols.TokenLBRACKET:
1002 pos = fPosition; // store
1004 // special: method declaration deep indentation
1005 if (prefArrayDimensionsDeepIndent()) {
1006 return setFirstElementAlignment(pos, bound);
1009 // normal: return the bracket as reference
1010 fIndent = prefBracketIndent();
1011 return pos; // restore
1014 Assert.isTrue(false);
1020 * Sets the deep indent offset (<code>fAlign</code>) to either the
1021 * offset right after <code>scopeIntroducerOffset</code> or - if available -
1022 * the first Java token after <code>scopeIntroducerOffset</code>, but
1023 * before <code>bound</code>.
1025 * @param scopeIntroducerOffset
1026 * the offset of the scope introducer
1028 * the bound for the search for another element
1029 * @return the reference position
1031 private int setFirstElementAlignment(int scopeIntroducerOffset, int bound) {
1032 int firstPossible = scopeIntroducerOffset + 1; // align with the first
1033 // position after the
1035 fAlign = fScanner.findNonWhitespaceForwardInAnyPartition(firstPossible,
1037 if (fAlign == JavaHeuristicScanner.NOT_FOUND)
1038 fAlign = firstPossible;
1043 * Returns <code>true</code> if the next token received after calling
1044 * <code>nextToken</code> is either an equal sign or an array designator
1047 * @return <code>true</code> if the next elements look like the start of
1048 * an array definition
1050 private boolean looksLikeArrayInitializerIntro() {
1052 if (fToken == Symbols.TokenEQUAL || skipBrackets()) {
1059 * Skips over the next <code>if</code> keyword. The current token when
1060 * calling this method must be an <code>else</code> keyword. Returns
1061 * <code>true</code> if a matching <code>if</code> could be found,
1062 * <code>false</code> otherwise. The cursor (<code>fPosition</code>)
1063 * is set to the offset of the <code>if</code> token.
1065 * @return <code>true</code> if a matching <code>if</code> token was
1066 * found, <code>false</code> otherwise
1068 private boolean skipNextIF() {
1069 Assert.isTrue(fToken == Symbols.TokenELSE);
1074 // scopes: skip them
1075 case Symbols.TokenRPAREN:
1076 case Symbols.TokenRBRACKET:
1077 case Symbols.TokenRBRACE:
1081 case Symbols.TokenIF:
1084 case Symbols.TokenELSE:
1085 // recursively skip else-if blocks
1089 // shortcut scope starts
1090 case Symbols.TokenLPAREN:
1091 case Symbols.TokenLBRACE:
1092 case Symbols.TokenLBRACKET:
1093 case Symbols.TokenEOF:
1100 * while(condition); is ambiguous when parsed backwardly, as it is a valid
1101 * statement by its own, so we have to check whether there is a matching do.
1102 * A <code>do</code> can either be separated from the while by a block, or
1103 * by a single statement, which limits our search distance.
1105 * @return <code>true</code> if the <code>while</code> currently in
1106 * <code>fToken</code> has a matching <code>do</code>.
1108 private boolean hasMatchingDo() {
1109 Assert.isTrue(fToken == Symbols.TokenWHILE);
1112 case Symbols.TokenRBRACE:
1113 skipScope(); // and fall thru
1114 case Symbols.TokenSEMICOLON:
1115 skipToStatementStart(false, false);
1116 return fToken == Symbols.TokenDO;
1122 * Skips brackets if the current token is a RBRACKET. There can be nothing
1123 * but whitespace in between, this is only to be used for <code>[]</code>
1126 * @return <code>true</code> if a <code>[]</code> could be scanned, the
1127 * current token is left at the LBRACKET.
1129 private boolean skipBrackets() {
1130 if (fToken == Symbols.TokenRBRACKET) {
1132 if (fToken == Symbols.TokenLBRACKET) {
1140 * Reads the next token in backward direction from the heuristic scanner and
1141 * sets the fields <code>fToken, fPreviousPosition</code> and
1142 * <code>fPosition</code> accordingly.
1144 private void nextToken() {
1145 nextToken(fPosition);
1149 * Reads the next token in backward direction of <code>start</code> from
1150 * the heuristic scanner and sets the fields
1151 * <code>fToken, fPreviousPosition</code> and <code>fPosition</code>
1154 private void nextToken(int start) {
1156 .previousToken(start - 1, JavaHeuristicScanner.UNBOUND);
1157 fPreviousPos = start;
1158 fPosition = fScanner.getPosition() + 1;
1160 fLine = fDocument.getLineOfOffset(fPosition);
1161 } catch (BadLocationException e) {
1167 * Returns <code>true</code> if the current tokens look like a method
1168 * declaration header (i.e. only the return type and method name). The
1169 * heuristic calls <code>nextToken</code> and expects an identifier
1170 * (method name) and a type declaration (an identifier with optional
1171 * brackets) which also covers the visibility modifier of constructors; it
1172 * does not recognize package visible constructors.
1174 * @return <code>true</code> if the current position looks like a method
1175 * declaration header.
1177 private boolean looksLikeMethodDecl() {
1179 * TODO This heuristic does not recognize package private constructors
1180 * since those do have neither type nor visibility keywords. One option
1181 * would be to go over the parameter list, but that might be empty as
1182 * well - hard to do without an AST...
1186 if (fToken == Symbols.TokenIDENT) { // method name
1189 while (skipBrackets()); // optional brackets for array valued return
1191 return fToken == Symbols.TokenIDENT; // type name
1198 * Returns <code>true</code> if the current tokens look like a method call
1199 * header (i.e. an identifier as opposed to a keyword taking parenthesized
1200 * parameters such as <code>if</code>).
1202 * The heuristic calls <code>nextToken</code> and expects an identifier
1205 * @return <code>true</code> if the current position looks like a method
1208 private boolean looksLikeMethodCall() {
1210 return fToken == Symbols.TokenIDENT; // method name
1214 * Scans tokens for the matching opening peer. The internal cursor (<code>fPosition</code>)
1215 * is set to the offset of the opening peer if found.
1217 * @return <code>true</code> if a matching token was found,
1218 * <code>false</code> otherwise
1220 private boolean skipScope(int openToken, int closeToken) {
1227 if (fToken == closeToken) {
1229 } else if (fToken == openToken) {
1233 } else if (fToken == Symbols.TokenEOF) {
1239 // TODO adjust once there are per-project settings
1241 private int prefTabLength() {
1243 // JavaCore core= JavaCore.getJavaCore();
1244 WebUI plugin = WebUI.getDefault();
1245 // if (core != null && plugin != null)
1249 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
1250 // if the formatter uses chars to mark indentation, then don't
1251 // substitute any chars
1252 tabLen = -1; // results in no tabs being substituted for
1255 // if the formatter uses tabs to mark indentations, use the
1256 // visual setting from the editor
1257 // to get nicely aligned indentations
1259 .getPreferenceStore()
1261 AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
1263 tabLen = 4; // sensible default for testing
1268 private boolean prefArrayDimensionsDeepIndent() {
1269 return true; // sensible default
1272 private int prefArrayIndent() {
1273 Plugin plugin = JavaCore.getPlugin();
1274 if (plugin != null) {
1275 String option = JavaCore
1276 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
1278 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1280 } catch (IllegalArgumentException e) {
1281 // ignore and return default
1285 return prefContinuationIndent(); // default
1288 private boolean prefArrayDeepIndent() {
1289 Plugin plugin = JavaCore.getPlugin();
1290 if (plugin != null) {
1291 String option = JavaCore
1292 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
1294 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1295 } catch (IllegalArgumentException e) {
1296 // ignore and return default
1303 private boolean prefTernaryDeepAlign() {
1304 Plugin plugin = JavaCore.getPlugin();
1305 if (plugin != null) {
1306 String option = JavaCore
1307 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
1309 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1310 } catch (IllegalArgumentException e) {
1311 // ignore and return default
1317 private int prefTernaryIndent() {
1318 Plugin plugin = JavaCore.getPlugin();
1319 if (plugin != null) {
1320 String option = JavaCore
1321 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
1323 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1326 return prefContinuationIndent();
1327 } catch (IllegalArgumentException e) {
1328 // ignore and return default
1332 return prefContinuationIndent();
1335 private int prefCaseIndent() {
1336 Plugin plugin = JavaCore.getPlugin();
1337 if (plugin != null) {
1338 if (DefaultCodeFormatterConstants.TRUE
1340 .getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_SWITCH)))
1341 return prefBlockIndent();
1346 return 0; // sun standard
1349 private int prefAssignmentIndent() {
1350 return prefBlockIndent();
1353 private int prefCaseBlockIndent() {
1355 return prefBlockIndent();
1357 Plugin plugin = JavaCore.getPlugin();
1358 if (plugin != null) {
1359 if (DefaultCodeFormatterConstants.TRUE
1361 .getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_CASES)))
1362 return prefBlockIndent();
1366 return prefBlockIndent(); // sun standard
1369 private int prefSimpleIndent() {
1370 return prefBlockIndent();
1373 private int prefBracketIndent() {
1374 return prefBlockIndent();
1377 private boolean prefMethodDeclDeepIndent() {
1378 Plugin plugin = JavaCore.getPlugin();
1379 if (plugin != null) {
1380 String option = JavaCore
1381 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
1383 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1384 } catch (IllegalArgumentException e) {
1385 // ignore and return default
1392 private int prefMethodDeclIndent() {
1393 Plugin plugin = JavaCore.getPlugin();
1394 if (plugin != null) {
1395 String option = JavaCore
1396 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
1398 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1401 return prefContinuationIndent();
1402 } catch (IllegalArgumentException e) {
1403 // ignore and return default
1409 private boolean prefMethodCallDeepIndent() {
1410 Plugin plugin = JavaCore.getPlugin();
1411 if (plugin != null) {
1412 String option = JavaCore
1413 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
1415 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1416 } catch (IllegalArgumentException e) {
1417 // ignore and return default
1420 return false; // sensible default
1423 private int prefMethodCallIndent() {
1424 Plugin plugin = JavaCore.getPlugin();
1425 if (plugin != null) {
1426 String option = JavaCore
1427 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
1429 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1432 return prefContinuationIndent();
1433 } catch (IllegalArgumentException e) {
1434 // ignore and return default
1438 return 1; // sensible default
1441 private boolean prefParenthesisDeepIndent() {
1443 if (true) // don't do parenthesis deep indentation
1446 Plugin plugin = JavaCore.getPlugin();
1447 if (plugin != null) {
1448 String option = JavaCore
1449 .getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
1451 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1452 } catch (IllegalArgumentException e) {
1453 // ignore and return default
1457 return false; // sensible default
1460 private int prefParenthesisIndent() {
1461 return prefContinuationIndent();
1464 private int prefBlockIndent() {
1465 return 1; // sensible default
1468 private boolean prefIndentBracesForBlocks() {
1469 Plugin plugin = JavaCore.getPlugin();
1470 if (plugin != null) {
1471 String option = JavaCore
1472 .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_BLOCK);
1474 .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1477 return false; // sensible default
1480 private boolean prefIndentBracesForArrays() {
1481 Plugin plugin = JavaCore.getPlugin();
1482 if (plugin != null) {
1483 String option = JavaCore
1484 .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_ARRAY_INITIALIZER);
1486 .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1489 return false; // sensible default
1492 private boolean prefIndentBracesForMethods() {
1493 Plugin plugin = JavaCore.getPlugin();
1494 if (plugin != null) {
1495 String option = JavaCore
1496 .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_METHOD_DECLARATION);
1498 .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1501 return false; // sensible default
1504 private int prefContinuationIndent() {
1505 Plugin plugin = JavaCore.getPlugin();
1506 if (plugin != null) {
1507 String option = JavaCore
1508 .getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
1510 return Integer.parseInt(option);
1511 } catch (NumberFormatException e) {
1512 // ignore and return default
1516 return 2; // sensible default