1 /*******************************************************************************
2 * Copyright (c) 2000, 2004 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.text;
13 import net.sourceforge.phpdt.core.JavaCore;
14 import net.sourceforge.phpdt.core.formatter.DefaultCodeFormatterConstants;
15 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
17 import org.eclipse.core.runtime.Plugin;
18 import org.eclipse.jface.text.Assert;
19 import org.eclipse.jface.text.BadLocationException;
20 import org.eclipse.jface.text.IDocument;
21 import org.eclipse.jface.text.IRegion;
22 import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
25 * Uses the {@link net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner}to
26 * get the indentation level for a certain position in a document.
29 * An instance holds some internal position in the document and is therefore not
35 public class JavaIndenter {
37 /** The document being scanned. */
38 private IDocument fDocument;
40 /** The indentation accumulated by <code>findPreviousIndenationUnit</code>. */
44 * The absolute (character-counted) indentation offset for special cases
45 * (method defs, array initializers)
49 /** The stateful scanposition for the indentation methods. */
50 private int fPosition;
52 /** The previous position. */
53 private int fPreviousPos;
55 /** The most recent token. */
58 /** The line of <code>fPosition</code>. */
62 * The scanner we will use to scan the document. It has to be installed on
63 * the same document as the one we get.
65 private JavaHeuristicScanner fScanner;
68 * Creates a new instance.
71 * the document to scan
73 * the {@link JavaHeuristicScanner} to be used for scanning the
74 * document. It must be installed on the same
75 * <code>IDocument</code>.
77 public JavaIndenter(IDocument document, JavaHeuristicScanner scanner) {
78 Assert.isNotNull(document);
79 Assert.isNotNull(scanner);
85 * Computes the indentation at the reference point of <code>position</code>.
88 * the offset in the document
89 * @return a String which reflects the indentation at the line in which the
90 * reference position to <code>offset</code> resides, or
91 * <code>null</code> if it cannot be determined
93 public StringBuffer getReferenceIndentation(int offset) {
94 return getReferenceIndentation(offset, false);
98 * Computes the indentation at the reference point of <code>position</code>.
101 * the offset in the document
102 * @param assumeOpeningBrace
103 * <code>true</code> if an opening brace should be assumed
104 * @return a String which reflects the indentation at the line in which the
105 * reference position to <code>offset</code> resides, or
106 * <code>null</code> if it cannot be determined
108 private StringBuffer getReferenceIndentation(int offset,
109 boolean assumeOpeningBrace) {
112 if (assumeOpeningBrace)
113 unit = findReferencePosition(offset, Symbols.TokenLBRACE);
115 unit = findReferencePosition(offset, peekChar(offset));
117 // if we were unable to find anything, return null
118 if (unit == JavaHeuristicScanner.NOT_FOUND)
121 return getLeadingWhitespace(unit);
126 * Computes the indentation at <code>offset</code>.
129 * the offset in the document
130 * @return a String which reflects the correct indentation for the line in
131 * which offset resides, or <code>null</code> if it cannot be
134 public StringBuffer computeIndentation(int offset) {
135 return computeIndentation(offset, false);
139 * Computes the indentation at <code>offset</code>.
142 * the offset in the document
143 * @param assumeOpeningBrace
144 * <code>true</code> if an opening brace should be assumed
145 * @return a String which reflects the correct indentation for the line in
146 * which offset resides, or <code>null</code> if it cannot be
149 public StringBuffer computeIndentation(int offset,
150 boolean assumeOpeningBrace) {
152 StringBuffer indent = getReferenceIndentation(offset,
155 // handle special alignment
156 if (fAlign != JavaHeuristicScanner.NOT_FOUND) {
158 // a special case has been detected.
159 IRegion line = fDocument.getLineInformationOfOffset(fAlign);
160 int lineOffset = line.getOffset();
161 return createIndent(lineOffset, fAlign);
162 } catch (BadLocationException e) {
170 // add additional indent
171 indent.append(createIndent(fIndent));
179 * Returns the indentation of the line at <code>offset</code> as a
180 * <code>StringBuffer</code>. If the offset is not valid, the empty
181 * string is returned.
184 * the offset in the document
185 * @return the indentation (leading whitespace) of the line in which
186 * <code>offset</code> is located
188 private StringBuffer getLeadingWhitespace(int offset) {
189 StringBuffer indent = new StringBuffer();
191 IRegion line = fDocument.getLineInformationOfOffset(offset);
192 int lineOffset = line.getOffset();
193 int nonWS = fScanner.findNonWhitespaceForwardInAnyPartition(
194 lineOffset, lineOffset + line.getLength());
195 indent.append(fDocument.get(lineOffset, nonWS - lineOffset));
197 } catch (BadLocationException e) {
203 * Reduces indentation in <code>indent</code> by one indentation unit.
206 * the indentation to be modified
208 private void unindent(StringBuffer indent) {
209 CharSequence oneIndent = createIndent();
210 int i = indent.lastIndexOf(oneIndent.toString()); //$NON-NLS-1$
212 indent.delete(i, i + oneIndent.length());
217 * Creates an indentation string of the length indent - start + 1,
218 * consisting of the content in <code>fDocument</code> in the range
219 * [start, indent), with every character replaced by a space except for
220 * tabs, which are kept as such.
223 * Every run of the number of spaces that make up a tab are replaced by a
227 * @return the indentation corresponding to the document content specified
228 * by <code>start</code> and <code>indent</code>
230 private StringBuffer createIndent(int start, int indent) {
231 final int tabLen = prefTabLength();
232 StringBuffer ret = new StringBuffer();
235 while (start < indent) {
237 char ch = fDocument.getChar(start);
241 } else if (tabLen == -1) {
245 if (spaces == tabLen) {
254 if (spaces == tabLen)
260 } catch (BadLocationException e) {
267 * Creates a string that represents the given number of indents (can be
271 * the requested indentation level.
273 * @return the indentation specified by <code>indent</code>
275 public StringBuffer createIndent(int indent) {
276 StringBuffer oneIndent = createIndent();
278 StringBuffer ret = new StringBuffer();
280 ret.append(oneIndent);
286 * Creates a string that represents one indent (can be spaces or tabs..)
288 * @return one indentation
290 private StringBuffer createIndent() {
291 // get a sensible default when running without the infrastructure for
293 StringBuffer oneIndent = new StringBuffer();
294 // JavaCore plugin= JavaCore.getJavaCore();
295 PHPeclipsePlugin plugin = PHPeclipsePlugin.getDefault();
296 if (plugin == null) {
297 oneIndent.append('\t');
301 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) {
304 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE));
305 for (int i = 0; i < tabLen; i++)
306 oneIndent.append(' ');
307 } else if (JavaCore.TAB
309 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
310 oneIndent.append('\t');
312 oneIndent.append('\t'); // default
318 * Returns the reference position regarding to indentation for
319 * <code>offset</code>, or <code>NOT_FOUND</code>. This method calls
320 * {@link #findReferencePosition(int, int) findReferencePosition(offset, nextChar)}
321 * where <code>nextChar</code> is the next character after
322 * <code>offset</code>.
325 * the offset for which the reference is computed
326 * @return the reference statement relative to which <code>offset</code>
327 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
329 public int findReferencePosition(int offset) {
330 return findReferencePosition(offset, peekChar(offset));
334 * Peeks the next char in the document that comes after <code>offset</code>
335 * on the same line as <code>offset</code>.
338 * the offset into document
339 * @return the token symbol of the next element, or TokenEOF if there is
342 private int peekChar(int offset) {
343 if (offset < fDocument.getLength()) {
345 IRegion line = fDocument.getLineInformationOfOffset(offset);
346 int lineOffset = line.getOffset();
347 int next = fScanner.nextToken(offset, lineOffset
350 } catch (BadLocationException e) {
353 return Symbols.TokenEOF;
357 * Returns the reference position regarding to indentation for
358 * <code>position</code>, or <code>NOT_FOUND</code>.
361 * If <code>peekNextChar</code> is <code>true</code>, the next token
362 * after <code>offset</code> is read and taken into account when computing
363 * the indentation. Currently, if the next token is the first token on the
364 * line (i.e. only preceded by whitespace), the following tokens are
367 * <li><code>switch</code> labels are indented relative to the switch
369 * <li>opening curly braces are aligned correctly with the introducing code</li>
370 * <li>closing curly braces are aligned properly with the introducing code
371 * of the matching opening brace</li>
372 * <li>closing parenthesis' are aligned with their opening peer</li>
373 * <li>the <code>else</code> keyword is aligned with its <code>if</code>,
374 * anything else is aligned normally (i.e. with the base of any introducing
376 * <li>if there is no token on the same line after <code>offset</code>,
377 * the indentation is the same as for an <code>else</code> keyword</li>
381 * the offset for which the reference is computed
383 * the next token to assume in the document
384 * @return the reference statement relative to which <code>offset</code>
385 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
387 public int findReferencePosition(int offset, int nextToken) {
388 boolean danglingElse = false;
389 boolean unindent = false;
390 boolean indent = false;
391 boolean matchBrace = false;
392 boolean matchParen = false;
393 boolean matchCase = false;
395 // account for unindenation characters already typed in, but after
397 // if they are on a line by themselves, the indentation gets adjusted
400 // also account for a dangling else
401 if (offset < fDocument.getLength()) {
403 IRegion line = fDocument.getLineInformationOfOffset(offset);
404 int lineOffset = line.getOffset();
405 int prevPos = Math.max(offset - 1, 0);
406 boolean isFirstTokenOnLine = fDocument.get(lineOffset,
407 prevPos + 1 - lineOffset).trim().length() == 0;
408 int prevToken = fScanner.previousToken(prevPos,
409 JavaHeuristicScanner.UNBOUND);
410 boolean bracelessBlockStart = fScanner.isBracelessBlockStart(
411 prevPos, JavaHeuristicScanner.UNBOUND);
414 case Symbols.TokenEOF:
415 case Symbols.TokenELSE:
418 case Symbols.TokenCASE:
419 case Symbols.TokenDEFAULT:
420 if (isFirstTokenOnLine)
423 case Symbols.TokenLBRACE: // for opening-brace-on-new-line
425 // if (bracelessBlockStart && !prefIndentBracesForBlocks())
427 // else if ((prevToken == Symbols.TokenCOLON || prevToken ==
428 // Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET) &&
429 // !prefIndentBracesForArrays())
431 // else if (!bracelessBlockStart &&
432 // prefIndentBracesForMethods())
435 if (bracelessBlockStart)
437 else if ((prevToken == Symbols.TokenCOLON
438 || prevToken == Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET))
440 else if (!bracelessBlockStart)
443 case Symbols.TokenRBRACE: // closing braces get unindented
444 if (isFirstTokenOnLine)
447 case Symbols.TokenRPAREN:
448 if (isFirstTokenOnLine)
452 } catch (BadLocationException e) {
455 // assume an else could come if we are at the end of file
459 int ref = findReferencePosition(offset, danglingElse, matchBrace,
460 matchParen, matchCase);
469 * Returns the reference position regarding to indentation for
470 * <code>position</code>, or <code>NOT_FOUND</code>.<code>fIndent</code>
471 * will contain the relative indentation (in indentation units, not
472 * characters) after the call. If there is a special alignment (e.g. for a
473 * method declaration where parameters should be aligned),
474 * <code>fAlign</code> will contain the absolute position of the alignment
475 * reference in <code>fDocument</code>, otherwise <code>fAlign</code>
476 * is set to <code>JavaHeuristicScanner.NOT_FOUND</code>.
479 * the offset for which the reference is computed
480 * @param danglingElse
481 * whether a dangling else should be assumed at
482 * <code>position</code>
484 * whether the position of the matching brace should be returned
485 * instead of doing code analysis
487 * whether the position of the matching parenthesis should be
488 * returned instead of doing code analysis
490 * whether the position of a switch statement reference should be
491 * returned (either an earlier case statement or the switch block
493 * @return the reference statement relative to which <code>position</code>
494 * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
496 public int findReferencePosition(int offset, boolean danglingElse,
497 boolean matchBrace, boolean matchParen, boolean matchCase) {
498 fIndent = 0; // the indentation modification
499 fAlign = JavaHeuristicScanner.NOT_FOUND;
503 // an unindentation happens sometimes if the next token is special,
504 // namely on braces, parens and case labels
505 // align braces, but handle the case where we align with the method
506 // declaration start instead of
507 // the opening brace.
509 // if (skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE)) {
511 // // align with the opening brace that is on a line by its own
512 // int lineOffset= fDocument.getLineOffset(fLine);
513 // if (lineOffset <= fPosition && fDocument.get(lineOffset, fPosition -
514 // lineOffset).trim().length() == 0)
516 // } catch (BadLocationException e) {
517 // // concurrent modification - walk default path
519 // // if the opening brace is not on the start of the line, skip to the
521 // int pos= skipToStatementStart(true, true);
522 // fIndent= 0; // indent is aligned with reference position
525 // // if we can't find the matching brace, the heuristic is to unindent
526 // // by one against the normal position
527 // int pos= findReferencePosition(offset, danglingElse, false,
528 // matchParen, matchCase);
534 // align parenthesis'
536 if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN))
539 // if we can't find the matching paren, the heuristic is to
541 // by one against the normal position
542 int pos = findReferencePosition(offset, danglingElse,
543 matchBrace, false, matchCase);
549 // the only reliable way to get case labels aligned (due to many
550 // different styles of using braces in a block)
551 // is to go for another case statement, or the scope opening brace
553 // return matchCaseAlignment();
558 case Symbols.TokenRBRACE:
559 // skip the block and fall through
560 // if we can't complete the scope, reset the scan position
564 case Symbols.TokenSEMICOLON:
565 // this is the 90% case: after a statement block
566 // the end of the previous statement / block previous.end
567 // search to the end of the statement / block before the previous;
568 // the token just after that is previous.start
569 return skipToStatementStart(danglingElse, false);
571 // scope introduction: special treat who special is
572 case Symbols.TokenLPAREN:
573 case Symbols.TokenLBRACE:
574 case Symbols.TokenLBRACKET:
575 return handleScopeIntroduction(offset + 1);
577 case Symbols.TokenEOF:
578 // trap when hitting start of document
581 case Symbols.TokenEQUAL:
582 // indent assignments
583 fIndent = prefAssignmentIndent();
586 case Symbols.TokenCOLON:
587 // TODO handle ternary deep indentation
588 fIndent = prefCaseBlockIndent();
591 case Symbols.TokenQUESTIONMARK:
592 if (prefTernaryDeepAlign()) {
593 setFirstElementAlignment(fPosition, offset + 1);
596 fIndent = prefTernaryIndent();
600 // indentation for blockless introducers:
601 case Symbols.TokenDO:
602 case Symbols.TokenWHILE:
603 case Symbols.TokenELSE:
604 fIndent = prefSimpleIndent();
606 case Symbols.TokenRPAREN:
608 if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN)) {
609 int scope = fPosition;
611 if (fToken == Symbols.TokenIF || fToken == Symbols.TokenWHILE
612 || fToken == Symbols.TokenFOR) {
613 fIndent = prefSimpleIndent();
617 if (looksLikeMethodDecl()) {
618 return skipToStatementStart(danglingElse, false);
624 // else: fall through to default
626 case Symbols.TokenCOMMA:
627 // inside a list of some type
628 // easy if there is already a list item before with its own
629 // indentation - we just align
630 // if not: take the start of the list ( LPAREN, LBRACE, LBRACKET )
631 // and either align or
632 // indent by list-indent
634 // inside whatever we don't know about: similar to the list case:
635 // if we are inside a continued expression, then either align with a
636 // previous line that has indentation
637 // or indent from the expression start line (either a scope
638 // introducer or the start of the expr).
639 return skipToPreviousListItemOrListStart();
645 * Skips to the start of a statement that ends at the current position.
647 * @param danglingElse
648 * whether to indent aligned with the last <code>if</code>
650 * whether the current position is inside a block, which limits
651 * the search scope to the next scope introducer
652 * @return the reference offset of the start of the statement
654 private int skipToStatementStart(boolean danglingElse, boolean isInBlock) {
660 // exit on all block introducers
661 case Symbols.TokenIF:
662 case Symbols.TokenELSE:
663 case Symbols.TokenSYNCHRONIZED:
664 case Symbols.TokenCOLON:
665 case Symbols.TokenSTATIC:
666 case Symbols.TokenCATCH:
667 case Symbols.TokenDO:
668 case Symbols.TokenWHILE:
669 case Symbols.TokenFINALLY:
670 case Symbols.TokenFOR:
671 case Symbols.TokenTRY:
674 case Symbols.TokenSWITCH:
675 fIndent = prefCaseIndent();
681 // scope introduction through: LPAREN, LBRACE, LBRACKET
682 // search stop on SEMICOLON, RBRACE, COLON, EOF
683 // -> the next token is the start of the statement (i.e. previousPos
684 // when backward scanning)
685 case Symbols.TokenLPAREN:
686 case Symbols.TokenLBRACE:
687 case Symbols.TokenLBRACKET:
688 case Symbols.TokenSEMICOLON:
689 case Symbols.TokenEOF:
692 case Symbols.TokenCOLON:
693 int pos = fPreviousPos;
694 if (!isConditional())
698 case Symbols.TokenRBRACE:
699 // RBRACE is a little tricky: it can be the end of an array
701 // usually it is the end of a previous block
702 pos = fPreviousPos; // store state
703 if (skipScope() && looksLikeArrayInitializerIntro())
704 continue; // it's an array
706 return pos; // it's not - do as with all the above
709 case Symbols.TokenRPAREN:
710 case Symbols.TokenRBRACKET:
717 // IF / ELSE: align the position after the conditional block
719 // so we are ready for an else, except if danglingElse is false
720 // in order for this to work, we must skip an else to its if
721 case Symbols.TokenIF:
726 case Symbols.TokenELSE:
727 // skip behind the next if, as we have that one covered
734 case Symbols.TokenDO:
735 // align the WHILE position with its do
738 case Symbols.TokenWHILE:
739 // this one is tricky: while can be the start of a while loop
740 // or the end of a do - while
742 if (hasMatchingDo()) {
743 // continue searching from the DO on
746 // continue searching from the WHILE on
759 * Returns true if the colon at the current position is part of a
760 * conditional (ternary) expression, false otherwise.
762 * @return true if the colon at the current position is part of a
765 private boolean isConditional() {
770 // search for case, otherwise return true
771 case Symbols.TokenIDENT:
773 case Symbols.TokenCASE:
783 * Returns as a reference any previous <code>switch</code> labels (<code>case</code>
784 * or <code>default</code>) or the offset of the brace that scopes the
785 * switch statement. Sets <code>fIndent</code> to
786 * <code>prefCaseIndent</code> upon a match.
788 * @return the reference offset for a <code>switch</code> label
790 // private int matchCaseAlignment() {
794 // // invalid cases: another case label or an LBRACE must come before a case
795 // // -> bail out with the current position
796 // case Symbols.TokenLPAREN:
797 // case Symbols.TokenLBRACKET:
798 // case Symbols.TokenEOF:
800 // case Symbols.TokenLBRACE:
801 // // opening brace of switch statement
802 // fIndent= prefCaseIndent();
804 // case Symbols.TokenCASE:
805 // case Symbols.TokenDEFAULT:
806 // // align with previous label
810 // // scopes: skip them
811 // case Symbols.TokenRPAREN:
812 // case Symbols.TokenRBRACKET:
813 // case Symbols.TokenRBRACE:
825 * Returns the reference position for a list element. The algorithm tries to
826 * match any previous indentation on the same list. If there is none, the
827 * reference position returned is determined depending on the type of list:
828 * The indentation will either match the list scope introducer (e.g. for
829 * method declarations), so called deep indents, or simply increase the
830 * indentation by a number of standard indents. See also
831 * {@link #handleScopeIntroduction(int)}.
833 * @return the reference position for a list item: either a previous list
834 * item that has its own indentation, or the list introduction
837 private int skipToPreviousListItemOrListStart() {
838 int startLine = fLine;
839 int startPosition = fPosition;
843 // if any line item comes with its own indentation, adapt to it
844 if (fLine < startLine) {
846 int lineOffset = fDocument.getLineOffset(startLine);
847 int bound = Math.min(fDocument.getLength(),
849 fAlign = fScanner.findNonWhitespaceForwardInAnyPartition(
851 } catch (BadLocationException e) {
852 // ignore and return just the position
854 return startPosition;
859 case Symbols.TokenRPAREN:
860 case Symbols.TokenRBRACKET:
861 case Symbols.TokenRBRACE:
865 // scope introduction: special treat who special is
866 case Symbols.TokenLPAREN:
867 case Symbols.TokenLBRACE:
868 case Symbols.TokenLBRACKET:
869 return handleScopeIntroduction(startPosition + 1);
871 case Symbols.TokenSEMICOLON:
873 case Symbols.TokenQUESTIONMARK:
874 if (prefTernaryDeepAlign()) {
875 setFirstElementAlignment(fPosition - 1, fPosition + 1);
878 fIndent = prefTernaryIndent();
881 case Symbols.TokenEOF:
889 * Skips a scope and positions the cursor (<code>fPosition</code>) on
890 * the token that opens the scope. Returns <code>true</code> if a matching
891 * peer could be found, <code>false</code> otherwise. The current token
892 * when calling must be one out of <code>Symbols.TokenRPAREN</code>,
893 * <code>Symbols.TokenRBRACE</code>, and
894 * <code>Symbols.TokenRBRACKET</code>.
896 * @return <code>true</code> if a matching peer was found,
897 * <code>false</code> otherwise
899 private boolean skipScope() {
901 case Symbols.TokenRPAREN:
902 return skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN);
903 case Symbols.TokenRBRACKET:
904 return skipScope(Symbols.TokenLBRACKET, Symbols.TokenRBRACKET);
905 case Symbols.TokenRBRACE:
906 return skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE);
908 Assert.isTrue(false);
914 * Handles the introduction of a new scope. The current token must be one
915 * out of <code>Symbols.TokenLPAREN</code>,
916 * <code>Symbols.TokenLBRACE</code>, and
917 * <code>Symbols.TokenLBRACKET</code>. Returns as the reference position
918 * either the token introducing the scope or - if available - the first java
922 * Depending on the type of scope introduction, the indentation will align
923 * (deep indenting) with the reference position (<code>fAlign</code> will
924 * be set to the reference position) or <code>fIndent</code> will be set
925 * to the number of indentation units.
929 * the bound for the search for the first token after the scope
933 private int handleScopeIntroduction(int bound) {
935 // scope introduction: special treat who special is
936 case Symbols.TokenLPAREN:
937 int pos = fPosition; // store
939 // special: method declaration deep indentation
940 if (looksLikeMethodDecl()) {
941 if (prefMethodDeclDeepIndent())
942 return setFirstElementAlignment(pos, bound);
944 fIndent = prefMethodDeclIndent();
949 if (looksLikeMethodCall()) {
950 if (prefMethodCallDeepIndent())
951 return setFirstElementAlignment(pos, bound);
953 fIndent = prefMethodCallIndent();
956 } else if (prefParenthesisDeepIndent())
957 return setFirstElementAlignment(pos, bound);
960 // normal: return the parenthesis as reference
961 fIndent = prefParenthesisIndent();
964 case Symbols.TokenLBRACE:
965 pos = fPosition; // store
967 // special: array initializer
968 if (looksLikeArrayInitializerIntro())
969 if (prefArrayDeepIndent())
970 return setFirstElementAlignment(pos, bound);
972 fIndent = prefArrayIndent();
974 fIndent = prefBlockIndent();
976 // normal: skip to the statement start before the scope introducer
977 // opening braces are often on differently ending indents than e.g.
978 // a method definition
979 fPosition = pos; // restore
980 return skipToStatementStart(true, true); // set to true to match
983 case Symbols.TokenLBRACKET:
984 pos = fPosition; // store
986 // special: method declaration deep indentation
987 if (prefArrayDimensionsDeepIndent()) {
988 return setFirstElementAlignment(pos, bound);
991 // normal: return the bracket as reference
992 fIndent = prefBracketIndent();
993 return pos; // restore
996 Assert.isTrue(false);
1002 * Sets the deep indent offset (<code>fAlign</code>) to either the
1003 * offset right after <code>scopeIntroducerOffset</code> or - if available -
1004 * the first Java token after <code>scopeIntroducerOffset</code>, but
1005 * before <code>bound</code>.
1007 * @param scopeIntroducerOffset
1008 * the offset of the scope introducer
1010 * the bound for the search for another element
1011 * @return the reference position
1013 private int setFirstElementAlignment(int scopeIntroducerOffset, int bound) {
1014 int firstPossible = scopeIntroducerOffset + 1; // align with the first
1015 // position after the
1017 fAlign = fScanner.findNonWhitespaceForwardInAnyPartition(firstPossible,
1019 if (fAlign == JavaHeuristicScanner.NOT_FOUND)
1020 fAlign = firstPossible;
1025 * Returns <code>true</code> if the next token received after calling
1026 * <code>nextToken</code> is either an equal sign or an array designator
1029 * @return <code>true</code> if the next elements look like the start of
1030 * an array definition
1032 private boolean looksLikeArrayInitializerIntro() {
1034 if (fToken == Symbols.TokenEQUAL || skipBrackets()) {
1041 * Skips over the next <code>if</code> keyword. The current token when
1042 * calling this method must be an <code>else</code> keyword. Returns
1043 * <code>true</code> if a matching <code>if</code> could be found,
1044 * <code>false</code> otherwise. The cursor (<code>fPosition</code>)
1045 * is set to the offset of the <code>if</code> token.
1047 * @return <code>true</code> if a matching <code>if</code> token was
1048 * found, <code>false</code> otherwise
1050 private boolean skipNextIF() {
1051 Assert.isTrue(fToken == Symbols.TokenELSE);
1056 // scopes: skip them
1057 case Symbols.TokenRPAREN:
1058 case Symbols.TokenRBRACKET:
1059 case Symbols.TokenRBRACE:
1063 case Symbols.TokenIF:
1066 case Symbols.TokenELSE:
1067 // recursively skip else-if blocks
1071 // shortcut scope starts
1072 case Symbols.TokenLPAREN:
1073 case Symbols.TokenLBRACE:
1074 case Symbols.TokenLBRACKET:
1075 case Symbols.TokenEOF:
1082 * while(condition); is ambiguous when parsed backwardly, as it is a valid
1083 * statement by its own, so we have to check whether there is a matching do.
1084 * A <code>do</code> can either be separated from the while by a block, or
1085 * by a single statement, which limits our search distance.
1087 * @return <code>true</code> if the <code>while</code> currently in
1088 * <code>fToken</code> has a matching <code>do</code>.
1090 private boolean hasMatchingDo() {
1091 Assert.isTrue(fToken == Symbols.TokenWHILE);
1094 case Symbols.TokenRBRACE:
1095 skipScope(); // and fall thru
1096 case Symbols.TokenSEMICOLON:
1097 skipToStatementStart(false, false);
1098 return fToken == Symbols.TokenDO;
1104 * Skips brackets if the current token is a RBRACKET. There can be nothing
1105 * but whitespace in between, this is only to be used for <code>[]</code>
1108 * @return <code>true</code> if a <code>[]</code> could be scanned, the
1109 * current token is left at the LBRACKET.
1111 private boolean skipBrackets() {
1112 if (fToken == Symbols.TokenRBRACKET) {
1114 if (fToken == Symbols.TokenLBRACKET) {
1122 * Reads the next token in backward direction from the heuristic scanner and
1123 * sets the fields <code>fToken, fPreviousPosition</code> and
1124 * <code>fPosition</code> accordingly.
1126 private void nextToken() {
1127 nextToken(fPosition);
1131 * Reads the next token in backward direction of <code>start</code> from
1132 * the heuristic scanner and sets the fields
1133 * <code>fToken, fPreviousPosition</code> and <code>fPosition</code>
1136 private void nextToken(int start) {
1138 .previousToken(start - 1, JavaHeuristicScanner.UNBOUND);
1139 fPreviousPos = start;
1140 fPosition = fScanner.getPosition() + 1;
1142 fLine = fDocument.getLineOfOffset(fPosition);
1143 } catch (BadLocationException e) {
1149 * Returns <code>true</code> if the current tokens look like a method
1150 * declaration header (i.e. only the return type and method name). The
1151 * heuristic calls <code>nextToken</code> and expects an identifier
1152 * (method name) and a type declaration (an identifier with optional
1153 * brackets) which also covers the visibility modifier of constructors; it
1154 * does not recognize package visible constructors.
1156 * @return <code>true</code> if the current position looks like a method
1157 * declaration header.
1159 private boolean looksLikeMethodDecl() {
1161 * TODO This heuristic does not recognize package private constructors
1162 * since those do have neither type nor visibility keywords. One option
1163 * would be to go over the parameter list, but that might be empty as
1164 * well - hard to do without an AST...
1168 if (fToken == Symbols.TokenIDENT) { // method name
1171 while (skipBrackets()); // optional brackets for array valued return
1173 return fToken == Symbols.TokenIDENT; // type name
1180 * Returns <code>true</code> if the current tokens look like a method call
1181 * header (i.e. an identifier as opposed to a keyword taking parenthesized
1182 * parameters such as <code>if</code>).
1184 * The heuristic calls <code>nextToken</code> and expects an identifier
1187 * @return <code>true</code> if the current position looks like a method
1190 private boolean looksLikeMethodCall() {
1192 return fToken == Symbols.TokenIDENT; // method name
1196 * Scans tokens for the matching opening peer. The internal cursor (<code>fPosition</code>)
1197 * is set to the offset of the opening peer if found.
1199 * @return <code>true</code> if a matching token was found,
1200 * <code>false</code> otherwise
1202 private boolean skipScope(int openToken, int closeToken) {
1209 if (fToken == closeToken) {
1211 } else if (fToken == openToken) {
1215 } else if (fToken == Symbols.TokenEOF) {
1221 // TODO adjust once there are per-project settings
1223 private int prefTabLength() {
1225 // JavaCore core= JavaCore.getJavaCore();
1226 PHPeclipsePlugin plugin = PHPeclipsePlugin.getDefault();
1227 // if (core != null && plugin != null)
1231 .getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
1232 // if the formatter uses chars to mark indentation, then don't
1233 // substitute any chars
1234 tabLen = -1; // results in no tabs being substituted for
1237 // if the formatter uses tabs to mark indentations, use the
1238 // visual setting from the editor
1239 // to get nicely aligned indentations
1241 .getPreferenceStore()
1243 AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
1245 tabLen = 4; // sensible default for testing
1250 private boolean prefArrayDimensionsDeepIndent() {
1251 return true; // sensible default
1254 private int prefArrayIndent() {
1255 Plugin plugin = JavaCore.getPlugin();
1256 if (plugin != null) {
1257 String option = JavaCore
1258 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
1260 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1262 } catch (IllegalArgumentException e) {
1263 // ignore and return default
1267 return prefContinuationIndent(); // default
1270 private boolean prefArrayDeepIndent() {
1271 Plugin plugin = JavaCore.getPlugin();
1272 if (plugin != null) {
1273 String option = JavaCore
1274 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
1276 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1277 } catch (IllegalArgumentException e) {
1278 // ignore and return default
1285 private boolean prefTernaryDeepAlign() {
1286 Plugin plugin = JavaCore.getPlugin();
1287 if (plugin != null) {
1288 String option = JavaCore
1289 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
1291 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1292 } catch (IllegalArgumentException e) {
1293 // ignore and return default
1299 private int prefTernaryIndent() {
1300 Plugin plugin = JavaCore.getPlugin();
1301 if (plugin != null) {
1302 String option = JavaCore
1303 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
1305 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1308 return prefContinuationIndent();
1309 } catch (IllegalArgumentException e) {
1310 // ignore and return default
1314 return prefContinuationIndent();
1317 private int prefCaseIndent() {
1318 Plugin plugin = JavaCore.getPlugin();
1319 if (plugin != null) {
1320 if (DefaultCodeFormatterConstants.TRUE
1322 .getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_SWITCH)))
1323 return prefBlockIndent();
1328 return 0; // sun standard
1331 private int prefAssignmentIndent() {
1332 return prefBlockIndent();
1335 private int prefCaseBlockIndent() {
1337 return prefBlockIndent();
1339 Plugin plugin = JavaCore.getPlugin();
1340 if (plugin != null) {
1341 if (DefaultCodeFormatterConstants.TRUE
1343 .getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_CASES)))
1344 return prefBlockIndent();
1348 return prefBlockIndent(); // sun standard
1351 private int prefSimpleIndent() {
1352 return prefBlockIndent();
1355 private int prefBracketIndent() {
1356 return prefBlockIndent();
1359 private boolean prefMethodDeclDeepIndent() {
1360 Plugin plugin = JavaCore.getPlugin();
1361 if (plugin != null) {
1362 String option = JavaCore
1363 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
1365 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1366 } catch (IllegalArgumentException e) {
1367 // ignore and return default
1374 private int prefMethodDeclIndent() {
1375 Plugin plugin = JavaCore.getPlugin();
1376 if (plugin != null) {
1377 String option = JavaCore
1378 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
1380 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1383 return prefContinuationIndent();
1384 } catch (IllegalArgumentException e) {
1385 // ignore and return default
1391 private boolean prefMethodCallDeepIndent() {
1392 Plugin plugin = JavaCore.getPlugin();
1393 if (plugin != null) {
1394 String option = JavaCore
1395 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
1397 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1398 } catch (IllegalArgumentException e) {
1399 // ignore and return default
1402 return false; // sensible default
1405 private int prefMethodCallIndent() {
1406 Plugin plugin = JavaCore.getPlugin();
1407 if (plugin != null) {
1408 String option = JavaCore
1409 .getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
1411 if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
1414 return prefContinuationIndent();
1415 } catch (IllegalArgumentException e) {
1416 // ignore and return default
1420 return 1; // sensible default
1423 private boolean prefParenthesisDeepIndent() {
1425 if (true) // don't do parenthesis deep indentation
1428 Plugin plugin = JavaCore.getPlugin();
1429 if (plugin != null) {
1430 String option = JavaCore
1431 .getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
1433 return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
1434 } catch (IllegalArgumentException e) {
1435 // ignore and return default
1439 return false; // sensible default
1442 private int prefParenthesisIndent() {
1443 return prefContinuationIndent();
1446 private int prefBlockIndent() {
1447 return 1; // sensible default
1450 private boolean prefIndentBracesForBlocks() {
1451 Plugin plugin = JavaCore.getPlugin();
1452 if (plugin != null) {
1453 String option = JavaCore
1454 .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_BLOCK);
1456 .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1459 return false; // sensible default
1462 private boolean prefIndentBracesForArrays() {
1463 Plugin plugin = JavaCore.getPlugin();
1464 if (plugin != null) {
1465 String option = JavaCore
1466 .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_ARRAY_INITIALIZER);
1468 .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1471 return false; // sensible default
1474 private boolean prefIndentBracesForMethods() {
1475 Plugin plugin = JavaCore.getPlugin();
1476 if (plugin != null) {
1477 String option = JavaCore
1478 .getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_METHOD_DECLARATION);
1480 .equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
1483 return false; // sensible default
1486 private int prefContinuationIndent() {
1487 Plugin plugin = JavaCore.getPlugin();
1488 if (plugin != null) {
1489 String option = JavaCore
1490 .getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
1492 return Integer.parseInt(option);
1493 } catch (NumberFormatException e) {
1494 // ignore and return default
1498 return 2; // sensible default