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;
20 //import org.eclipse.jface.text.Assert;
21 import org.eclipse.core.runtime.Assert;
22 import org.eclipse.jface.text.BadLocationException;
23 import org.eclipse.jface.text.IDocument;
24 import org.eclipse.jface.text.IRegion;
25 import org.eclipse.jface.text.ITypedRegion;
26 import org.eclipse.jface.text.TextUtilities;
27 import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
30 * Uses the {@link net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner}to
31 * get the indentation level for a certain position in a document.
34 * An instance holds some internal position in the document and is therefore not
40 public class JavaIndenter {
42 /** The document being scanned. */
43 private IDocument fDocument;
45 /** The indentation accumulated by <code>findPreviousIndenationUnit</code>. */
49 * The absolute (character-counted) indentation offset for special cases
50 * (method defs, array initializers)
54 /** The stateful scanposition for the indentation methods. */
55 private int fPosition;
57 /** The previous position. */
58 private int fPreviousPos;
60 /** The most recent token. */
63 /** The line of <code>fPosition</code>. */
67 * The scanner we will use to scan the document. It has to be installed on
68 * the same document as the one we get.
70 private JavaHeuristicScanner fScanner;
73 * Creates a new instance.
76 * the document to scan
78 * the {@link JavaHeuristicScanner} to be used for scanning the
79 * document. It must be installed on the same
80 * <code>IDocument</code>.
82 public JavaIndenter(IDocument document, JavaHeuristicScanner scanner) {
83 Assert.isNotNull(document);
84 Assert.isNotNull(scanner);
90 * Computes the indentation at the reference point of <code>position</code>.
93 * the offset in the document
94 * @return a String which reflects the indentation at the line in which the
95 * reference position to <code>offset</code> resides, or
96 * <code>null</code> if it cannot be determined
98 public StringBuffer getReferenceIndentation(int offset) {
99 return getReferenceIndentation(offset, false);
103 * Computes the indentation at the reference point of <code>position</code>.
106 * the offset in the document
107 * @param assumeOpeningBrace
108 * <code>true</code> if an opening brace should be assumed
109 * @return a String which reflects the indentation at the line in which the
110 * reference position to <code>offset</code> resides, or
111 * <code>null</code> if it cannot be determined
113 private StringBuffer getReferenceIndentation(int offset,
114 boolean assumeOpeningBrace) {
117 if (assumeOpeningBrace)
118 unit = findReferencePosition(offset, Symbols.TokenLBRACE);
120 unit = findReferencePosition(offset, peekChar(offset));
122 // if we were unable to find anything, return null
123 if (unit == JavaHeuristicScanner.NOT_FOUND)
126 return getLeadingWhitespace(unit);
131 * Computes the indentation at <code>offset</code>.
134 * the offset in the document
135 * @return a String which reflects the correct indentation for the line in
136 * which offset resides, or <code>null</code> if it cannot be
139 public StringBuffer computeIndentation(int offset) {
140 return computeIndentation(offset, false);
144 * Computes the indentation at <code>offset</code>.
147 * the offset in the document
148 * @param assumeOpeningBrace
149 * <code>true</code> if an opening brace should be assumed
150 * @return a String which reflects the correct indentation for the line in
151 * which offset resides, or <code>null</code> if it cannot be
154 public StringBuffer computeIndentation(int offset,
155 boolean assumeOpeningBrace) {
157 StringBuffer indent = getReferenceIndentation(offset,
160 // handle special alignment
161 if (fAlign != JavaHeuristicScanner.NOT_FOUND) {
163 // a special case has been detected.
164 IRegion line = fDocument.getLineInformationOfOffset(fAlign);
165 int lineOffset = line.getOffset();
166 return createIndent(lineOffset, fAlign);
167 } catch (BadLocationException e) {
175 // add additional indent
176 //indent.append(createIndent(fIndent));
177 indent.insert(0, createIndent(fIndent));
185 * Returns the indentation of the line at <code>offset</code> as a
186 * <code>StringBuffer</code>. If the offset is not valid, the empty
187 * string is returned.
190 * the offset in the document
191 * @return the indentation (leading whitespace) of the line in which
192 * <code>offset</code> is located
194 private StringBuffer getLeadingWhitespace(int offset) {
195 StringBuffer indent = new StringBuffer();
197 IRegion line = fDocument.getLineInformationOfOffset(offset);
198 int lineOffset = line.getOffset();
199 int nonWS = fScanner.findNonWhitespaceForwardInAnyPartition(
200 lineOffset, lineOffset + line.getLength());
201 indent.append(fDocument.get(lineOffset, nonWS - lineOffset));
203 } catch (BadLocationException e) {
209 * Reduces indentation in <code>indent</code> by one indentation unit.
212 * the indentation to be modified
214 private void unindent(StringBuffer indent) {
215 CharSequence oneIndent = createIndent();
216 int i = indent.lastIndexOf(oneIndent.toString()); //$NON-NLS-1$
218 indent.delete(i, i + oneIndent.length());
223 * Creates an indentation string of the length indent - start + 1,
224 * consisting of the content in <code>fDocument</code> in the range
225 * [start, indent), with every character replaced by a space except for
226 * tabs, which are kept as such.
229 * Every run of the number of spaces that make up a tab are replaced by a
233 * @return the indentation corresponding to the document content specified
234 * by <code>start</code> and <code>indent</code>
236 private StringBuffer createIndent(int start, int indent) {
237 final int tabLen = prefTabLength();
238 StringBuffer ret = new StringBuffer();
241 while (start < indent) {
243 char ch = fDocument.getChar(start);
247 } else if (tabLen == -1) {
251 if (spaces == tabLen) {
260 if (spaces == tabLen)
266 } catch (BadLocationException e) {
273 * Creates a string that represents the given number of indents (can be
277 * the requested indentation level.
279 * @return the indentation specified by <code>indent</code>
281 public StringBuffer createIndent(int indent) {
282 StringBuffer oneIndent = createIndent();
284 StringBuffer ret = new StringBuffer();
286 ret.append(oneIndent);
292 * Creates a string that represents one indent (can be spaces or tabs..)
294 * @return one indentation
296 private StringBuffer createIndent() {
297 // get a sensible default when running without the infrastructure for
299 StringBuffer oneIndent = new StringBuffer();
300 // JavaCore plugin= JavaCore.getJavaCore();
301 WebUI plugin = WebUI.getDefault();
302 if (plugin == null) {
303 oneIndent.append('\t');
307 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) {
310 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE));
311 for (int i = 0; i < tabLen; i++)
312 oneIndent.append(' ');
313 } else if (JavaCore.TAB
315 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
316 oneIndent.append('\t');
318 oneIndent.append('\t'); // default
324 * Returns the reference position regarding to indentation for
325 * <code>offset</code>, or <code>NOT_FOUND</code>. This method calls
326 * {@link #findReferencePosition(int, int) findReferencePosition(offset, nextChar)}
327 * where <code>nextChar</code> is the next character after
328 * <code>offset</code>.
331 * the offset for which the reference is computed
332 * @return the reference statement relative to which <code>offset</code>
333 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
335 public int findReferencePosition(int offset) {
336 return findReferencePosition(offset, peekChar(offset));
340 * Peeks the next char in the document that comes after <code>offset</code>
341 * on the same line as <code>offset</code>.
344 * the offset into document
345 * @return the token symbol of the next element, or TokenEOF if there is
348 private int peekChar(int offset) {
349 if (offset < fDocument.getLength()) {
351 IRegion line = fDocument.getLineInformationOfOffset(offset);
352 int lineOffset = line.getOffset();
353 int next = fScanner.nextToken(offset, lineOffset
356 } catch (BadLocationException e) {
359 return Symbols.TokenEOF;
363 * Returns the reference position regarding to indentation for
364 * <code>position</code>, or <code>NOT_FOUND</code>.
367 * If <code>peekNextChar</code> is <code>true</code>, the next token
368 * after <code>offset</code> is read and taken into account when computing
369 * the indentation. Currently, if the next token is the first token on the
370 * line (i.e. only preceded by whitespace), the following tokens are
373 * <li><code>switch</code> labels are indented relative to the switch
375 * <li>opening curly braces are aligned correctly with the introducing code</li>
376 * <li>closing curly braces are aligned properly with the introducing code
377 * of the matching opening brace</li>
378 * <li>closing parenthesis' are aligned with their opening peer</li>
379 * <li>the <code>else</code> keyword is aligned with its <code>if</code>,
380 * anything else is aligned normally (i.e. with the base of any introducing
382 * <li>if there is no token on the same line after <code>offset</code>,
383 * the indentation is the same as for an <code>else</code> keyword</li>
387 * the offset for which the reference is computed
389 * the next token to assume in the document
390 * @return the reference statement relative to which <code>offset</code>
391 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
393 public int findReferencePosition(int offset, int nextToken) {
394 boolean danglingElse = false;
395 boolean unindent = false;
396 boolean indent = false;
397 boolean matchBrace = false;
398 boolean matchParen = false;
399 boolean matchCase = false;
401 // account for unindenation characters already typed in, but after
403 // if they are on a line by themselves, the indentation gets adjusted
406 // also account for a dangling else
407 if (offset < fDocument.getLength()) {
409 IRegion line = fDocument.getLineInformationOfOffset(offset);
410 int lineOffset = line.getOffset();
411 int prevPos = Math.max(offset - 1, 0);
412 boolean isFirstTokenOnLine = fDocument.get(lineOffset,
413 prevPos + 1 - lineOffset).trim().length() == 0;
414 int prevToken = fScanner.previousToken(prevPos,
415 JavaHeuristicScanner.UNBOUND);
416 if (prevToken == Symbols.TokenEOF && nextToken == Symbols.TokenEOF) {
417 ITypedRegion partition = TextUtilities.getPartition(fDocument, IPHPPartitions.PHP_PARTITIONING, offset, true);
418 if (partition.getType().equals(IPHPPartitions.PHP_SINGLELINE_COMMENT)) {
419 fAlign = fScanner.getPosition();
421 fAlign = JavaHeuristicScanner.NOT_FOUND;
423 return JavaHeuristicScanner.NOT_FOUND;
425 boolean bracelessBlockStart = fScanner.isBracelessBlockStart(
426 prevPos, JavaHeuristicScanner.UNBOUND);
429 case Symbols.TokenEOF:
430 case Symbols.TokenELSE:
433 case Symbols.TokenCASE:
434 case Symbols.TokenDEFAULT:
435 if (isFirstTokenOnLine)
438 case Symbols.TokenLBRACE: // for opening-brace-on-new-line
440 // if (bracelessBlockStart && !prefIndentBracesForBlocks())
442 // else if ((prevToken == Symbols.TokenCOLON || prevToken ==
443 // Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET) &&
444 // !prefIndentBracesForArrays())
446 // else if (!bracelessBlockStart &&
447 // prefIndentBracesForMethods())
450 if (bracelessBlockStart)
452 else if ((prevToken == Symbols.TokenCOLON
453 || prevToken == Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET))
455 else if (!bracelessBlockStart)
458 case Symbols.TokenRBRACE: // closing braces get unindented
459 if (isFirstTokenOnLine)
462 case Symbols.TokenRPAREN:
463 if (isFirstTokenOnLine)
467 } catch (BadLocationException e) {
470 // assume an else could come if we are at the end of file
474 int ref = findReferencePosition(offset, danglingElse, matchBrace,
475 matchParen, matchCase);
484 * Returns the reference position regarding to indentation for
485 * <code>position</code>, or <code>NOT_FOUND</code>.<code>fIndent</code>
486 * will contain the relative indentation (in indentation units, not
487 * characters) after the call. If there is a special alignment (e.g. for a
488 * method declaration where parameters should be aligned),
489 * <code>fAlign</code> will contain the absolute position of the alignment
490 * reference in <code>fDocument</code>, otherwise <code>fAlign</code>
491 * is set to <code>JavaHeuristicScanner.NOT_FOUND</code>.
494 * the offset for which the reference is computed
495 * @param danglingElse
496 * whether a dangling else should be assumed at
497 * <code>position</code>
499 * whether the position of the matching brace should be returned
500 * instead of doing code analysis
502 * whether the position of the matching parenthesis should be
503 * returned instead of doing code analysis
505 * whether the position of a switch statement reference should be
506 * returned (either an earlier case statement or the switch block
508 * @return the reference statement relative to which <code>position</code>
509 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
511 public int findReferencePosition(int offset, boolean danglingElse,
512 boolean matchBrace, boolean matchParen, boolean matchCase) {
513 fIndent = 0; // the indentation modification
514 fAlign = JavaHeuristicScanner.NOT_FOUND;
518 // an unindentation happens sometimes if the next token is special,
519 // namely on braces, parens and case labels
520 // align braces, but handle the case where we align with the method
521 // declaration start instead of
522 // the opening brace.
524 if (skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE)) {
526 // align with the opening brace that is on a line by its own
527 int lineOffset = fDocument.getLineOffset(fLine);
528 if (lineOffset <= fPosition
530 .get(lineOffset, fPosition - lineOffset)
531 .trim().length() == 0)
533 } catch (BadLocationException e) {
534 // concurrent modification - walk default path
536 // if the opening brace is not on the start of the line, skip to
538 int pos = skipToStatementStart(true, true);
539 fIndent = 0; // indent is aligned with reference position
542 // if we can't find the matching brace, the heuristic is to
544 // by one against the normal position
545 int pos = findReferencePosition(offset, danglingElse, false,
546 matchParen, matchCase);
552 // align parenthesis'
554 if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN))
557 // if we can't find the matching paren, the heuristic is to
559 // by one against the normal position
560 int pos = findReferencePosition(offset, danglingElse,
561 matchBrace, false, matchCase);
567 // the only reliable way to get case labels aligned (due to many
568 // different styles of using braces in a block)
569 // is to go for another case statement, or the scope opening brace
571 return matchCaseAlignment();
576 case Symbols.TokenRBRACE:
577 // skip the block and fall through
578 // if we can't complete the scope, reset the scan position
582 case Symbols.TokenSEMICOLON:
583 // this is the 90% case: after a statement block
584 // the end of the previous statement / block previous.end
585 // search to the end of the statement / block before the previous;
586 // the token just after that is previous.start
587 return skipToStatementStart(danglingElse, false);
589 // scope introduction: special treat who special is
590 case Symbols.TokenLPAREN:
591 case Symbols.TokenLBRACE:
592 case Symbols.TokenLBRACKET:
593 return handleScopeIntroduction(offset + 1);
595 case Symbols.TokenEOF:
596 // trap when hitting start of document
599 case Symbols.TokenEQUAL:
600 // indent assignments
601 fIndent = prefAssignmentIndent();
604 case Symbols.TokenCOLON:
605 // TODO handle ternary deep indentation
606 fIndent = prefCaseBlockIndent();
609 case Symbols.TokenQUESTIONMARK:
610 if (prefTernaryDeepAlign()) {
611 setFirstElementAlignment(fPosition, offset + 1);
614 fIndent = prefTernaryIndent();
618 // indentation for blockless introducers:
619 case Symbols.TokenDO:
620 case Symbols.TokenWHILE:
621 case Symbols.TokenELSE:
622 fIndent = prefSimpleIndent();
624 case Symbols.TokenRPAREN:
626 if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN)) {
627 int scope = fPosition;
629 if (fToken == Symbols.TokenIF || fToken == Symbols.TokenWHILE
630 || fToken == Symbols.TokenFOR) {
631 fIndent = prefSimpleIndent();
635 if (looksLikeMethodDecl()) {
636 return skipToStatementStart(danglingElse, false);
642 // else: fall through to default
644 case Symbols.TokenCOMMA:
645 // inside a list of some type
646 // easy if there is already a list item before with its own
647 // indentation - we just align
648 // if not: take the start of the list ( LPAREN, LBRACE, LBRACKET )
649 // and either align or
650 // indent by list-indent
652 // inside whatever we don't know about: similar to the list case:
653 // if we are inside a continued expression, then either align with a
654 // previous line that has indentation
655 // or indent from the expression start line (either a scope
656 // introducer or the start of the expr).
657 return skipToPreviousListItemOrListStart();
663 * Skips to the start of a statement that ends at the current position.
665 * @param danglingElse
666 * whether to indent aligned with the last <code>if</code>
668 * whether the current position is inside a block, which limits
669 * the search scope to the next scope introducer
670 * @return the reference offset of the start of the statement
672 private int skipToStatementStart(boolean danglingElse, boolean isInBlock) {
678 // exit on all block introducers
679 case Symbols.TokenIF:
680 case Symbols.TokenELSE:
681 case Symbols.TokenSYNCHRONIZED:
682 case Symbols.TokenCOLON:
683 case Symbols.TokenSTATIC:
684 case Symbols.TokenCATCH:
685 case Symbols.TokenDO:
686 case Symbols.TokenWHILE:
687 case Symbols.TokenFINALLY:
688 case Symbols.TokenFOR:
689 case Symbols.TokenTRY:
692 case Symbols.TokenSWITCH:
693 fIndent = prefCaseIndent();
699 // scope introduction through: LPAREN, LBRACE, LBRACKET
700 // search stop on SEMICOLON, RBRACE, COLON, EOF
701 // -> the next token is the start of the statement (i.e. previousPos
702 // when backward scanning)
703 case Symbols.TokenLPAREN:
704 case Symbols.TokenLBRACE:
705 case Symbols.TokenLBRACKET:
706 case Symbols.TokenSEMICOLON:
707 case Symbols.TokenEOF:
710 case Symbols.TokenCOLON:
711 int pos = fPreviousPos;
712 if (!isConditional())
716 case Symbols.TokenRBRACE:
717 // RBRACE is a little tricky: it can be the end of an array
719 // usually it is the end of a previous block
720 pos = fPreviousPos; // store state
721 if (skipScope() && looksLikeArrayInitializerIntro())
722 continue; // it's an array
724 return pos; // it's not - do as with all the above
727 case Symbols.TokenRPAREN:
728 case Symbols.TokenRBRACKET:
735 // IF / ELSE: align the position after the conditional block
737 // so we are ready for an else, except if danglingElse is false
738 // in order for this to work, we must skip an else to its if
739 case Symbols.TokenIF:
744 case Symbols.TokenELSE:
745 // skip behind the next if, as we have that one covered
752 case Symbols.TokenDO:
753 // align the WHILE position with its do
756 case Symbols.TokenWHILE:
757 // this one is tricky: while can be the start of a while loop
758 // or the end of a do - while
760 if (hasMatchingDo()) {
761 // continue searching from the DO on
764 // continue searching from the WHILE on
777 * Returns true if the colon at the current position is part of a
778 * conditional (ternary) expression, false otherwise.
780 * @return true if the colon at the current position is part of a
783 private boolean isConditional() {
788 // search for case, otherwise return true
789 case Symbols.TokenIDENT:
791 case Symbols.TokenCASE:
801 * Returns as a reference any previous <code>switch</code> labels (<code>case</code>
802 * or <code>default</code>) or the offset of the brace that scopes the
803 * switch statement. Sets <code>fIndent</code> to
804 * <code>prefCaseIndent</code> upon a match.
806 * @return the reference offset for a <code>switch</code> label
808 private int matchCaseAlignment() {
812 // invalid cases: another case label or an LBRACE must come before a
814 // -> bail out with the current position
815 case Symbols.TokenLPAREN:
816 case Symbols.TokenLBRACKET:
817 case Symbols.TokenEOF:
819 case Symbols.TokenLBRACE:
820 // opening brace of switch statement
821 fIndent = 1; //prefCaseIndent() is for Java
823 case Symbols.TokenCASE:
824 case Symbols.TokenDEFAULT:
825 // align with previous label
829 case Symbols.TokenRPAREN:
830 case Symbols.TokenRBRACKET:
831 case Symbols.TokenRBRACE:
842 * Returns the reference position for a list element. The algorithm tries to
843 * match any previous indentation on the same list. If there is none, the
844 * reference position returned is determined depending on the type of list:
845 * The indentation will either match the list scope introducer (e.g. for
846 * method declarations), so called deep indents, or simply increase the
847 * indentation by a number of standard indents. See also
848 * {@link #handleScopeIntroduction(int)}.
850 * @return the reference position for a list item: either a previous list
851 * item that has its own indentation, or the list introduction
854 private int skipToPreviousListItemOrListStart() {
855 int startLine = fLine;
856 int startPosition = fPosition;
860 // if any line item comes with its own indentation, adapt to it
861 if (fLine < startLine) {
863 int lineOffset = fDocument.getLineOffset(startLine);
864 int bound = Math.min(fDocument.getLength(),
866 fAlign = fScanner.findNonWhitespaceForwardInAnyPartition(
868 } catch (BadLocationException e) {
869 // ignore and return just the position
871 return startPosition;
876 case Symbols.TokenRPAREN:
877 case Symbols.TokenRBRACKET:
878 case Symbols.TokenRBRACE:
882 // scope introduction: special treat who special is
883 case Symbols.TokenLPAREN:
884 case Symbols.TokenLBRACE:
885 case Symbols.TokenLBRACKET:
886 return handleScopeIntroduction(startPosition + 1);
888 case Symbols.TokenSEMICOLON:
890 case Symbols.TokenQUESTIONMARK:
891 if (prefTernaryDeepAlign()) {
892 setFirstElementAlignment(fPosition - 1, fPosition + 1);
894 fIndent = prefTernaryIndent();
897 case Symbols.TokenEOF:
900 case Symbols.TokenEQUAL:
901 // indent assignments
902 fIndent= prefAssignmentIndent();
909 * Skips a scope and positions the cursor (<code>fPosition</code>) on
910 * the token that opens the scope. Returns <code>true</code> if a matching
911 * peer could be found, <code>false</code> otherwise. The current token
912 * when calling must be one out of <code>Symbols.TokenRPAREN</code>,
913 * <code>Symbols.TokenRBRACE</code>, and
914 * <code>Symbols.TokenRBRACKET</code>.
916 * @return <code>true</code> if a matching peer was found,
917 * <code>false</code> otherwise
919 private boolean skipScope() {
921 case Symbols.TokenRPAREN:
922 return skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN);
923 case Symbols.TokenRBRACKET:
924 return skipScope(Symbols.TokenLBRACKET, Symbols.TokenRBRACKET);
925 case Symbols.TokenRBRACE:
926 return skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE);
928 Assert.isTrue(false);
934 * Handles the introduction of a new scope. The current token must be one
935 * out of <code>Symbols.TokenLPAREN</code>,
936 * <code>Symbols.TokenLBRACE</code>, and
937 * <code>Symbols.TokenLBRACKET</code>. Returns as the reference position
938 * either the token introducing the scope or - if available - the first java
942 * Depending on the type of scope introduction, the indentation will align
943 * (deep indenting) with the reference position (<code>fAlign</code> will
944 * be set to the reference position) or <code>fIndent</code> will be set
945 * to the number of indentation units.
949 * the bound for the search for the first token after the scope
953 private int handleScopeIntroduction(int bound) {
955 // scope introduction: special treat who special is
956 case Symbols.TokenLPAREN:
957 int pos = fPosition; // store
959 // special: method declaration deep indentation
960 if (looksLikeMethodDecl()) {
961 if (prefMethodDeclDeepIndent())
962 return setFirstElementAlignment(pos, bound);
964 fIndent = prefMethodDeclIndent();
969 if (looksLikeMethodCall()) {
970 if (prefMethodCallDeepIndent())
971 return setFirstElementAlignment(pos, bound);
973 fIndent = prefMethodCallIndent();
976 } else if (prefParenthesisDeepIndent())
977 return setFirstElementAlignment(pos, bound);
980 // normal: return the parenthesis as reference
981 fIndent = prefParenthesisIndent();
984 case Symbols.TokenLBRACE:
985 pos = fPosition; // store
987 // special: array initializer
988 if (looksLikeArrayInitializerIntro())
989 if (prefArrayDeepIndent())
990 return setFirstElementAlignment(pos, bound);
992 fIndent = prefArrayIndent();
994 fIndent = prefBlockIndent();
996 // normal: skip to the statement start before the scope introducer
997 // opening braces are often on differently ending indents than e.g.
998 // a method definition
999 fPosition = pos; // restore
1000 return skipToStatementStart(true, true); // set to true to match
1003 case Symbols.TokenLBRACKET:
1004 pos = fPosition; // store
1006 // special: method declaration deep indentation
1007 if (prefArrayDimensionsDeepIndent()) {
1008 return setFirstElementAlignment(pos, bound);
1011 // normal: return the bracket as reference
1012 fIndent = prefBracketIndent();
1013 return pos; // restore
1016 Assert.isTrue(false);
1022 * Sets the deep indent offset (<code>fAlign</code>) to either the
1023 * offset right after <code>scopeIntroducerOffset</code> or - if available -
1024 * the first Java token after <code>scopeIntroducerOffset</code>, but
1025 * before <code>bound</code>.
1027 * @param scopeIntroducerOffset
1028 * the offset of the scope introducer
1030 * the bound for the search for another element
1031 * @return the reference position
1033 private int setFirstElementAlignment(int scopeIntroducerOffset, int bound) {
1034 int firstPossible = scopeIntroducerOffset + 1; // align with the first
1035 // position after the
1037 fAlign = fScanner.findNonWhitespaceForwardInAnyPartition(firstPossible,
1039 if (fAlign == JavaHeuristicScanner.NOT_FOUND)
1040 fAlign = firstPossible;
1045 * Returns <code>true</code> if the next token received after calling
1046 * <code>nextToken</code> is either an equal sign or an array designator
1049 * @return <code>true</code> if the next elements look like the start of
1050 * an array definition
1052 private boolean looksLikeArrayInitializerIntro() {
1054 if (fToken == Symbols.TokenEQUAL || skipBrackets()) {
1061 * Skips over the next <code>if</code> keyword. The current token when
1062 * calling this method must be an <code>else</code> keyword. Returns
1063 * <code>true</code> if a matching <code>if</code> could be found,
1064 * <code>false</code> otherwise. The cursor (<code>fPosition</code>)
1065 * is set to the offset of the <code>if</code> token.
1067 * @return <code>true</code> if a matching <code>if</code> token was
1068 * found, <code>false</code> otherwise
1070 private boolean skipNextIF() {
1071 Assert.isTrue(fToken == Symbols.TokenELSE);
1076 // scopes: skip them
1077 case Symbols.TokenRPAREN:
1078 case Symbols.TokenRBRACKET:
1079 case Symbols.TokenRBRACE:
1083 case Symbols.TokenIF:
1086 case Symbols.TokenELSE:
1087 // recursively skip else-if blocks
1091 // shortcut scope starts
1092 case Symbols.TokenLPAREN:
1093 case Symbols.TokenLBRACE:
1094 case Symbols.TokenLBRACKET:
1095 case Symbols.TokenEOF:
1102 * while(condition); is ambiguous when parsed backwardly, as it is a valid
1103 * statement by its own, so we have to check whether there is a matching do.
1104 * A <code>do</code> can either be separated from the while by a block, or
1105 * by a single statement, which limits our search distance.
1107 * @return <code>true</code> if the <code>while</code> currently in
1108 * <code>fToken</code> has a matching <code>do</code>.
1110 private boolean hasMatchingDo() {
1111 Assert.isTrue(fToken == Symbols.TokenWHILE);
1114 case Symbols.TokenRBRACE:
1115 skipScope(); // and fall thru
1116 case Symbols.TokenSEMICOLON:
1117 skipToStatementStart(false, false);
1118 return fToken == Symbols.TokenDO;
1124 * Skips brackets if the current token is a RBRACKET. There can be nothing
1125 * but whitespace in between, this is only to be used for <code>[]</code>
1128 * @return <code>true</code> if a <code>[]</code> could be scanned, the
1129 * current token is left at the LBRACKET.
1131 private boolean skipBrackets() {
1132 if (fToken == Symbols.TokenRBRACKET) {
1134 if (fToken == Symbols.TokenLBRACKET) {
1142 * Reads the next token in backward direction from the heuristic scanner and
1143 * sets the fields <code>fToken, fPreviousPosition</code> and
1144 * <code>fPosition</code> accordingly.
1146 private void nextToken() {
1147 nextToken(fPosition);
1151 * Reads the next token in backward direction of <code>start</code> from
1152 * the heuristic scanner and sets the fields
1153 * <code>fToken, fPreviousPosition</code> and <code>fPosition</code>
1156 private void nextToken(int start) {
1158 .previousToken(start - 1, JavaHeuristicScanner.UNBOUND);
1159 fPreviousPos = start;
1160 fPosition = fScanner.getPosition() + 1;
1162 fLine = fDocument.getLineOfOffset(fPosition);
1163 } catch (BadLocationException e) {
1169 * Returns <code>true</code> if the current tokens look like a method
1170 * declaration header (i.e. only the return type and method name). The
1171 * heuristic calls <code>nextToken</code> and expects an identifier
1172 * (method name) and a type declaration (an identifier with optional
1173 * brackets) which also covers the visibility modifier of constructors; it
1174 * does not recognize package visible constructors.
1176 * @return <code>true</code> if the current position looks like a method
1177 * declaration header.
1179 private boolean looksLikeMethodDecl() {
1181 * TODO This heuristic does not recognize package private constructors
1182 * since those do have neither type nor visibility keywords. One option
1183 * would be to go over the parameter list, but that might be empty as
1184 * well - hard to do without an AST...
1188 if (fToken == Symbols.TokenIDENT) { // method name
1191 while (skipBrackets()); // optional brackets for array valued return
1193 return fToken == Symbols.TokenIDENT; // type name
1200 * Returns <code>true</code> if the current tokens look like a method call
1201 * header (i.e. an identifier as opposed to a keyword taking parenthesized
1202 * parameters such as <code>if</code>).
1204 * The heuristic calls <code>nextToken</code> and expects an identifier
1207 * @return <code>true</code> if the current position looks like a method
1210 private boolean looksLikeMethodCall() {
1212 return fToken == Symbols.TokenIDENT; // method name
1216 * Scans tokens for the matching opening peer. The internal cursor (<code>fPosition</code>)
1217 * is set to the offset of the opening peer if found.
1219 * @return <code>true</code> if a matching token was found,
1220 * <code>false</code> otherwise
1222 private boolean skipScope(int openToken, int closeToken) {
1229 if (fToken == closeToken) {
1231 } else if (fToken == openToken) {
1235 } else if (fToken == Symbols.TokenEOF) {
1241 // TODO adjust once there are per-project settings
1243 private int prefTabLength() {
1245 // JavaCore core= JavaCore.getJavaCore();
1246 WebUI plugin = WebUI.getDefault();
1247 // if (core != null && plugin != null)
1251 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
1252 // if the formatter uses chars to mark indentation, then don't
1253 // substitute any chars
1254 tabLen = -1; // results in no tabs being substituted for
1257 // if the formatter uses tabs to mark indentations, use the
1258 // visual setting from the editor
1259 // to get nicely aligned indentations
1261 .getPreferenceStore()
1263 AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
1265 tabLen = 4; // sensible default for testing
1270 private boolean prefArrayDimensionsDeepIndent() {
1271 return true; // sensible default
1274 private int prefArrayIndent() {
1275 Plugin plugin = JavaCore.getPlugin();
1276 if (plugin != null) {
1277 String option = JavaCore
1278 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
1280 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1282 } catch (IllegalArgumentException e) {
1283 // ignore and return default
1287 return prefContinuationIndent(); // default
1290 private boolean prefArrayDeepIndent() {
1291 Plugin plugin = JavaCore.getPlugin();
1292 if (plugin != null) {
1293 String option = JavaCore
1294 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
1296 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1297 } catch (IllegalArgumentException e) {
1298 // ignore and return default
1305 private boolean prefTernaryDeepAlign() {
1306 Plugin plugin = JavaCore.getPlugin();
1307 if (plugin != null) {
1308 String option = JavaCore
1309 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
1311 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1312 } catch (IllegalArgumentException e) {
1313 // ignore and return default
1319 private int prefTernaryIndent() {
1320 Plugin plugin = JavaCore.getPlugin();
1321 if (plugin != null) {
1322 String option = JavaCore
1323 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
1325 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1328 return prefContinuationIndent();
1329 } catch (IllegalArgumentException e) {
1330 // ignore and return default
1334 return prefContinuationIndent();
1337 private int prefCaseIndent() {
1338 Plugin plugin = JavaCore.getPlugin();
1339 if (plugin != null) {
1340 if (DefaultCodeFormatterConstants.TRUE
1342 .getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_SWITCH)))
1343 return prefBlockIndent();
1348 return 0; // sun standard
1351 private int prefAssignmentIndent() {
1352 return prefBlockIndent();
1355 private int prefCaseBlockIndent() {
1357 return prefBlockIndent();
1359 Plugin plugin = JavaCore.getPlugin();
1360 if (plugin != null) {
1361 if (DefaultCodeFormatterConstants.TRUE
1363 .getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_CASES)))
1364 return prefBlockIndent();
1368 return prefBlockIndent(); // sun standard
1371 private int prefSimpleIndent() {
1372 return prefBlockIndent();
1375 private int prefBracketIndent() {
1376 return prefBlockIndent();
1379 private boolean prefMethodDeclDeepIndent() {
1380 Plugin plugin = JavaCore.getPlugin();
1381 if (plugin != null) {
1382 String option = JavaCore
1383 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
1385 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1386 } catch (IllegalArgumentException e) {
1387 // ignore and return default
1394 private int prefMethodDeclIndent() {
1395 Plugin plugin = JavaCore.getPlugin();
1396 if (plugin != null) {
1397 String option = JavaCore
1398 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
1400 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1403 return prefContinuationIndent();
1404 } catch (IllegalArgumentException e) {
1405 // ignore and return default
1411 private boolean prefMethodCallDeepIndent() {
1412 Plugin plugin = JavaCore.getPlugin();
1413 if (plugin != null) {
1414 String option = JavaCore
1415 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
1417 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1418 } catch (IllegalArgumentException e) {
1419 // ignore and return default
1422 return false; // sensible default
1425 private int prefMethodCallIndent() {
1426 Plugin plugin = JavaCore.getPlugin();
1427 if (plugin != null) {
1428 String option = JavaCore
1429 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
1431 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1434 return prefContinuationIndent();
1435 } catch (IllegalArgumentException e) {
1436 // ignore and return default
1440 return 1; // sensible default
1443 private boolean prefParenthesisDeepIndent() {
1445 if (true) // don't do parenthesis deep indentation
1448 Plugin plugin = JavaCore.getPlugin();
1449 if (plugin != null) {
1450 String option = JavaCore
1451 .getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
1453 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1454 } catch (IllegalArgumentException e) {
1455 // ignore and return default
1459 return false; // sensible default
1462 private int prefParenthesisIndent() {
1463 return prefContinuationIndent();
1466 private int prefBlockIndent() {
1467 return 1; // sensible default
1470 private boolean prefIndentBracesForBlocks() {
1471 Plugin plugin = JavaCore.getPlugin();
1472 if (plugin != null) {
1473 String option = JavaCore
1474 .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_BLOCK);
1476 .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1479 return false; // sensible default
1482 private boolean prefIndentBracesForArrays() {
1483 Plugin plugin = JavaCore.getPlugin();
1484 if (plugin != null) {
1485 String option = JavaCore
1486 .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_ARRAY_INITIALIZER);
1488 .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1491 return false; // sensible default
1494 private boolean prefIndentBracesForMethods() {
1495 Plugin plugin = JavaCore.getPlugin();
1496 if (plugin != null) {
1497 String option = JavaCore
1498 .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_METHOD_DECLARATION);
1500 .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1503 return false; // sensible default
1506 private int prefContinuationIndent() {
1507 Plugin plugin = JavaCore.getPlugin();
1508 if (plugin != null) {
1509 String option = JavaCore
1510 .getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
1512 return Integer.parseInt(option);
1513 } catch (NumberFormatException e) {
1514 // ignore and return default
1518 return 2; // sensible default