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;
19 //import org.eclipse.jface.text.Assert;
20 import org.eclipse.core.runtime.Assert;
21 import org.eclipse.jface.text.BadLocationException;
22 import org.eclipse.jface.text.IDocument;
23 import org.eclipse.jface.text.IRegion;
24 import org.eclipse.jface.text.ITypedRegion;
25 import org.eclipse.jface.text.TextUtilities;
26 import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
29 * Uses the {@link net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner}to
30 * get the indentation level for a certain position in a document.
33 * An instance holds some internal position in the document and is therefore not
39 public class JavaIndenter {
41 /** The document being scanned. */
42 private IDocument fDocument;
44 /** The indentation accumulated by <code>findPreviousIndenationUnit</code>. */
48 * The absolute (character-counted) indentation offset for special cases
49 * (method defs, array initializers)
53 /** The stateful scanposition for the indentation methods. */
54 private int fPosition;
56 /** The previous position. */
57 private int fPreviousPos;
59 /** The most recent token. */
62 /** The line of <code>fPosition</code>. */
66 * The scanner we will use to scan the document. It has to be installed on
67 * the same document as the one we get.
69 private JavaHeuristicScanner fScanner;
72 * Creates a new instance.
75 * the document to scan
77 * the {@link JavaHeuristicScanner} to be used for scanning the
78 * document. It must be installed on the same
79 * <code>IDocument</code>.
81 public JavaIndenter(IDocument document, JavaHeuristicScanner scanner) {
82 Assert.isNotNull(document);
83 Assert.isNotNull(scanner);
89 * Computes the indentation at the reference point of <code>position</code>.
92 * the offset in the document
93 * @return a String which reflects the indentation at the line in which the
94 * reference position to <code>offset</code> resides, or
95 * <code>null</code> if it cannot be determined
97 // public StringBuffer getReferenceIndentation(int offset) {
98 // return getReferenceIndentation(offset, false);
102 * Computes the indentation at the reference point of <code>position</code>.
105 * the offset in the document
106 * @param assumeOpeningBrace
107 * <code>true</code> if an opening brace should be assumed
108 * @return a String which reflects the indentation at the line in which the
109 * reference position to <code>offset</code> resides, or
110 * <code>null</code> if it cannot be determined
112 private StringBuffer getReferenceIndentation(int offset,
113 boolean assumeOpeningBrace) {
116 if (assumeOpeningBrace)
117 unit = findReferencePosition(offset, Symbols.TokenLBRACE);
119 unit = findReferencePosition(offset, peekChar(offset));
121 // if we were unable to find anything, return null
122 if (unit == JavaHeuristicScanner.NOT_FOUND)
125 return getLeadingWhitespace(unit);
130 * Computes the indentation at <code>offset</code>.
133 * the offset in the document
134 * @return a String which reflects the correct indentation for the line in
135 * which offset resides, or <code>null</code> if it cannot be
138 public StringBuffer computeIndentation(int offset) {
139 return computeIndentation(offset, false);
143 * Computes the indentation at <code>offset</code>.
146 * the offset in the document
147 * @param assumeOpeningBrace
148 * <code>true</code> if an opening brace should be assumed
149 * @return a String which reflects the correct indentation for the line in
150 * which offset resides, or <code>null</code> if it cannot be
153 public StringBuffer computeIndentation(int offset,
154 boolean assumeOpeningBrace) {
156 StringBuffer indent = getReferenceIndentation(offset,
159 // handle special alignment
160 if (fAlign != JavaHeuristicScanner.NOT_FOUND) {
162 // a special case has been detected.
163 IRegion line = fDocument.getLineInformationOfOffset(fAlign);
164 int lineOffset = line.getOffset();
165 return createIndent(lineOffset, fAlign);
166 } catch (BadLocationException e) {
174 // add additional indent
175 //indent.append(createIndent(fIndent));
176 indent.insert(0, createIndent(fIndent));
184 * Returns the indentation of the line at <code>offset</code> as a
185 * <code>StringBuffer</code>. If the offset is not valid, the empty
186 * string is returned.
189 * the offset in the document
190 * @return the indentation (leading whitespace) of the line in which
191 * <code>offset</code> is located
193 private StringBuffer getLeadingWhitespace(int offset) {
194 StringBuffer indent = new StringBuffer();
196 IRegion line = fDocument.getLineInformationOfOffset(offset);
197 int lineOffset = line.getOffset();
198 int nonWS = fScanner.findNonWhitespaceForwardInAnyPartition(
199 lineOffset, lineOffset + line.getLength());
200 indent.append(fDocument.get(lineOffset, nonWS - lineOffset));
202 } catch (BadLocationException e) {
208 * Reduces indentation in <code>indent</code> by one indentation unit.
211 * the indentation to be modified
213 private void unindent(StringBuffer indent) {
214 CharSequence oneIndent = createIndent();
215 int i = indent.lastIndexOf(oneIndent.toString()); //$NON-NLS-1$
217 indent.delete(i, i + oneIndent.length());
222 * Creates an indentation string of the length indent - start + 1,
223 * consisting of the content in <code>fDocument</code> in the range
224 * [start, indent), with every character replaced by a space except for
225 * tabs, which are kept as such.
228 * Every run of the number of spaces that make up a tab are replaced by a
232 * @return the indentation corresponding to the document content specified
233 * by <code>start</code> and <code>indent</code>
235 private StringBuffer createIndent(int start, int indent) {
236 final int tabLen = prefTabLength();
237 StringBuffer ret = new StringBuffer();
240 while (start < indent) {
242 char ch = fDocument.getChar(start);
246 } else if (tabLen == -1) {
250 if (spaces == tabLen) {
259 if (spaces == tabLen)
265 } catch (BadLocationException e) {
272 * Creates a string that represents the given number of indents (can be
276 * the requested indentation level.
278 * @return the indentation specified by <code>indent</code>
280 public StringBuffer createIndent(int indent) {
281 StringBuffer oneIndent = createIndent();
283 StringBuffer ret = new StringBuffer();
285 ret.append(oneIndent);
291 * Creates a string that represents one indent (can be spaces or tabs..)
293 * @return one indentation
295 private StringBuffer createIndent() {
296 // get a sensible default when running without the infrastructure for
298 StringBuffer oneIndent = new StringBuffer();
299 // JavaCore plugin= JavaCore.getJavaCore();
300 PHPeclipsePlugin plugin = PHPeclipsePlugin.getDefault();
301 if (plugin == null) {
302 oneIndent.append('\t');
306 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) {
309 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE));
310 for (int i = 0; i < tabLen; i++)
311 oneIndent.append(' ');
312 } else if (JavaCore.TAB
314 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
315 oneIndent.append('\t');
317 oneIndent.append('\t'); // default
323 * Returns the reference position regarding to indentation for
324 * <code>offset</code>, or <code>NOT_FOUND</code>. This method calls
325 * {@link #findReferencePosition(int, int) findReferencePosition(offset, nextChar)}
326 * where <code>nextChar</code> is the next character after
327 * <code>offset</code>.
330 * the offset for which the reference is computed
331 * @return the reference statement relative to which <code>offset</code>
332 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
334 // public int findReferencePosition(int offset) {
335 // return findReferencePosition(offset, peekChar(offset));
339 * Peeks the next char in the document that comes after <code>offset</code>
340 * on the same line as <code>offset</code>.
343 * the offset into document
344 * @return the token symbol of the next element, or TokenEOF if there is
347 private int peekChar(int offset) {
348 if (offset < fDocument.getLength()) {
350 IRegion line = fDocument.getLineInformationOfOffset(offset);
351 int lineOffset = line.getOffset();
352 int next = fScanner.nextToken(offset, lineOffset
355 } catch (BadLocationException e) {
358 return Symbols.TokenEOF;
362 * Returns the reference position regarding to indentation for
363 * <code>position</code>, or <code>NOT_FOUND</code>.
366 * If <code>peekNextChar</code> is <code>true</code>, the next token
367 * after <code>offset</code> is read and taken into account when computing
368 * the indentation. Currently, if the next token is the first token on the
369 * line (i.e. only preceded by whitespace), the following tokens are
372 * <li><code>switch</code> labels are indented relative to the switch
374 * <li>opening curly braces are aligned correctly with the introducing code</li>
375 * <li>closing curly braces are aligned properly with the introducing code
376 * of the matching opening brace</li>
377 * <li>closing parenthesis' are aligned with their opening peer</li>
378 * <li>the <code>else</code> keyword is aligned with its <code>if</code>,
379 * anything else is aligned normally (i.e. with the base of any introducing
381 * <li>if there is no token on the same line after <code>offset</code>,
382 * the indentation is the same as for an <code>else</code> keyword</li>
386 * the offset for which the reference is computed
388 * the next token to assume in the document
389 * @return the reference statement relative to which <code>offset</code>
390 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
392 public int findReferencePosition(int offset, int nextToken) {
393 boolean danglingElse = false;
394 boolean unindent = false;
395 boolean indent = false;
396 boolean matchBrace = false;
397 boolean matchParen = false;
398 boolean matchCase = false;
400 // account for unindenation characters already typed in, but after
402 // if they are on a line by themselves, the indentation gets adjusted
405 // also account for a dangling else
406 if (offset < fDocument.getLength()) {
408 IRegion line = fDocument.getLineInformationOfOffset(offset);
409 int lineOffset = line.getOffset();
410 int prevPos = Math.max(offset - 1, 0);
411 boolean isFirstTokenOnLine = fDocument.get(lineOffset,
412 prevPos + 1 - lineOffset).trim().length() == 0;
413 int prevToken = fScanner.previousToken(prevPos,
414 JavaHeuristicScanner.UNBOUND);
415 if (prevToken == Symbols.TokenEOF && nextToken == Symbols.TokenEOF) {
416 ITypedRegion partition = TextUtilities.getPartition(fDocument, IPHPPartitions.PHP_PARTITIONING, offset, true);
417 if (partition.getType().equals(IPHPPartitions.PHP_SINGLELINE_COMMENT)) {
418 fAlign = fScanner.getPosition();
420 fAlign = JavaHeuristicScanner.NOT_FOUND;
422 return JavaHeuristicScanner.NOT_FOUND;
424 boolean bracelessBlockStart = fScanner.isBracelessBlockStart(
425 prevPos, JavaHeuristicScanner.UNBOUND);
428 case Symbols.TokenEOF:
429 case Symbols.TokenELSE:
432 case Symbols.TokenCASE:
433 case Symbols.TokenDEFAULT:
434 if (isFirstTokenOnLine)
437 case Symbols.TokenLBRACE: // for opening-brace-on-new-line
439 // if (bracelessBlockStart && !prefIndentBracesForBlocks())
441 // else if ((prevToken == Symbols.TokenCOLON || prevToken ==
442 // Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET) &&
443 // !prefIndentBracesForArrays())
445 // else if (!bracelessBlockStart &&
446 // prefIndentBracesForMethods())
449 if (bracelessBlockStart)
451 else if ((prevToken == Symbols.TokenCOLON
452 || prevToken == Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET))
454 else if (!bracelessBlockStart)
457 case Symbols.TokenRBRACE: // closing braces get unindented
458 if (isFirstTokenOnLine)
461 case Symbols.TokenRPAREN:
462 if (isFirstTokenOnLine)
466 } catch (BadLocationException e) {
469 // assume an else could come if we are at the end of file
473 int ref = findReferencePosition(offset, danglingElse, matchBrace,
474 matchParen, matchCase);
483 * Returns the reference position regarding to indentation for
484 * <code>position</code>, or <code>NOT_FOUND</code>.<code>fIndent</code>
485 * will contain the relative indentation (in indentation units, not
486 * characters) after the call. If there is a special alignment (e.g. for a
487 * method declaration where parameters should be aligned),
488 * <code>fAlign</code> will contain the absolute position of the alignment
489 * reference in <code>fDocument</code>, otherwise <code>fAlign</code>
490 * is set to <code>JavaHeuristicScanner.NOT_FOUND</code>.
493 * the offset for which the reference is computed
494 * @param danglingElse
495 * whether a dangling else should be assumed at
496 * <code>position</code>
498 * whether the position of the matching brace should be returned
499 * instead of doing code analysis
501 * whether the position of the matching parenthesis should be
502 * returned instead of doing code analysis
504 * whether the position of a switch statement reference should be
505 * returned (either an earlier case statement or the switch block
507 * @return the reference statement relative to which <code>position</code>
508 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
510 public int findReferencePosition(int offset, boolean danglingElse,
511 boolean matchBrace, boolean matchParen, boolean matchCase) {
512 fIndent = 0; // the indentation modification
513 fAlign = JavaHeuristicScanner.NOT_FOUND;
517 // an unindentation happens sometimes if the next token is special,
518 // namely on braces, parens and case labels
519 // align braces, but handle the case where we align with the method
520 // declaration start instead of
521 // the opening brace.
523 if (skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE)) {
525 // align with the opening brace that is on a line by its own
526 int lineOffset = fDocument.getLineOffset(fLine);
527 if (lineOffset <= fPosition
529 .get(lineOffset, fPosition - lineOffset)
530 .trim().length() == 0)
532 } catch (BadLocationException e) {
533 // concurrent modification - walk default path
535 // if the opening brace is not on the start of the line, skip to
537 int pos = skipToStatementStart(true, true);
538 fIndent = 0; // indent is aligned with reference position
541 // if we can't find the matching brace, the heuristic is to
543 // by one against the normal position
544 int pos = findReferencePosition(offset, danglingElse, false,
545 matchParen, matchCase);
551 // align parenthesis'
553 if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN))
556 // if we can't find the matching paren, the heuristic is to
558 // by one against the normal position
559 int pos = findReferencePosition(offset, danglingElse,
560 matchBrace, false, matchCase);
566 // the only reliable way to get case labels aligned (due to many
567 // different styles of using braces in a block)
568 // is to go for another case statement, or the scope opening brace
570 return matchCaseAlignment();
575 case Symbols.TokenRBRACE:
576 // skip the block and fall through
577 // if we can't complete the scope, reset the scan position
581 case Symbols.TokenSEMICOLON:
582 // this is the 90% case: after a statement block
583 // the end of the previous statement / block previous.end
584 // search to the end of the statement / block before the previous;
585 // the token just after that is previous.start
586 return skipToStatementStart(danglingElse, false);
588 // scope introduction: special treat who special is
589 case Symbols.TokenLPAREN:
590 case Symbols.TokenLBRACE:
591 case Symbols.TokenLBRACKET:
592 return handleScopeIntroduction(offset + 1);
594 case Symbols.TokenEOF:
595 // trap when hitting start of document
598 case Symbols.TokenEQUAL:
599 // indent assignments
600 fIndent = prefAssignmentIndent();
603 case Symbols.TokenCOLON:
604 // TODO handle ternary deep indentation
605 fIndent = prefCaseBlockIndent();
608 case Symbols.TokenQUESTIONMARK:
609 if (prefTernaryDeepAlign()) {
610 setFirstElementAlignment(fPosition, offset + 1);
613 fIndent = prefTernaryIndent();
617 // indentation for blockless introducers:
618 case Symbols.TokenDO:
619 case Symbols.TokenWHILE:
620 case Symbols.TokenELSE:
621 fIndent = prefSimpleIndent();
623 case Symbols.TokenRPAREN:
625 if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN)) {
626 int scope = fPosition;
628 if (fToken == Symbols.TokenIF || fToken == Symbols.TokenWHILE
629 || fToken == Symbols.TokenFOR) {
630 fIndent = prefSimpleIndent();
634 if (looksLikeMethodDecl()) {
635 return skipToStatementStart(danglingElse, false);
641 // else: fall through to default
643 case Symbols.TokenCOMMA:
644 // inside a list of some type
645 // easy if there is already a list item before with its own
646 // indentation - we just align
647 // if not: take the start of the list ( LPAREN, LBRACE, LBRACKET )
648 // and either align or
649 // indent by list-indent
651 // inside whatever we don't know about: similar to the list case:
652 // if we are inside a continued expression, then either align with a
653 // previous line that has indentation
654 // or indent from the expression start line (either a scope
655 // introducer or the start of the expr).
656 return skipToPreviousListItemOrListStart();
662 * Skips to the start of a statement that ends at the current position.
664 * @param danglingElse
665 * whether to indent aligned with the last <code>if</code>
667 * whether the current position is inside a block, which limits
668 * the search scope to the next scope introducer
669 * @return the reference offset of the start of the statement
671 private int skipToStatementStart(boolean danglingElse, boolean isInBlock) {
677 // exit on all block introducers
678 case Symbols.TokenIF:
679 case Symbols.TokenELSE:
680 case Symbols.TokenSYNCHRONIZED:
681 case Symbols.TokenCOLON:
682 case Symbols.TokenSTATIC:
683 case Symbols.TokenCATCH:
684 case Symbols.TokenDO:
685 case Symbols.TokenWHILE:
686 case Symbols.TokenFINALLY:
687 case Symbols.TokenFOR:
688 case Symbols.TokenTRY:
691 case Symbols.TokenSWITCH:
692 fIndent = prefCaseIndent();
698 // scope introduction through: LPAREN, LBRACE, LBRACKET
699 // search stop on SEMICOLON, RBRACE, COLON, EOF
700 // -> the next token is the start of the statement (i.e. previousPos
701 // when backward scanning)
702 case Symbols.TokenLPAREN:
703 case Symbols.TokenLBRACE:
704 case Symbols.TokenLBRACKET:
705 case Symbols.TokenSEMICOLON:
706 case Symbols.TokenEOF:
709 case Symbols.TokenCOLON:
710 int pos = fPreviousPos;
711 if (!isConditional())
715 case Symbols.TokenRBRACE:
716 // RBRACE is a little tricky: it can be the end of an array
718 // usually it is the end of a previous block
719 pos = fPreviousPos; // store state
720 if (skipScope() && looksLikeArrayInitializerIntro())
721 continue; // it's an array
723 return pos; // it's not - do as with all the above
726 case Symbols.TokenRPAREN:
727 case Symbols.TokenRBRACKET:
734 // IF / ELSE: align the position after the conditional block
736 // so we are ready for an else, except if danglingElse is false
737 // in order for this to work, we must skip an else to its if
738 case Symbols.TokenIF:
743 case Symbols.TokenELSE:
744 // skip behind the next if, as we have that one covered
751 case Symbols.TokenDO:
752 // align the WHILE position with its do
755 case Symbols.TokenWHILE:
756 // this one is tricky: while can be the start of a while loop
757 // or the end of a do - while
759 if (hasMatchingDo()) {
760 // continue searching from the DO on
763 // continue searching from the WHILE on
776 * Returns true if the colon at the current position is part of a
777 * conditional (ternary) expression, false otherwise.
779 * @return true if the colon at the current position is part of a
782 private boolean isConditional() {
787 // search for case, otherwise return true
788 case Symbols.TokenIDENT:
790 case Symbols.TokenCASE:
800 * Returns as a reference any previous <code>switch</code> labels (<code>case</code>
801 * or <code>default</code>) or the offset of the brace that scopes the
802 * switch statement. Sets <code>fIndent</code> to
803 * <code>prefCaseIndent</code> upon a match.
805 * @return the reference offset for a <code>switch</code> label
807 private int matchCaseAlignment() {
811 // invalid cases: another case label or an LBRACE must come before a
813 // -> bail out with the current position
814 case Symbols.TokenLPAREN:
815 case Symbols.TokenLBRACKET:
816 case Symbols.TokenEOF:
818 case Symbols.TokenLBRACE:
819 // opening brace of switch statement
820 fIndent = 1; //prefCaseIndent() is for Java
822 case Symbols.TokenCASE:
823 case Symbols.TokenDEFAULT:
824 // align with previous label
828 case Symbols.TokenRPAREN:
829 case Symbols.TokenRBRACKET:
830 case Symbols.TokenRBRACE:
841 * Returns the reference position for a list element. The algorithm tries to
842 * match any previous indentation on the same list. If there is none, the
843 * reference position returned is determined depending on the type of list:
844 * The indentation will either match the list scope introducer (e.g. for
845 * method declarations), so called deep indents, or simply increase the
846 * indentation by a number of standard indents. See also
847 * {@link #handleScopeIntroduction(int)}.
849 * @return the reference position for a list item: either a previous list
850 * item that has its own indentation, or the list introduction
853 private int skipToPreviousListItemOrListStart() {
854 int startLine = fLine;
855 int startPosition = fPosition;
859 // if any line item comes with its own indentation, adapt to it
860 if (fLine < startLine) {
862 int lineOffset = fDocument.getLineOffset(startLine);
863 int bound = Math.min(fDocument.getLength(),
865 fAlign = fScanner.findNonWhitespaceForwardInAnyPartition(
867 } catch (BadLocationException e) {
868 // ignore and return just the position
870 return startPosition;
875 case Symbols.TokenRPAREN:
876 case Symbols.TokenRBRACKET:
877 case Symbols.TokenRBRACE:
881 // scope introduction: special treat who special is
882 case Symbols.TokenLPAREN:
883 case Symbols.TokenLBRACE:
884 case Symbols.TokenLBRACKET:
885 return handleScopeIntroduction(startPosition + 1);
887 case Symbols.TokenSEMICOLON:
889 case Symbols.TokenQUESTIONMARK:
890 if (prefTernaryDeepAlign()) {
891 setFirstElementAlignment(fPosition - 1, fPosition + 1);
893 fIndent = prefTernaryIndent();
896 case Symbols.TokenEOF:
899 case Symbols.TokenEQUAL:
900 // indent assignments
901 fIndent= prefAssignmentIndent();
908 * Skips a scope and positions the cursor (<code>fPosition</code>) on
909 * the token that opens the scope. Returns <code>true</code> if a matching
910 * peer could be found, <code>false</code> otherwise. The current token
911 * when calling must be one out of <code>Symbols.TokenRPAREN</code>,
912 * <code>Symbols.TokenRBRACE</code>, and
913 * <code>Symbols.TokenRBRACKET</code>.
915 * @return <code>true</code> if a matching peer was found,
916 * <code>false</code> otherwise
918 private boolean skipScope() {
920 case Symbols.TokenRPAREN:
921 return skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN);
922 case Symbols.TokenRBRACKET:
923 return skipScope(Symbols.TokenLBRACKET, Symbols.TokenRBRACKET);
924 case Symbols.TokenRBRACE:
925 return skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE);
927 Assert.isTrue(false);
933 * Handles the introduction of a new scope. The current token must be one
934 * out of <code>Symbols.TokenLPAREN</code>,
935 * <code>Symbols.TokenLBRACE</code>, and
936 * <code>Symbols.TokenLBRACKET</code>. Returns as the reference position
937 * either the token introducing the scope or - if available - the first java
941 * Depending on the type of scope introduction, the indentation will align
942 * (deep indenting) with the reference position (<code>fAlign</code> will
943 * be set to the reference position) or <code>fIndent</code> will be set
944 * to the number of indentation units.
948 * the bound for the search for the first token after the scope
952 private int handleScopeIntroduction(int bound) {
954 // scope introduction: special treat who special is
955 case Symbols.TokenLPAREN:
956 int pos = fPosition; // store
958 // special: method declaration deep indentation
959 if (looksLikeMethodDecl()) {
960 if (prefMethodDeclDeepIndent())
961 return setFirstElementAlignment(pos, bound);
963 fIndent = prefMethodDeclIndent();
968 if (looksLikeMethodCall()) {
969 if (prefMethodCallDeepIndent())
970 return setFirstElementAlignment(pos, bound);
972 fIndent = prefMethodCallIndent();
975 } else if (prefParenthesisDeepIndent())
976 return setFirstElementAlignment(pos, bound);
979 // normal: return the parenthesis as reference
980 fIndent = prefParenthesisIndent();
983 case Symbols.TokenLBRACE:
984 pos = fPosition; // store
986 // special: array initializer
987 if (looksLikeArrayInitializerIntro())
988 if (prefArrayDeepIndent())
989 return setFirstElementAlignment(pos, bound);
991 fIndent = prefArrayIndent();
993 fIndent = prefBlockIndent();
995 // normal: skip to the statement start before the scope introducer
996 // opening braces are often on differently ending indents than e.g.
997 // a method definition
998 fPosition = pos; // restore
999 return skipToStatementStart(true, true); // set to true to match
1002 case Symbols.TokenLBRACKET:
1003 pos = fPosition; // store
1005 // special: method declaration deep indentation
1006 if (prefArrayDimensionsDeepIndent()) {
1007 return setFirstElementAlignment(pos, bound);
1010 // normal: return the bracket as reference
1011 fIndent = prefBracketIndent();
1012 return pos; // restore
1015 Assert.isTrue(false);
1021 * Sets the deep indent offset (<code>fAlign</code>) to either the
1022 * offset right after <code>scopeIntroducerOffset</code> or - if available -
1023 * the first Java token after <code>scopeIntroducerOffset</code>, but
1024 * before <code>bound</code>.
1026 * @param scopeIntroducerOffset
1027 * the offset of the scope introducer
1029 * the bound for the search for another element
1030 * @return the reference position
1032 private int setFirstElementAlignment(int scopeIntroducerOffset, int bound) {
1033 int firstPossible = scopeIntroducerOffset + 1; // align with the first
1034 // position after the
1036 fAlign = fScanner.findNonWhitespaceForwardInAnyPartition(firstPossible,
1038 if (fAlign == JavaHeuristicScanner.NOT_FOUND)
1039 fAlign = firstPossible;
1044 * Returns <code>true</code> if the next token received after calling
1045 * <code>nextToken</code> is either an equal sign or an array designator
1048 * @return <code>true</code> if the next elements look like the start of
1049 * an array definition
1051 private boolean looksLikeArrayInitializerIntro() {
1053 if (fToken == Symbols.TokenEQUAL || skipBrackets()) {
1060 * Skips over the next <code>if</code> keyword. The current token when
1061 * calling this method must be an <code>else</code> keyword. Returns
1062 * <code>true</code> if a matching <code>if</code> could be found,
1063 * <code>false</code> otherwise. The cursor (<code>fPosition</code>)
1064 * is set to the offset of the <code>if</code> token.
1066 * @return <code>true</code> if a matching <code>if</code> token was
1067 * found, <code>false</code> otherwise
1069 private boolean skipNextIF() {
1070 Assert.isTrue(fToken == Symbols.TokenELSE);
1075 // scopes: skip them
1076 case Symbols.TokenRPAREN:
1077 case Symbols.TokenRBRACKET:
1078 case Symbols.TokenRBRACE:
1082 case Symbols.TokenIF:
1085 case Symbols.TokenELSE:
1086 // recursively skip else-if blocks
1090 // shortcut scope starts
1091 case Symbols.TokenLPAREN:
1092 case Symbols.TokenLBRACE:
1093 case Symbols.TokenLBRACKET:
1094 case Symbols.TokenEOF:
1101 * while(condition); is ambiguous when parsed backwardly, as it is a valid
1102 * statement by its own, so we have to check whether there is a matching do.
1103 * A <code>do</code> can either be separated from the while by a block, or
1104 * by a single statement, which limits our search distance.
1106 * @return <code>true</code> if the <code>while</code> currently in
1107 * <code>fToken</code> has a matching <code>do</code>.
1109 private boolean hasMatchingDo() {
1110 Assert.isTrue(fToken == Symbols.TokenWHILE);
1113 case Symbols.TokenRBRACE:
1114 skipScope(); // and fall thru
1115 case Symbols.TokenSEMICOLON:
1116 skipToStatementStart(false, false);
1117 return fToken == Symbols.TokenDO;
1123 * Skips brackets if the current token is a RBRACKET. There can be nothing
1124 * but whitespace in between, this is only to be used for <code>[]</code>
1127 * @return <code>true</code> if a <code>[]</code> could be scanned, the
1128 * current token is left at the LBRACKET.
1130 private boolean skipBrackets() {
1131 if (fToken == Symbols.TokenRBRACKET) {
1133 if (fToken == Symbols.TokenLBRACKET) {
1141 * Reads the next token in backward direction from the heuristic scanner and
1142 * sets the fields <code>fToken, fPreviousPosition</code> and
1143 * <code>fPosition</code> accordingly.
1145 private void nextToken() {
1146 nextToken(fPosition);
1150 * Reads the next token in backward direction of <code>start</code> from
1151 * the heuristic scanner and sets the fields
1152 * <code>fToken, fPreviousPosition</code> and <code>fPosition</code>
1155 private void nextToken(int start) {
1157 .previousToken(start - 1, JavaHeuristicScanner.UNBOUND);
1158 fPreviousPos = start;
1159 fPosition = fScanner.getPosition() + 1;
1161 fLine = fDocument.getLineOfOffset(fPosition);
1162 } catch (BadLocationException e) {
1168 * Returns <code>true</code> if the current tokens look like a method
1169 * declaration header (i.e. only the return type and method name). The
1170 * heuristic calls <code>nextToken</code> and expects an identifier
1171 * (method name) and a type declaration (an identifier with optional
1172 * brackets) which also covers the visibility modifier of constructors; it
1173 * does not recognize package visible constructors.
1175 * @return <code>true</code> if the current position looks like a method
1176 * declaration header.
1178 private boolean looksLikeMethodDecl() {
1180 * TODO This heuristic does not recognize package private constructors
1181 * since those do have neither type nor visibility keywords. One option
1182 * would be to go over the parameter list, but that might be empty as
1183 * well - hard to do without an AST...
1187 if (fToken == Symbols.TokenIDENT) { // method name
1190 while (skipBrackets()); // optional brackets for array valued return
1192 return fToken == Symbols.TokenIDENT; // type name
1199 * Returns <code>true</code> if the current tokens look like a method call
1200 * header (i.e. an identifier as opposed to a keyword taking parenthesized
1201 * parameters such as <code>if</code>).
1203 * The heuristic calls <code>nextToken</code> and expects an identifier
1206 * @return <code>true</code> if the current position looks like a method
1209 private boolean looksLikeMethodCall() {
1211 return fToken == Symbols.TokenIDENT; // method name
1215 * Scans tokens for the matching opening peer. The internal cursor (<code>fPosition</code>)
1216 * is set to the offset of the opening peer if found.
1218 * @return <code>true</code> if a matching token was found,
1219 * <code>false</code> otherwise
1221 private boolean skipScope(int openToken, int closeToken) {
1228 if (fToken == closeToken) {
1230 } else if (fToken == openToken) {
1234 } else if (fToken == Symbols.TokenEOF) {
1240 // TODO adjust once there are per-project settings
1242 private int prefTabLength() {
1244 // JavaCore core= JavaCore.getJavaCore();
1245 PHPeclipsePlugin plugin = PHPeclipsePlugin.getDefault();
1246 // if (core != null && plugin != null)
1250 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
1251 // if the formatter uses chars to mark indentation, then don't
1252 // substitute any chars
1253 tabLen = -1; // results in no tabs being substituted for
1256 // if the formatter uses tabs to mark indentations, use the
1257 // visual setting from the editor
1258 // to get nicely aligned indentations
1260 .getPreferenceStore()
1262 AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
1264 tabLen = 4; // sensible default for testing
1269 private boolean prefArrayDimensionsDeepIndent() {
1270 return true; // sensible default
1273 private int prefArrayIndent() {
1274 Plugin plugin = JavaCore.getPlugin();
1275 if (plugin != null) {
1276 String option = JavaCore
1277 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
1279 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1281 } catch (IllegalArgumentException e) {
1282 // ignore and return default
1286 return prefContinuationIndent(); // default
1289 private boolean prefArrayDeepIndent() {
1290 Plugin plugin = JavaCore.getPlugin();
1291 if (plugin != null) {
1292 String option = JavaCore
1293 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
1295 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1296 } catch (IllegalArgumentException e) {
1297 // ignore and return default
1304 private boolean prefTernaryDeepAlign() {
1305 Plugin plugin = JavaCore.getPlugin();
1306 if (plugin != null) {
1307 String option = JavaCore
1308 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
1310 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1311 } catch (IllegalArgumentException e) {
1312 // ignore and return default
1318 private int prefTernaryIndent() {
1319 Plugin plugin = JavaCore.getPlugin();
1320 if (plugin != null) {
1321 String option = JavaCore
1322 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
1324 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1327 return prefContinuationIndent();
1328 } catch (IllegalArgumentException e) {
1329 // ignore and return default
1333 return prefContinuationIndent();
1336 private int prefCaseIndent() {
1337 Plugin plugin = JavaCore.getPlugin();
1338 if (plugin != null) {
1339 if (DefaultCodeFormatterConstants.TRUE
1341 .getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_SWITCH)))
1342 return prefBlockIndent();
1347 return 0; // sun standard
1350 private int prefAssignmentIndent() {
1351 return prefBlockIndent();
1354 private int prefCaseBlockIndent() {
1356 return prefBlockIndent();
1358 Plugin plugin = JavaCore.getPlugin();
1359 if (plugin != null) {
1360 if (DefaultCodeFormatterConstants.TRUE
1362 .getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_CASES)))
1363 return prefBlockIndent();
1367 return prefBlockIndent(); // sun standard
1370 private int prefSimpleIndent() {
1371 return prefBlockIndent();
1374 private int prefBracketIndent() {
1375 return prefBlockIndent();
1378 private boolean prefMethodDeclDeepIndent() {
1379 Plugin plugin = JavaCore.getPlugin();
1380 if (plugin != null) {
1381 String option = JavaCore
1382 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
1384 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1385 } catch (IllegalArgumentException e) {
1386 // ignore and return default
1393 private int prefMethodDeclIndent() {
1394 Plugin plugin = JavaCore.getPlugin();
1395 if (plugin != null) {
1396 String option = JavaCore
1397 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
1399 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1402 return prefContinuationIndent();
1403 } catch (IllegalArgumentException e) {
1404 // ignore and return default
1410 private boolean prefMethodCallDeepIndent() {
1411 Plugin plugin = JavaCore.getPlugin();
1412 if (plugin != null) {
1413 String option = JavaCore
1414 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
1416 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1417 } catch (IllegalArgumentException e) {
1418 // ignore and return default
1421 return false; // sensible default
1424 private int prefMethodCallIndent() {
1425 Plugin plugin = JavaCore.getPlugin();
1426 if (plugin != null) {
1427 String option = JavaCore
1428 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
1430 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1433 return prefContinuationIndent();
1434 } catch (IllegalArgumentException e) {
1435 // ignore and return default
1439 return 1; // sensible default
1442 private boolean prefParenthesisDeepIndent() {
1444 if (true) // don't do parenthesis deep indentation
1447 Plugin plugin = JavaCore.getPlugin();
1448 if (plugin != null) {
1449 String option = JavaCore
1450 .getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
1452 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1453 } catch (IllegalArgumentException e) {
1454 // ignore and return default
1458 return false; // sensible default
1461 private int prefParenthesisIndent() {
1462 return prefContinuationIndent();
1465 private int prefBlockIndent() {
1466 return 1; // sensible default
1469 // private boolean prefIndentBracesForBlocks() {
1470 // Plugin plugin = JavaCore.getPlugin();
1471 // if (plugin != null) {
1472 // String option = JavaCore
1473 // .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_BLOCK);
1475 // .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1478 // return false; // sensible default
1481 // private boolean prefIndentBracesForArrays() {
1482 // Plugin plugin = JavaCore.getPlugin();
1483 // if (plugin != null) {
1484 // String option = JavaCore
1485 // .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_ARRAY_INITIALIZER);
1487 // .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1490 // return false; // sensible default
1493 // private boolean prefIndentBracesForMethods() {
1494 // Plugin plugin = JavaCore.getPlugin();
1495 // if (plugin != null) {
1496 // String option = JavaCore
1497 // .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_METHOD_DECLARATION);
1499 // .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1502 // return false; // sensible default
1505 private int prefContinuationIndent() {
1506 Plugin plugin = JavaCore.getPlugin();
1507 if (plugin != null) {
1508 String option = JavaCore
1509 .getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
1511 return Integer.parseInt(option);
1512 } catch (NumberFormatException e) {
1513 // ignore and return default
1517 return 2; // sensible default