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.jface.text.ITypedRegion;
23 import org.eclipse.jface.text.TextUtilities;
24 import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
27 * Uses the {@link net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner}to
28 * get the indentation level for a certain position in a document.
31 * An instance holds some internal position in the document and is therefore not
37 public class JavaIndenter {
39 /** The document being scanned. */
40 private IDocument fDocument;
42 /** The indentation accumulated by <code>findPreviousIndenationUnit</code>. */
46 * The absolute (character-counted) indentation offset for special cases
47 * (method defs, array initializers)
51 /** The stateful scanposition for the indentation methods. */
52 private int fPosition;
54 /** The previous position. */
55 private int fPreviousPos;
57 /** The most recent token. */
60 /** The line of <code>fPosition</code>. */
64 * The scanner we will use to scan the document. It has to be installed on
65 * the same document as the one we get.
67 private JavaHeuristicScanner fScanner;
70 * Creates a new instance.
73 * the document to scan
75 * the {@link JavaHeuristicScanner} to be used for scanning the
76 * document. It must be installed on the same
77 * <code>IDocument</code>.
79 public JavaIndenter(IDocument document, JavaHeuristicScanner scanner) {
80 Assert.isNotNull(document);
81 Assert.isNotNull(scanner);
87 * Computes the indentation at the reference point of <code>position</code>.
90 * the offset in the document
91 * @return a String which reflects the indentation at the line in which the
92 * reference position to <code>offset</code> resides, or
93 * <code>null</code> if it cannot be determined
95 public StringBuffer getReferenceIndentation(int offset) {
96 return getReferenceIndentation(offset, false);
100 * Computes the indentation at the reference point of <code>position</code>.
103 * the offset in the document
104 * @param assumeOpeningBrace
105 * <code>true</code> if an opening brace should be assumed
106 * @return a String which reflects the indentation at the line in which the
107 * reference position to <code>offset</code> resides, or
108 * <code>null</code> if it cannot be determined
110 private StringBuffer getReferenceIndentation(int offset,
111 boolean assumeOpeningBrace) {
114 if (assumeOpeningBrace)
115 unit = findReferencePosition(offset, Symbols.TokenLBRACE);
117 unit = findReferencePosition(offset, peekChar(offset));
119 // if we were unable to find anything, return null
120 if (unit == JavaHeuristicScanner.NOT_FOUND)
123 return getLeadingWhitespace(unit);
128 * Computes the indentation at <code>offset</code>.
131 * the offset in the document
132 * @return a String which reflects the correct indentation for the line in
133 * which offset resides, or <code>null</code> if it cannot be
136 public StringBuffer computeIndentation(int offset) {
137 return computeIndentation(offset, false);
141 * Computes the indentation at <code>offset</code>.
144 * the offset in the document
145 * @param assumeOpeningBrace
146 * <code>true</code> if an opening brace should be assumed
147 * @return a String which reflects the correct indentation for the line in
148 * which offset resides, or <code>null</code> if it cannot be
151 public StringBuffer computeIndentation(int offset,
152 boolean assumeOpeningBrace) {
154 StringBuffer indent = getReferenceIndentation(offset,
157 // handle special alignment
158 if (fAlign != JavaHeuristicScanner.NOT_FOUND) {
160 // a special case has been detected.
161 IRegion line = fDocument.getLineInformationOfOffset(fAlign);
162 int lineOffset = line.getOffset();
163 return createIndent(lineOffset, fAlign);
164 } catch (BadLocationException e) {
172 // add additional indent
173 //indent.append(createIndent(fIndent));
174 indent.insert(0, createIndent(fIndent));
182 * Returns the indentation of the line at <code>offset</code> as a
183 * <code>StringBuffer</code>. If the offset is not valid, the empty
184 * string is returned.
187 * the offset in the document
188 * @return the indentation (leading whitespace) of the line in which
189 * <code>offset</code> is located
191 private StringBuffer getLeadingWhitespace(int offset) {
192 StringBuffer indent = new StringBuffer();
194 IRegion line = fDocument.getLineInformationOfOffset(offset);
195 int lineOffset = line.getOffset();
196 int nonWS = fScanner.findNonWhitespaceForwardInAnyPartition(
197 lineOffset, lineOffset + line.getLength());
198 indent.append(fDocument.get(lineOffset, nonWS - lineOffset));
200 } catch (BadLocationException e) {
206 * Reduces indentation in <code>indent</code> by one indentation unit.
209 * the indentation to be modified
211 private void unindent(StringBuffer indent) {
212 CharSequence oneIndent = createIndent();
213 int i = indent.lastIndexOf(oneIndent.toString()); //$NON-NLS-1$
215 indent.delete(i, i + oneIndent.length());
220 * Creates an indentation string of the length indent - start + 1,
221 * consisting of the content in <code>fDocument</code> in the range
222 * [start, indent), with every character replaced by a space except for
223 * tabs, which are kept as such.
226 * Every run of the number of spaces that make up a tab are replaced by a
230 * @return the indentation corresponding to the document content specified
231 * by <code>start</code> and <code>indent</code>
233 private StringBuffer createIndent(int start, int indent) {
234 final int tabLen = prefTabLength();
235 StringBuffer ret = new StringBuffer();
238 while (start < indent) {
240 char ch = fDocument.getChar(start);
244 } else if (tabLen == -1) {
248 if (spaces == tabLen) {
257 if (spaces == tabLen)
263 } catch (BadLocationException e) {
270 * Creates a string that represents the given number of indents (can be
274 * the requested indentation level.
276 * @return the indentation specified by <code>indent</code>
278 public StringBuffer createIndent(int indent) {
279 StringBuffer oneIndent = createIndent();
281 StringBuffer ret = new StringBuffer();
283 ret.append(oneIndent);
289 * Creates a string that represents one indent (can be spaces or tabs..)
291 * @return one indentation
293 private StringBuffer createIndent() {
294 // get a sensible default when running without the infrastructure for
296 StringBuffer oneIndent = new StringBuffer();
297 // JavaCore plugin= JavaCore.getJavaCore();
298 PHPeclipsePlugin plugin = PHPeclipsePlugin.getDefault();
299 if (plugin == null) {
300 oneIndent.append('\t');
304 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) {
307 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE));
308 for (int i = 0; i < tabLen; i++)
309 oneIndent.append(' ');
310 } else if (JavaCore.TAB
312 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
313 oneIndent.append('\t');
315 oneIndent.append('\t'); // default
321 * Returns the reference position regarding to indentation for
322 * <code>offset</code>, or <code>NOT_FOUND</code>. This method calls
323 * {@link #findReferencePosition(int, int) findReferencePosition(offset, nextChar)}
324 * where <code>nextChar</code> is the next character after
325 * <code>offset</code>.
328 * the offset for which the reference is computed
329 * @return the reference statement relative to which <code>offset</code>
330 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
332 public int findReferencePosition(int offset) {
333 return findReferencePosition(offset, peekChar(offset));
337 * Peeks the next char in the document that comes after <code>offset</code>
338 * on the same line as <code>offset</code>.
341 * the offset into document
342 * @return the token symbol of the next element, or TokenEOF if there is
345 private int peekChar(int offset) {
346 if (offset < fDocument.getLength()) {
348 IRegion line = fDocument.getLineInformationOfOffset(offset);
349 int lineOffset = line.getOffset();
350 int next = fScanner.nextToken(offset, lineOffset
353 } catch (BadLocationException e) {
356 return Symbols.TokenEOF;
360 * Returns the reference position regarding to indentation for
361 * <code>position</code>, or <code>NOT_FOUND</code>.
364 * If <code>peekNextChar</code> is <code>true</code>, the next token
365 * after <code>offset</code> is read and taken into account when computing
366 * the indentation. Currently, if the next token is the first token on the
367 * line (i.e. only preceded by whitespace), the following tokens are
370 * <li><code>switch</code> labels are indented relative to the switch
372 * <li>opening curly braces are aligned correctly with the introducing code</li>
373 * <li>closing curly braces are aligned properly with the introducing code
374 * of the matching opening brace</li>
375 * <li>closing parenthesis' are aligned with their opening peer</li>
376 * <li>the <code>else</code> keyword is aligned with its <code>if</code>,
377 * anything else is aligned normally (i.e. with the base of any introducing
379 * <li>if there is no token on the same line after <code>offset</code>,
380 * the indentation is the same as for an <code>else</code> keyword</li>
384 * the offset for which the reference is computed
386 * the next token to assume in the document
387 * @return the reference statement relative to which <code>offset</code>
388 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
390 public int findReferencePosition(int offset, int nextToken) {
391 boolean danglingElse = false;
392 boolean unindent = false;
393 boolean indent = false;
394 boolean matchBrace = false;
395 boolean matchParen = false;
396 boolean matchCase = false;
398 // account for unindenation characters already typed in, but after
400 // if they are on a line by themselves, the indentation gets adjusted
403 // also account for a dangling else
404 if (offset < fDocument.getLength()) {
406 IRegion line = fDocument.getLineInformationOfOffset(offset);
407 int lineOffset = line.getOffset();
408 int prevPos = Math.max(offset - 1, 0);
409 boolean isFirstTokenOnLine = fDocument.get(lineOffset,
410 prevPos + 1 - lineOffset).trim().length() == 0;
411 int prevToken = fScanner.previousToken(prevPos,
412 JavaHeuristicScanner.UNBOUND);
413 if (prevToken == Symbols.TokenEOF && nextToken == Symbols.TokenEOF) {
414 ITypedRegion partition = TextUtilities.getPartition(fDocument, IPHPPartitions.PHP_PARTITIONING, offset, true);
415 if (partition.getType().equals(IPHPPartitions.PHP_SINGLELINE_COMMENT)) {
416 fAlign = fScanner.getPosition();
418 fAlign = JavaHeuristicScanner.NOT_FOUND;
420 return JavaHeuristicScanner.NOT_FOUND;
422 boolean bracelessBlockStart = fScanner.isBracelessBlockStart(
423 prevPos, JavaHeuristicScanner.UNBOUND);
426 case Symbols.TokenEOF:
427 case Symbols.TokenELSE:
430 case Symbols.TokenCASE:
431 case Symbols.TokenDEFAULT:
432 if (isFirstTokenOnLine)
435 case Symbols.TokenLBRACE: // for opening-brace-on-new-line
437 // if (bracelessBlockStart && !prefIndentBracesForBlocks())
439 // else if ((prevToken == Symbols.TokenCOLON || prevToken ==
440 // Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET) &&
441 // !prefIndentBracesForArrays())
443 // else if (!bracelessBlockStart &&
444 // prefIndentBracesForMethods())
447 if (bracelessBlockStart)
449 else if ((prevToken == Symbols.TokenCOLON
450 || prevToken == Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET))
452 else if (!bracelessBlockStart)
455 case Symbols.TokenRBRACE: // closing braces get unindented
456 if (isFirstTokenOnLine)
459 case Symbols.TokenRPAREN:
460 if (isFirstTokenOnLine)
464 } catch (BadLocationException e) {
467 // assume an else could come if we are at the end of file
471 int ref = findReferencePosition(offset, danglingElse, matchBrace,
472 matchParen, matchCase);
481 * Returns the reference position regarding to indentation for
482 * <code>position</code>, or <code>NOT_FOUND</code>.<code>fIndent</code>
483 * will contain the relative indentation (in indentation units, not
484 * characters) after the call. If there is a special alignment (e.g. for a
485 * method declaration where parameters should be aligned),
486 * <code>fAlign</code> will contain the absolute position of the alignment
487 * reference in <code>fDocument</code>, otherwise <code>fAlign</code>
488 * is set to <code>JavaHeuristicScanner.NOT_FOUND</code>.
491 * the offset for which the reference is computed
492 * @param danglingElse
493 * whether a dangling else should be assumed at
494 * <code>position</code>
496 * whether the position of the matching brace should be returned
497 * instead of doing code analysis
499 * whether the position of the matching parenthesis should be
500 * returned instead of doing code analysis
502 * whether the position of a switch statement reference should be
503 * returned (either an earlier case statement or the switch block
505 * @return the reference statement relative to which <code>position</code>
506 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
508 public int findReferencePosition(int offset, boolean danglingElse,
509 boolean matchBrace, boolean matchParen, boolean matchCase) {
510 fIndent = 0; // the indentation modification
511 fAlign = JavaHeuristicScanner.NOT_FOUND;
515 // an unindentation happens sometimes if the next token is special,
516 // namely on braces, parens and case labels
517 // align braces, but handle the case where we align with the method
518 // declaration start instead of
519 // the opening brace.
521 if (skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE)) {
523 // align with the opening brace that is on a line by its own
524 int lineOffset = fDocument.getLineOffset(fLine);
525 if (lineOffset <= fPosition
527 .get(lineOffset, fPosition - lineOffset)
528 .trim().length() == 0)
530 } catch (BadLocationException e) {
531 // concurrent modification - walk default path
533 // if the opening brace is not on the start of the line, skip to
535 int pos = skipToStatementStart(true, true);
536 fIndent = 0; // indent is aligned with reference position
539 // if we can't find the matching brace, the heuristic is to
541 // by one against the normal position
542 int pos = findReferencePosition(offset, danglingElse, false,
543 matchParen, matchCase);
549 // align parenthesis'
551 if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN))
554 // if we can't find the matching paren, the heuristic is to
556 // by one against the normal position
557 int pos = findReferencePosition(offset, danglingElse,
558 matchBrace, false, matchCase);
564 // the only reliable way to get case labels aligned (due to many
565 // different styles of using braces in a block)
566 // is to go for another case statement, or the scope opening brace
568 return matchCaseAlignment();
573 case Symbols.TokenRBRACE:
574 // skip the block and fall through
575 // if we can't complete the scope, reset the scan position
579 case Symbols.TokenSEMICOLON:
580 // this is the 90% case: after a statement block
581 // the end of the previous statement / block previous.end
582 // search to the end of the statement / block before the previous;
583 // the token just after that is previous.start
584 return skipToStatementStart(danglingElse, false);
586 // scope introduction: special treat who special is
587 case Symbols.TokenLPAREN:
588 case Symbols.TokenLBRACE:
589 case Symbols.TokenLBRACKET:
590 return handleScopeIntroduction(offset + 1);
592 case Symbols.TokenEOF:
593 // trap when hitting start of document
596 case Symbols.TokenEQUAL:
597 // indent assignments
598 fIndent = prefAssignmentIndent();
601 case Symbols.TokenCOLON:
602 // TODO handle ternary deep indentation
603 fIndent = prefCaseBlockIndent();
606 case Symbols.TokenQUESTIONMARK:
607 if (prefTernaryDeepAlign()) {
608 setFirstElementAlignment(fPosition, offset + 1);
611 fIndent = prefTernaryIndent();
615 // indentation for blockless introducers:
616 case Symbols.TokenDO:
617 case Symbols.TokenWHILE:
618 case Symbols.TokenELSE:
619 fIndent = prefSimpleIndent();
621 case Symbols.TokenRPAREN:
623 if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN)) {
624 int scope = fPosition;
626 if (fToken == Symbols.TokenIF || fToken == Symbols.TokenWHILE
627 || fToken == Symbols.TokenFOR) {
628 fIndent = prefSimpleIndent();
632 if (looksLikeMethodDecl()) {
633 return skipToStatementStart(danglingElse, false);
639 // else: fall through to default
641 case Symbols.TokenCOMMA:
642 // inside a list of some type
643 // easy if there is already a list item before with its own
644 // indentation - we just align
645 // if not: take the start of the list ( LPAREN, LBRACE, LBRACKET )
646 // and either align or
647 // indent by list-indent
649 // inside whatever we don't know about: similar to the list case:
650 // if we are inside a continued expression, then either align with a
651 // previous line that has indentation
652 // or indent from the expression start line (either a scope
653 // introducer or the start of the expr).
654 return skipToPreviousListItemOrListStart();
660 * Skips to the start of a statement that ends at the current position.
662 * @param danglingElse
663 * whether to indent aligned with the last <code>if</code>
665 * whether the current position is inside a block, which limits
666 * the search scope to the next scope introducer
667 * @return the reference offset of the start of the statement
669 private int skipToStatementStart(boolean danglingElse, boolean isInBlock) {
675 // exit on all block introducers
676 case Symbols.TokenIF:
677 case Symbols.TokenELSE:
678 case Symbols.TokenSYNCHRONIZED:
679 case Symbols.TokenCOLON:
680 case Symbols.TokenSTATIC:
681 case Symbols.TokenCATCH:
682 case Symbols.TokenDO:
683 case Symbols.TokenWHILE:
684 case Symbols.TokenFINALLY:
685 case Symbols.TokenFOR:
686 case Symbols.TokenTRY:
689 case Symbols.TokenSWITCH:
690 fIndent = prefCaseIndent();
696 // scope introduction through: LPAREN, LBRACE, LBRACKET
697 // search stop on SEMICOLON, RBRACE, COLON, EOF
698 // -> the next token is the start of the statement (i.e. previousPos
699 // when backward scanning)
700 case Symbols.TokenLPAREN:
701 case Symbols.TokenLBRACE:
702 case Symbols.TokenLBRACKET:
703 case Symbols.TokenSEMICOLON:
704 case Symbols.TokenEOF:
707 case Symbols.TokenCOLON:
708 int pos = fPreviousPos;
709 if (!isConditional())
713 case Symbols.TokenRBRACE:
714 // RBRACE is a little tricky: it can be the end of an array
716 // usually it is the end of a previous block
717 pos = fPreviousPos; // store state
718 if (skipScope() && looksLikeArrayInitializerIntro())
719 continue; // it's an array
721 return pos; // it's not - do as with all the above
724 case Symbols.TokenRPAREN:
725 case Symbols.TokenRBRACKET:
732 // IF / ELSE: align the position after the conditional block
734 // so we are ready for an else, except if danglingElse is false
735 // in order for this to work, we must skip an else to its if
736 case Symbols.TokenIF:
741 case Symbols.TokenELSE:
742 // skip behind the next if, as we have that one covered
749 case Symbols.TokenDO:
750 // align the WHILE position with its do
753 case Symbols.TokenWHILE:
754 // this one is tricky: while can be the start of a while loop
755 // or the end of a do - while
757 if (hasMatchingDo()) {
758 // continue searching from the DO on
761 // continue searching from the WHILE on
774 * Returns true if the colon at the current position is part of a
775 * conditional (ternary) expression, false otherwise.
777 * @return true if the colon at the current position is part of a
780 private boolean isConditional() {
785 // search for case, otherwise return true
786 case Symbols.TokenIDENT:
788 case Symbols.TokenCASE:
798 * Returns as a reference any previous <code>switch</code> labels (<code>case</code>
799 * or <code>default</code>) or the offset of the brace that scopes the
800 * switch statement. Sets <code>fIndent</code> to
801 * <code>prefCaseIndent</code> upon a match.
803 * @return the reference offset for a <code>switch</code> label
805 private int matchCaseAlignment() {
809 // invalid cases: another case label or an LBRACE must come before a
811 // -> bail out with the current position
812 case Symbols.TokenLPAREN:
813 case Symbols.TokenLBRACKET:
814 case Symbols.TokenEOF:
816 case Symbols.TokenLBRACE:
817 // opening brace of switch statement
818 fIndent = 1; //prefCaseIndent() is for Java
820 case Symbols.TokenCASE:
821 case Symbols.TokenDEFAULT:
822 // align with previous label
826 case Symbols.TokenRPAREN:
827 case Symbols.TokenRBRACKET:
828 case Symbols.TokenRBRACE:
839 * Returns the reference position for a list element. The algorithm tries to
840 * match any previous indentation on the same list. If there is none, the
841 * reference position returned is determined depending on the type of list:
842 * The indentation will either match the list scope introducer (e.g. for
843 * method declarations), so called deep indents, or simply increase the
844 * indentation by a number of standard indents. See also
845 * {@link #handleScopeIntroduction(int)}.
847 * @return the reference position for a list item: either a previous list
848 * item that has its own indentation, or the list introduction
851 private int skipToPreviousListItemOrListStart() {
852 int startLine = fLine;
853 int startPosition = fPosition;
857 // if any line item comes with its own indentation, adapt to it
858 if (fLine < startLine) {
860 int lineOffset = fDocument.getLineOffset(startLine);
861 int bound = Math.min(fDocument.getLength(),
863 fAlign = fScanner.findNonWhitespaceForwardInAnyPartition(
865 } catch (BadLocationException e) {
866 // ignore and return just the position
868 return startPosition;
873 case Symbols.TokenRPAREN:
874 case Symbols.TokenRBRACKET:
875 case Symbols.TokenRBRACE:
879 // scope introduction: special treat who special is
880 case Symbols.TokenLPAREN:
881 case Symbols.TokenLBRACE:
882 case Symbols.TokenLBRACKET:
883 return handleScopeIntroduction(startPosition + 1);
885 case Symbols.TokenSEMICOLON:
887 case Symbols.TokenQUESTIONMARK:
888 if (prefTernaryDeepAlign()) {
889 setFirstElementAlignment(fPosition - 1, fPosition + 1);
891 fIndent = prefTernaryIndent();
894 case Symbols.TokenEOF:
897 case Symbols.TokenEQUAL:
898 // indent assignments
899 fIndent= prefAssignmentIndent();
906 * Skips a scope and positions the cursor (<code>fPosition</code>) on
907 * the token that opens the scope. Returns <code>true</code> if a matching
908 * peer could be found, <code>false</code> otherwise. The current token
909 * when calling must be one out of <code>Symbols.TokenRPAREN</code>,
910 * <code>Symbols.TokenRBRACE</code>, and
911 * <code>Symbols.TokenRBRACKET</code>.
913 * @return <code>true</code> if a matching peer was found,
914 * <code>false</code> otherwise
916 private boolean skipScope() {
918 case Symbols.TokenRPAREN:
919 return skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN);
920 case Symbols.TokenRBRACKET:
921 return skipScope(Symbols.TokenLBRACKET, Symbols.TokenRBRACKET);
922 case Symbols.TokenRBRACE:
923 return skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE);
925 Assert.isTrue(false);
931 * Handles the introduction of a new scope. The current token must be one
932 * out of <code>Symbols.TokenLPAREN</code>,
933 * <code>Symbols.TokenLBRACE</code>, and
934 * <code>Symbols.TokenLBRACKET</code>. Returns as the reference position
935 * either the token introducing the scope or - if available - the first java
939 * Depending on the type of scope introduction, the indentation will align
940 * (deep indenting) with the reference position (<code>fAlign</code> will
941 * be set to the reference position) or <code>fIndent</code> will be set
942 * to the number of indentation units.
946 * the bound for the search for the first token after the scope
950 private int handleScopeIntroduction(int bound) {
952 // scope introduction: special treat who special is
953 case Symbols.TokenLPAREN:
954 int pos = fPosition; // store
956 // special: method declaration deep indentation
957 if (looksLikeMethodDecl()) {
958 if (prefMethodDeclDeepIndent())
959 return setFirstElementAlignment(pos, bound);
961 fIndent = prefMethodDeclIndent();
966 if (looksLikeMethodCall()) {
967 if (prefMethodCallDeepIndent())
968 return setFirstElementAlignment(pos, bound);
970 fIndent = prefMethodCallIndent();
973 } else if (prefParenthesisDeepIndent())
974 return setFirstElementAlignment(pos, bound);
977 // normal: return the parenthesis as reference
978 fIndent = prefParenthesisIndent();
981 case Symbols.TokenLBRACE:
982 pos = fPosition; // store
984 // special: array initializer
985 if (looksLikeArrayInitializerIntro())
986 if (prefArrayDeepIndent())
987 return setFirstElementAlignment(pos, bound);
989 fIndent = prefArrayIndent();
991 fIndent = prefBlockIndent();
993 // normal: skip to the statement start before the scope introducer
994 // opening braces are often on differently ending indents than e.g.
995 // a method definition
996 fPosition = pos; // restore
997 return skipToStatementStart(true, true); // set to true to match
1000 case Symbols.TokenLBRACKET:
1001 pos = fPosition; // store
1003 // special: method declaration deep indentation
1004 if (prefArrayDimensionsDeepIndent()) {
1005 return setFirstElementAlignment(pos, bound);
1008 // normal: return the bracket as reference
1009 fIndent = prefBracketIndent();
1010 return pos; // restore
1013 Assert.isTrue(false);
1019 * Sets the deep indent offset (<code>fAlign</code>) to either the
1020 * offset right after <code>scopeIntroducerOffset</code> or - if available -
1021 * the first Java token after <code>scopeIntroducerOffset</code>, but
1022 * before <code>bound</code>.
1024 * @param scopeIntroducerOffset
1025 * the offset of the scope introducer
1027 * the bound for the search for another element
1028 * @return the reference position
1030 private int setFirstElementAlignment(int scopeIntroducerOffset, int bound) {
1031 int firstPossible = scopeIntroducerOffset + 1; // align with the first
1032 // position after the
1034 fAlign = fScanner.findNonWhitespaceForwardInAnyPartition(firstPossible,
1036 if (fAlign == JavaHeuristicScanner.NOT_FOUND)
1037 fAlign = firstPossible;
1042 * Returns <code>true</code> if the next token received after calling
1043 * <code>nextToken</code> is either an equal sign or an array designator
1046 * @return <code>true</code> if the next elements look like the start of
1047 * an array definition
1049 private boolean looksLikeArrayInitializerIntro() {
1051 if (fToken == Symbols.TokenEQUAL || skipBrackets()) {
1058 * Skips over the next <code>if</code> keyword. The current token when
1059 * calling this method must be an <code>else</code> keyword. Returns
1060 * <code>true</code> if a matching <code>if</code> could be found,
1061 * <code>false</code> otherwise. The cursor (<code>fPosition</code>)
1062 * is set to the offset of the <code>if</code> token.
1064 * @return <code>true</code> if a matching <code>if</code> token was
1065 * found, <code>false</code> otherwise
1067 private boolean skipNextIF() {
1068 Assert.isTrue(fToken == Symbols.TokenELSE);
1073 // scopes: skip them
1074 case Symbols.TokenRPAREN:
1075 case Symbols.TokenRBRACKET:
1076 case Symbols.TokenRBRACE:
1080 case Symbols.TokenIF:
1083 case Symbols.TokenELSE:
1084 // recursively skip else-if blocks
1088 // shortcut scope starts
1089 case Symbols.TokenLPAREN:
1090 case Symbols.TokenLBRACE:
1091 case Symbols.TokenLBRACKET:
1092 case Symbols.TokenEOF:
1099 * while(condition); is ambiguous when parsed backwardly, as it is a valid
1100 * statement by its own, so we have to check whether there is a matching do.
1101 * A <code>do</code> can either be separated from the while by a block, or
1102 * by a single statement, which limits our search distance.
1104 * @return <code>true</code> if the <code>while</code> currently in
1105 * <code>fToken</code> has a matching <code>do</code>.
1107 private boolean hasMatchingDo() {
1108 Assert.isTrue(fToken == Symbols.TokenWHILE);
1111 case Symbols.TokenRBRACE:
1112 skipScope(); // and fall thru
1113 case Symbols.TokenSEMICOLON:
1114 skipToStatementStart(false, false);
1115 return fToken == Symbols.TokenDO;
1121 * Skips brackets if the current token is a RBRACKET. There can be nothing
1122 * but whitespace in between, this is only to be used for <code>[]</code>
1125 * @return <code>true</code> if a <code>[]</code> could be scanned, the
1126 * current token is left at the LBRACKET.
1128 private boolean skipBrackets() {
1129 if (fToken == Symbols.TokenRBRACKET) {
1131 if (fToken == Symbols.TokenLBRACKET) {
1139 * Reads the next token in backward direction from the heuristic scanner and
1140 * sets the fields <code>fToken, fPreviousPosition</code> and
1141 * <code>fPosition</code> accordingly.
1143 private void nextToken() {
1144 nextToken(fPosition);
1148 * Reads the next token in backward direction of <code>start</code> from
1149 * the heuristic scanner and sets the fields
1150 * <code>fToken, fPreviousPosition</code> and <code>fPosition</code>
1153 private void nextToken(int start) {
1155 .previousToken(start - 1, JavaHeuristicScanner.UNBOUND);
1156 fPreviousPos = start;
1157 fPosition = fScanner.getPosition() + 1;
1159 fLine = fDocument.getLineOfOffset(fPosition);
1160 } catch (BadLocationException e) {
1166 * Returns <code>true</code> if the current tokens look like a method
1167 * declaration header (i.e. only the return type and method name). The
1168 * heuristic calls <code>nextToken</code> and expects an identifier
1169 * (method name) and a type declaration (an identifier with optional
1170 * brackets) which also covers the visibility modifier of constructors; it
1171 * does not recognize package visible constructors.
1173 * @return <code>true</code> if the current position looks like a method
1174 * declaration header.
1176 private boolean looksLikeMethodDecl() {
1178 * TODO This heuristic does not recognize package private constructors
1179 * since those do have neither type nor visibility keywords. One option
1180 * would be to go over the parameter list, but that might be empty as
1181 * well - hard to do without an AST...
1185 if (fToken == Symbols.TokenIDENT) { // method name
1188 while (skipBrackets()); // optional brackets for array valued return
1190 return fToken == Symbols.TokenIDENT; // type name
1197 * Returns <code>true</code> if the current tokens look like a method call
1198 * header (i.e. an identifier as opposed to a keyword taking parenthesized
1199 * parameters such as <code>if</code>).
1201 * The heuristic calls <code>nextToken</code> and expects an identifier
1204 * @return <code>true</code> if the current position looks like a method
1207 private boolean looksLikeMethodCall() {
1209 return fToken == Symbols.TokenIDENT; // method name
1213 * Scans tokens for the matching opening peer. The internal cursor (<code>fPosition</code>)
1214 * is set to the offset of the opening peer if found.
1216 * @return <code>true</code> if a matching token was found,
1217 * <code>false</code> otherwise
1219 private boolean skipScope(int openToken, int closeToken) {
1226 if (fToken == closeToken) {
1228 } else if (fToken == openToken) {
1232 } else if (fToken == Symbols.TokenEOF) {
1238 // TODO adjust once there are per-project settings
1240 private int prefTabLength() {
1242 // JavaCore core= JavaCore.getJavaCore();
1243 PHPeclipsePlugin plugin = PHPeclipsePlugin.getDefault();
1244 // if (core != null && plugin != null)
1248 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
1249 // if the formatter uses chars to mark indentation, then don't
1250 // substitute any chars
1251 tabLen = -1; // results in no tabs being substituted for
1254 // if the formatter uses tabs to mark indentations, use the
1255 // visual setting from the editor
1256 // to get nicely aligned indentations
1258 .getPreferenceStore()
1260 AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
1262 tabLen = 4; // sensible default for testing
1267 private boolean prefArrayDimensionsDeepIndent() {
1268 return true; // sensible default
1271 private int prefArrayIndent() {
1272 Plugin plugin = JavaCore.getPlugin();
1273 if (plugin != null) {
1274 String option = JavaCore
1275 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
1277 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1279 } catch (IllegalArgumentException e) {
1280 // ignore and return default
1284 return prefContinuationIndent(); // default
1287 private boolean prefArrayDeepIndent() {
1288 Plugin plugin = JavaCore.getPlugin();
1289 if (plugin != null) {
1290 String option = JavaCore
1291 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
1293 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1294 } catch (IllegalArgumentException e) {
1295 // ignore and return default
1302 private boolean prefTernaryDeepAlign() {
1303 Plugin plugin = JavaCore.getPlugin();
1304 if (plugin != null) {
1305 String option = JavaCore
1306 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
1308 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1309 } catch (IllegalArgumentException e) {
1310 // ignore and return default
1316 private int prefTernaryIndent() {
1317 Plugin plugin = JavaCore.getPlugin();
1318 if (plugin != null) {
1319 String option = JavaCore
1320 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
1322 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1325 return prefContinuationIndent();
1326 } catch (IllegalArgumentException e) {
1327 // ignore and return default
1331 return prefContinuationIndent();
1334 private int prefCaseIndent() {
1335 Plugin plugin = JavaCore.getPlugin();
1336 if (plugin != null) {
1337 if (DefaultCodeFormatterConstants.TRUE
1339 .getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_SWITCH)))
1340 return prefBlockIndent();
1345 return 0; // sun standard
1348 private int prefAssignmentIndent() {
1349 return prefBlockIndent();
1352 private int prefCaseBlockIndent() {
1354 return prefBlockIndent();
1356 Plugin plugin = JavaCore.getPlugin();
1357 if (plugin != null) {
1358 if (DefaultCodeFormatterConstants.TRUE
1360 .getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_CASES)))
1361 return prefBlockIndent();
1365 return prefBlockIndent(); // sun standard
1368 private int prefSimpleIndent() {
1369 return prefBlockIndent();
1372 private int prefBracketIndent() {
1373 return prefBlockIndent();
1376 private boolean prefMethodDeclDeepIndent() {
1377 Plugin plugin = JavaCore.getPlugin();
1378 if (plugin != null) {
1379 String option = JavaCore
1380 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
1382 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1383 } catch (IllegalArgumentException e) {
1384 // ignore and return default
1391 private int prefMethodDeclIndent() {
1392 Plugin plugin = JavaCore.getPlugin();
1393 if (plugin != null) {
1394 String option = JavaCore
1395 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
1397 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1400 return prefContinuationIndent();
1401 } catch (IllegalArgumentException e) {
1402 // ignore and return default
1408 private boolean prefMethodCallDeepIndent() {
1409 Plugin plugin = JavaCore.getPlugin();
1410 if (plugin != null) {
1411 String option = JavaCore
1412 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
1414 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1415 } catch (IllegalArgumentException e) {
1416 // ignore and return default
1419 return false; // sensible default
1422 private int prefMethodCallIndent() {
1423 Plugin plugin = JavaCore.getPlugin();
1424 if (plugin != null) {
1425 String option = JavaCore
1426 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
1428 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1431 return prefContinuationIndent();
1432 } catch (IllegalArgumentException e) {
1433 // ignore and return default
1437 return 1; // sensible default
1440 private boolean prefParenthesisDeepIndent() {
1442 if (true) // don't do parenthesis deep indentation
1445 Plugin plugin = JavaCore.getPlugin();
1446 if (plugin != null) {
1447 String option = JavaCore
1448 .getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
1450 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1451 } catch (IllegalArgumentException e) {
1452 // ignore and return default
1456 return false; // sensible default
1459 private int prefParenthesisIndent() {
1460 return prefContinuationIndent();
1463 private int prefBlockIndent() {
1464 return 1; // sensible default
1467 private boolean prefIndentBracesForBlocks() {
1468 Plugin plugin = JavaCore.getPlugin();
1469 if (plugin != null) {
1470 String option = JavaCore
1471 .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_BLOCK);
1473 .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1476 return false; // sensible default
1479 private boolean prefIndentBracesForArrays() {
1480 Plugin plugin = JavaCore.getPlugin();
1481 if (plugin != null) {
1482 String option = JavaCore
1483 .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_ARRAY_INITIALIZER);
1485 .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1488 return false; // sensible default
1491 private boolean prefIndentBracesForMethods() {
1492 Plugin plugin = JavaCore.getPlugin();
1493 if (plugin != null) {
1494 String option = JavaCore
1495 .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_METHOD_DECLARATION);
1497 .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1500 return false; // sensible default
1503 private int prefContinuationIndent() {
1504 Plugin plugin = JavaCore.getPlugin();
1505 if (plugin != null) {
1506 String option = JavaCore
1507 .getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
1509 return Integer.parseInt(option);
1510 } catch (NumberFormatException e) {
1511 // ignore and return default
1515 return 2; // sensible default