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