/******************************************************************************* * Copyright (c) 2000, 2004 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package net.sourceforge.phpdt.internal.ui.text; import net.sourceforge.phpdt.core.JavaCore; import net.sourceforge.phpdt.core.formatter.DefaultCodeFormatterConstants; import net.sourceforge.phpeclipse.PHPeclipsePlugin; import org.eclipse.core.runtime.Plugin; import org.eclipse.jface.text.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; /** * Uses the {@link net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner}to * get the indentation level for a certain position in a document. * *

* An instance holds some internal position in the document and is therefore * not threadsafe. *

* * @since 3.0 */ public class JavaIndenter { /** The document being scanned. */ private IDocument fDocument; /** The indentation accumulated by findPreviousIndenationUnit. */ private int fIndent; /** * The absolute (character-counted) indentation offset for special cases * (method defs, array initializers) */ private int fAlign; /** The stateful scanposition for the indentation methods. */ private int fPosition; /** The previous position. */ private int fPreviousPos; /** The most recent token. */ private int fToken; /** The line of fPosition. */ private int fLine; /** * The scanner we will use to scan the document. It has to be installed * on the same document as the one we get. */ private JavaHeuristicScanner fScanner; /** * Creates a new instance. * * @param document the document to scan * @param scanner the {@link JavaHeuristicScanner} to be used for scanning * the document. It must be installed on the same IDocument. */ public JavaIndenter(IDocument document, JavaHeuristicScanner scanner) { Assert.isNotNull(document); Assert.isNotNull(scanner); fDocument= document; fScanner= scanner; } /** * Computes the indentation at the reference point of position. * * @param offset the offset in the document * @return a String which reflects the indentation at the line in which the * reference position to offset resides, or null * if it cannot be determined */ public StringBuffer getReferenceIndentation(int offset) { return getReferenceIndentation(offset, false); } /** * Computes the indentation at the reference point of position. * * @param offset the offset in the document * @param assumeOpeningBrace true if an opening brace should be assumed * @return a String which reflects the indentation at the line in which the * reference position to offset resides, or null * if it cannot be determined */ private StringBuffer getReferenceIndentation(int offset, boolean assumeOpeningBrace) { int unit; if (assumeOpeningBrace) unit= findReferencePosition(offset, Symbols.TokenLBRACE); else unit= findReferencePosition(offset, peekChar(offset)); // if we were unable to find anything, return null if (unit == JavaHeuristicScanner.NOT_FOUND) return null; return getLeadingWhitespace(unit); } /** * Computes the indentation at offset. * * @param offset the offset in the document * @return a String which reflects the correct indentation for the line in * which offset resides, or null if it cannot be * determined */ public StringBuffer computeIndentation(int offset) { return computeIndentation(offset, false); } /** * Computes the indentation at offset. * * @param offset the offset in the document * @param assumeOpeningBrace true if an opening brace should be assumed * @return a String which reflects the correct indentation for the line in * which offset resides, or null if it cannot be * determined */ public StringBuffer computeIndentation(int offset, boolean assumeOpeningBrace) { StringBuffer indent= getReferenceIndentation(offset, assumeOpeningBrace); // handle special alignment if (fAlign != JavaHeuristicScanner.NOT_FOUND) { try { // a special case has been detected. IRegion line= fDocument.getLineInformationOfOffset(fAlign); int lineOffset= line.getOffset(); return createIndent(lineOffset, fAlign); } catch (BadLocationException e) { return null; } } if (indent == null) return null; // add additional indent indent.append(createIndent(fIndent)); if (fIndent < 0) unindent(indent); return indent; } /** * Returns the indentation of the line at offset as a * StringBuffer. If the offset is not valid, the empty string * is returned. * * @param offset the offset in the document * @return the indentation (leading whitespace) of the line in which * offset is located */ private StringBuffer getLeadingWhitespace(int offset) { StringBuffer indent= new StringBuffer(); try { IRegion line= fDocument.getLineInformationOfOffset(offset); int lineOffset= line.getOffset(); int nonWS= fScanner.findNonWhitespaceForwardInAnyPartition(lineOffset, lineOffset + line.getLength()); indent.append(fDocument.get(lineOffset, nonWS - lineOffset)); return indent; } catch (BadLocationException e) { return indent; } } /** * Reduces indentation in indent by one indentation unit. * * @param indent the indentation to be modified */ private void unindent(StringBuffer indent) { CharSequence oneIndent= createIndent(); int i= indent.lastIndexOf(oneIndent.toString()); //$NON-NLS-1$ if (i != -1) { indent.delete(i, i + oneIndent.length()); } } /** * Creates an indentation string of the length indent - start + 1, * consisting of the content in fDocument in the range * [start, indent), with every character replaced by a space except for * tabs, which are kept as such. * *

Every run of the number of spaces that make up a tab are replaced * by a tab character.

* * @return the indentation corresponding to the document content specified * by start and indent */ private StringBuffer createIndent(int start, int indent) { final int tabLen= prefTabLength(); StringBuffer ret= new StringBuffer(); try { int spaces= 0; while (start < indent) { char ch= fDocument.getChar(start); if (ch == '\t') { ret.append('\t'); spaces= 0; } else if (tabLen == -1){ ret.append(' '); } else { spaces++; if (spaces == tabLen) { ret.append('\t'); spaces= 0; } } start++; } // remainder if (spaces == tabLen) ret.append('\t'); else while (spaces-- > 0) ret.append(' '); } catch (BadLocationException e) { } return ret; } /** * Creates a string that represents the given number of indents (can be * spaces or tabs..) * * @param indent the requested indentation level. * * @return the indentation specified by indent */ public StringBuffer createIndent(int indent) { StringBuffer oneIndent= createIndent(); StringBuffer ret= new StringBuffer(); while (indent-- > 0) ret.append(oneIndent); return ret; } /** * Creates a string that represents one indent (can be * spaces or tabs..) * * @return one indentation */ private StringBuffer createIndent() { // get a sensible default when running without the infrastructure for testing StringBuffer oneIndent= new StringBuffer(); // JavaCore plugin= JavaCore.getJavaCore(); PHPeclipsePlugin plugin = PHPeclipsePlugin.getDefault(); if (plugin == null) { oneIndent.append('\t'); } else { if (JavaCore.SPACE.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) { int tabLen= Integer.parseInt(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE)); for (int i= 0; i < tabLen; i++) oneIndent.append(' '); } else if (JavaCore.TAB.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) oneIndent.append('\t'); else oneIndent.append('\t'); // default } return oneIndent; } /** * Returns the reference position regarding to indentation for offset, * or NOT_FOUND. This method calls * {@link #findReferencePosition(int, int) findReferencePosition(offset, nextChar)} where * nextChar is the next character after offset. * * @param offset the offset for which the reference is computed * @return the reference statement relative to which offset * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND} */ public int findReferencePosition(int offset) { return findReferencePosition(offset, peekChar(offset)); } /** * Peeks the next char in the document that comes after offset * on the same line as offset. * * @param offset the offset into document * @return the token symbol of the next element, or TokenEOF if there is none */ private int peekChar(int offset) { if (offset < fDocument.getLength()) { try { IRegion line= fDocument.getLineInformationOfOffset(offset); int lineOffset= line.getOffset(); int next= fScanner.nextToken(offset, lineOffset + line.getLength()); return next; } catch (BadLocationException e) { } } return Symbols.TokenEOF; } /** * Returns the reference position regarding to indentation for position, * or NOT_FOUND. * *

If peekNextChar is true, the next token after * offset is read and taken into account when computing the * indentation. Currently, if the next token is the first token on the line * (i.e. only preceded by whitespace), the following tokens are specially * handled: *

* * @param offset the offset for which the reference is computed * @param nextToken the next token to assume in the document * @return the reference statement relative to which offset * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND} */ public int findReferencePosition(int offset, int nextToken) { boolean danglingElse= false; boolean unindent= false; boolean indent= false; boolean matchBrace= false; boolean matchParen= false; boolean matchCase= false; // account for unindenation characters already typed in, but after position // if they are on a line by themselves, the indentation gets adjusted // accordingly // // also account for a dangling else if (offset < fDocument.getLength()) { try { IRegion line= fDocument.getLineInformationOfOffset(offset); int lineOffset= line.getOffset(); int prevPos= Math.max(offset - 1, 0); boolean isFirstTokenOnLine= fDocument.get(lineOffset, prevPos + 1 - lineOffset).trim().length() == 0; int prevToken= fScanner.previousToken(prevPos, JavaHeuristicScanner.UNBOUND); boolean bracelessBlockStart= fScanner.isBracelessBlockStart(prevPos, JavaHeuristicScanner.UNBOUND); switch (nextToken) { case Symbols.TokenEOF: case Symbols.TokenELSE: danglingElse= true; break; case Symbols.TokenCASE: case Symbols.TokenDEFAULT: if (isFirstTokenOnLine) matchCase= true; break; case Symbols.TokenLBRACE: // for opening-brace-on-new-line style // if (bracelessBlockStart && !prefIndentBracesForBlocks()) // unindent= true; // else if ((prevToken == Symbols.TokenCOLON || prevToken == Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET) && !prefIndentBracesForArrays()) // unindent= true; // else if (!bracelessBlockStart && prefIndentBracesForMethods()) // indent= true; // break; if (bracelessBlockStart ) unindent= true; else if ((prevToken == Symbols.TokenCOLON || prevToken == Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET) ) unindent= true; else if (!bracelessBlockStart) indent= true; break; case Symbols.TokenRBRACE: // closing braces get unindented if (isFirstTokenOnLine) matchBrace= true; break; case Symbols.TokenRPAREN: if (isFirstTokenOnLine) matchParen= true; break; } } catch (BadLocationException e) { } } else { // assume an else could come if we are at the end of file danglingElse= true; } int ref= findReferencePosition(offset, danglingElse, matchBrace, matchParen, matchCase); if (unindent) fIndent--; if (indent) fIndent++; return ref; } /** * Returns the reference position regarding to indentation for position, * or NOT_FOUND.fIndent will contain the * relative indentation (in indentation units, not characters) after the * call. If there is a special alignment (e.g. for a method declaration * where parameters should be aligned), fAlign will contain * the absolute position of the alignment reference in fDocument, * otherwise fAlign is set to JavaHeuristicScanner.NOT_FOUND. * * @param offset the offset for which the reference is computed * @param danglingElse whether a dangling else should be assumed at position * @param matchBrace whether the position of the matching brace should be * returned instead of doing code analysis * @param matchParen whether the position of the matching parenthesis * should be returned instead of doing code analysis * @param matchCase whether the position of a switch statement reference * should be returned (either an earlier case statement or the * switch block brace) * @return the reference statement relative to which position * should be indented, or {@link JavaHeuristicScanner#NOT_FOUND} */ public int findReferencePosition(int offset, boolean danglingElse, boolean matchBrace, boolean matchParen, boolean matchCase) { fIndent= 0; // the indentation modification fAlign= JavaHeuristicScanner.NOT_FOUND; fPosition= offset; // forward cases // an unindentation happens sometimes if the next token is special, namely on braces, parens and case labels // align braces, but handle the case where we align with the method declaration start instead of // the opening brace. // if (matchBrace) { // if (skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE)) { // try { // // align with the opening brace that is on a line by its own // int lineOffset= fDocument.getLineOffset(fLine); // if (lineOffset <= fPosition && fDocument.get(lineOffset, fPosition - lineOffset).trim().length() == 0) // return fPosition; // } catch (BadLocationException e) { // // concurrent modification - walk default path // } // // if the opening brace is not on the start of the line, skip to the start // int pos= skipToStatementStart(true, true); // fIndent= 0; // indent is aligned with reference position // return pos; // } else { // // if we can't find the matching brace, the heuristic is to unindent // // by one against the normal position // int pos= findReferencePosition(offset, danglingElse, false, matchParen, matchCase); // fIndent--; // return pos; // } // } // align parenthesis' if (matchParen) { if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN)) return fPosition; else { // if we can't find the matching paren, the heuristic is to unindent // by one against the normal position int pos= findReferencePosition(offset, danglingElse, matchBrace, false, matchCase); fIndent--; return pos; } } // the only reliable way to get case labels aligned (due to many different styles of using braces in a block) // is to go for another case statement, or the scope opening brace // if (matchCase) { // return matchCaseAlignment(); // } nextToken(); switch (fToken) { case Symbols.TokenRBRACE: // skip the block and fall through // if we can't complete the scope, reset the scan position int pos= fPosition; if (!skipScope()) fPosition= pos; case Symbols.TokenSEMICOLON: // this is the 90% case: after a statement block // the end of the previous statement / block previous.end // search to the end of the statement / block before the previous; the token just after that is previous.start return skipToStatementStart(danglingElse, false); // scope introduction: special treat who special is case Symbols.TokenLPAREN: case Symbols.TokenLBRACE: case Symbols.TokenLBRACKET: return handleScopeIntroduction(offset + 1); case Symbols.TokenEOF: // trap when hitting start of document return 0; case Symbols.TokenEQUAL: // indent assignments fIndent= prefAssignmentIndent(); return fPosition; case Symbols.TokenCOLON: // TODO handle ternary deep indentation fIndent= prefCaseBlockIndent(); return fPosition; case Symbols.TokenQUESTIONMARK: if (prefTernaryDeepAlign()) { setFirstElementAlignment(fPosition, offset + 1); return fPosition; } else { fIndent= prefTernaryIndent(); return fPosition; } // indentation for blockless introducers: case Symbols.TokenDO: case Symbols.TokenWHILE: case Symbols.TokenELSE: fIndent= prefSimpleIndent(); return fPosition; case Symbols.TokenRPAREN: int line= fLine; if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN)) { int scope= fPosition; nextToken(); if (fToken == Symbols.TokenIF || fToken == Symbols.TokenWHILE || fToken == Symbols.TokenFOR) { fIndent= prefSimpleIndent(); return fPosition; } fPosition= scope; if (looksLikeMethodDecl()) { return skipToStatementStart(danglingElse, false); } } // restore fPosition= offset; fLine= line; // else: fall through to default case Symbols.TokenCOMMA: // inside a list of some type // easy if there is already a list item before with its own indentation - we just align // if not: take the start of the list ( LPAREN, LBRACE, LBRACKET ) and either align or // indent by list-indent default: // inside whatever we don't know about: similar to the list case: // if we are inside a continued expression, then either align with a previous line that has indentation // or indent from the expression start line (either a scope introducer or the start of the expr). return skipToPreviousListItemOrListStart(); } } /** * Skips to the start of a statement that ends at the current position. * * @param danglingElse whether to indent aligned with the last if * @param isInBlock whether the current position is inside a block, which limits the search scope to the next scope introducer * @return the reference offset of the start of the statement */ private int skipToStatementStart(boolean danglingElse, boolean isInBlock) { while (true) { nextToken(); if (isInBlock) { switch (fToken) { // exit on all block introducers case Symbols.TokenIF: case Symbols.TokenELSE: case Symbols.TokenSYNCHRONIZED: case Symbols.TokenCOLON: case Symbols.TokenSTATIC: case Symbols.TokenCATCH: case Symbols.TokenDO: case Symbols.TokenWHILE: case Symbols.TokenFINALLY: case Symbols.TokenFOR: case Symbols.TokenTRY: return fPosition; case Symbols.TokenSWITCH: fIndent= prefCaseIndent(); return fPosition; } } switch (fToken) { // scope introduction through: LPAREN, LBRACE, LBRACKET // search stop on SEMICOLON, RBRACE, COLON, EOF // -> the next token is the start of the statement (i.e. previousPos when backward scanning) case Symbols.TokenLPAREN: case Symbols.TokenLBRACE: case Symbols.TokenLBRACKET: case Symbols.TokenSEMICOLON: case Symbols.TokenEOF: return fPreviousPos; case Symbols.TokenCOLON: int pos= fPreviousPos; if (!isConditional()) return pos; break; case Symbols.TokenRBRACE: // RBRACE is a little tricky: it can be the end of an array definition, but // usually it is the end of a previous block pos= fPreviousPos; // store state if (skipScope() && looksLikeArrayInitializerIntro()) continue; // it's an array else return pos; // it's not - do as with all the above // scopes: skip them case Symbols.TokenRPAREN: case Symbols.TokenRBRACKET: pos= fPreviousPos; if (skipScope()) break; else return pos; // IF / ELSE: align the position after the conditional block with the if // so we are ready for an else, except if danglingElse is false // in order for this to work, we must skip an else to its if case Symbols.TokenIF: if (danglingElse) return fPosition; else break; case Symbols.TokenELSE: // skip behind the next if, as we have that one covered pos= fPosition; if (skipNextIF()) break; else return pos; case Symbols.TokenDO: // align the WHILE position with its do return fPosition; case Symbols.TokenWHILE: // this one is tricky: while can be the start of a while loop // or the end of a do - while pos= fPosition; if (hasMatchingDo()) { // continue searching from the DO on break; } else { // continue searching from the WHILE on fPosition= pos; break; } default: // keep searching } } } /** * Returns true if the colon at the current position is part of a conditional * (ternary) expression, false otherwise. * * @return true if the colon at the current position is part of a conditional */ private boolean isConditional() { while (true) { nextToken(); switch (fToken) { // search for case, otherwise return true case Symbols.TokenIDENT: continue; case Symbols.TokenCASE: return false; default: return true; } } } /** * Returns as a reference any previous switch labels (case * or default) or the offset of the brace that scopes the switch * statement. Sets fIndent to prefCaseIndent upon * a match. * * @return the reference offset for a switch label */ // private int matchCaseAlignment() { // while (true) { // nextToken(); // switch (fToken) { // // invalid cases: another case label or an LBRACE must come before a case // // -> bail out with the current position // case Symbols.TokenLPAREN: // case Symbols.TokenLBRACKET: // case Symbols.TokenEOF: // return fPosition; // case Symbols.TokenLBRACE: // // opening brace of switch statement // fIndent= prefCaseIndent(); // return fPosition; // case Symbols.TokenCASE: // case Symbols.TokenDEFAULT: // // align with previous label // fIndent= 0; // return fPosition; // // // scopes: skip them // case Symbols.TokenRPAREN: // case Symbols.TokenRBRACKET: // case Symbols.TokenRBRACE: // skipScope(); // break; // // default: // // keep searching // continue; // // } // } // } /** * Returns the reference position for a list element. The algorithm * tries to match any previous indentation on the same list. If there is none, * the reference position returned is determined depending on the type of list: * The indentation will either match the list scope introducer (e.g. for * method declarations), so called deep indents, or simply increase the * indentation by a number of standard indents. See also {@link #handleScopeIntroduction(int)}. * * @return the reference position for a list item: either a previous list item * that has its own indentation, or the list introduction start. */ private int skipToPreviousListItemOrListStart() { int startLine= fLine; int startPosition= fPosition; while (true) { nextToken(); // if any line item comes with its own indentation, adapt to it if (fLine < startLine) { try { int lineOffset= fDocument.getLineOffset(startLine); int bound= Math.min(fDocument.getLength(), startPosition + 1); fAlign= fScanner.findNonWhitespaceForwardInAnyPartition(lineOffset, bound); } catch (BadLocationException e) { // ignore and return just the position } return startPosition; } switch (fToken) { // scopes: skip them case Symbols.TokenRPAREN: case Symbols.TokenRBRACKET: case Symbols.TokenRBRACE: skipScope(); break; // scope introduction: special treat who special is case Symbols.TokenLPAREN: case Symbols.TokenLBRACE: case Symbols.TokenLBRACKET: return handleScopeIntroduction(startPosition + 1); case Symbols.TokenSEMICOLON: return fPosition; case Symbols.TokenQUESTIONMARK: if (prefTernaryDeepAlign()) { setFirstElementAlignment(fPosition - 1, fPosition + 1); return fPosition; } else { fIndent= prefTernaryIndent(); return fPosition; } case Symbols.TokenEOF: return 0; } } } /** * Skips a scope and positions the cursor (fPosition) on the * token that opens the scope. Returns true if a matching peer * could be found, false otherwise. The current token when calling * must be one out of Symbols.TokenRPAREN, Symbols.TokenRBRACE, * and Symbols.TokenRBRACKET. * * @return true if a matching peer was found, false otherwise */ private boolean skipScope() { switch (fToken) { case Symbols.TokenRPAREN: return skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN); case Symbols.TokenRBRACKET: return skipScope(Symbols.TokenLBRACKET, Symbols.TokenRBRACKET); case Symbols.TokenRBRACE: return skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE); default: Assert.isTrue(false); return false; } } /** * Handles the introduction of a new scope. The current token must be one out * of Symbols.TokenLPAREN, Symbols.TokenLBRACE, * and Symbols.TokenLBRACKET. Returns as the reference position * either the token introducing the scope or - if available - the first * java token after that. * *

Depending on the type of scope introduction, the indentation will align * (deep indenting) with the reference position (fAlign will be * set to the reference position) or fIndent will be set to * the number of indentation units. *

* * @param bound the bound for the search for the first token after the scope * introduction. * @return */ private int handleScopeIntroduction(int bound) { switch (fToken) { // scope introduction: special treat who special is case Symbols.TokenLPAREN: int pos= fPosition; // store // special: method declaration deep indentation if (looksLikeMethodDecl()) { if (prefMethodDeclDeepIndent()) return setFirstElementAlignment(pos, bound); else { fIndent= prefMethodDeclIndent(); return pos; } } else { fPosition= pos; if (looksLikeMethodCall()) { if (prefMethodCallDeepIndent()) return setFirstElementAlignment(pos, bound); else { fIndent= prefMethodCallIndent(); return pos; } } else if (prefParenthesisDeepIndent()) return setFirstElementAlignment(pos, bound); } // normal: return the parenthesis as reference fIndent= prefParenthesisIndent(); return pos; case Symbols.TokenLBRACE: pos= fPosition; // store // special: array initializer if (looksLikeArrayInitializerIntro()) if (prefArrayDeepIndent()) return setFirstElementAlignment(pos, bound); else fIndent= prefArrayIndent(); else fIndent= prefBlockIndent(); // normal: skip to the statement start before the scope introducer // opening braces are often on differently ending indents than e.g. a method definition fPosition= pos; // restore return skipToStatementStart(true, true); // set to true to match the first if case Symbols.TokenLBRACKET: pos= fPosition; // store // special: method declaration deep indentation if (prefArrayDimensionsDeepIndent()) { return setFirstElementAlignment(pos, bound); } // normal: return the bracket as reference fIndent= prefBracketIndent(); return pos; // restore default: Assert.isTrue(false); return -1; // dummy } } /** * Sets the deep indent offset (fAlign) to either the offset * right after scopeIntroducerOffset or - if available - the * first Java token after scopeIntroducerOffset, but before * bound. * * @param scopeIntroducerOffset the offset of the scope introducer * @param bound the bound for the search for another element * @return the reference position */ private int setFirstElementAlignment(int scopeIntroducerOffset, int bound) { int firstPossible= scopeIntroducerOffset + 1; // align with the first position after the scope intro fAlign= fScanner.findNonWhitespaceForwardInAnyPartition(firstPossible, bound); if (fAlign == JavaHeuristicScanner.NOT_FOUND) fAlign= firstPossible; return fAlign; } /** * Returns true if the next token received after calling * nextToken is either an equal sign or an array designator ('[]'). * * @return true if the next elements look like the start of an array definition */ private boolean looksLikeArrayInitializerIntro() { nextToken(); if (fToken == Symbols.TokenEQUAL || skipBrackets()) { return true; } return false; } /** * Skips over the next if keyword. The current token when calling * this method must be an else keyword. Returns true * if a matching if could be found, false otherwise. * The cursor (fPosition) is set to the offset of the if * token. * * @return true if a matching if token was found, false otherwise */ private boolean skipNextIF() { Assert.isTrue(fToken == Symbols.TokenELSE); while (true) { nextToken(); switch (fToken) { // scopes: skip them case Symbols.TokenRPAREN: case Symbols.TokenRBRACKET: case Symbols.TokenRBRACE: skipScope(); break; case Symbols.TokenIF: // found it, return return true; case Symbols.TokenELSE: // recursively skip else-if blocks skipNextIF(); break; // shortcut scope starts case Symbols.TokenLPAREN: case Symbols.TokenLBRACE: case Symbols.TokenLBRACKET: case Symbols.TokenEOF: return false; } } } /** * while(condition); is ambiguous when parsed backwardly, as it is a valid * statement by its own, so we have to check whether there is a matching * do. A do can either be separated from the while by a * block, or by a single statement, which limits our search distance. * * @return true if the while currently in * fToken has a matching do. */ private boolean hasMatchingDo() { Assert.isTrue(fToken == Symbols.TokenWHILE); nextToken(); switch (fToken) { case Symbols.TokenRBRACE: skipScope(); // and fall thru case Symbols.TokenSEMICOLON: skipToStatementStart(false, false); return fToken == Symbols.TokenDO; } return false; } /** * Skips brackets if the current token is a RBRACKET. There can be nothing * but whitespace in between, this is only to be used for [] elements. * * @return true if a [] could be scanned, the * current token is left at the LBRACKET. */ private boolean skipBrackets() { if (fToken == Symbols.TokenRBRACKET) { nextToken(); if (fToken == Symbols.TokenLBRACKET) { return true; } } return false; } /** * Reads the next token in backward direction from the heuristic scanner * and sets the fields fToken, fPreviousPosition and fPosition * accordingly. */ private void nextToken() { nextToken(fPosition); } /** * Reads the next token in backward direction of start from * the heuristic scanner and sets the fields fToken, fPreviousPosition * and fPosition accordingly. */ private void nextToken(int start) { fToken= fScanner.previousToken(start - 1, JavaHeuristicScanner.UNBOUND); fPreviousPos= start; fPosition= fScanner.getPosition() + 1; try { fLine= fDocument.getLineOfOffset(fPosition); } catch (BadLocationException e) { fLine= -1; } } /** * Returns true if the current tokens look like a method * declaration header (i.e. only the return type and method name). The * heuristic calls nextToken and expects an identifier * (method name) and a type declaration (an identifier with optional * brackets) which also covers the visibility modifier of constructors; it * does not recognize package visible constructors. * * @return true if the current position looks like a method * declaration header. */ private boolean looksLikeMethodDecl() { /* * TODO This heuristic does not recognize package private constructors * since those do have neither type nor visibility keywords. * One option would be to go over the parameter list, but that might * be empty as well - hard to do without an AST... */ nextToken(); if (fToken == Symbols.TokenIDENT) { // method name do nextToken(); while (skipBrackets()); // optional brackets for array valued return types return fToken == Symbols.TokenIDENT; // type name } return false; } /** * Returns true if the current tokens look like a method * call header (i.e. an identifier as opposed to a keyword taking parenthesized * parameters such as if). *

The heuristic calls nextToken and expects an identifier * (method name). * * @return true if the current position looks like a method call * header. */ private boolean looksLikeMethodCall() { nextToken(); return fToken == Symbols.TokenIDENT; // method name } /** * Scans tokens for the matching opening peer. The internal cursor * (fPosition) is set to the offset of the opening peer if found. * * @return true if a matching token was found, false * otherwise */ private boolean skipScope(int openToken, int closeToken) { int depth= 1; while (true) { nextToken(); if (fToken == closeToken) { depth++; } else if (fToken == openToken) { depth--; if (depth == 0) return true; } else if (fToken == Symbols.TokenEOF) { return false; } } } // TODO adjust once there are per-project settings private int prefTabLength() { int tabLen; // JavaCore core= JavaCore.getJavaCore(); PHPeclipsePlugin plugin= PHPeclipsePlugin.getDefault(); // if (core != null && plugin != null) if (plugin != null) if (JavaCore.SPACE.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) // if the formatter uses chars to mark indentation, then don't substitute any chars tabLen= -1; // results in no tabs being substituted for space runs else // if the formatter uses tabs to mark indentations, use the visual setting from the editor // to get nicely aligned indentations tabLen= plugin.getPreferenceStore().getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH); else tabLen= 4; // sensible default for testing return tabLen; } private boolean prefArrayDimensionsDeepIndent() { return true; // sensible default } private int prefArrayIndent() { Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER); try { if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE) return 1; } catch (IllegalArgumentException e) { // ignore and return default } } return prefContinuationIndent(); // default } private boolean prefArrayDeepIndent() { Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER); try { return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN; } catch (IllegalArgumentException e) { // ignore and return default } } return true; } private boolean prefTernaryDeepAlign() { Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION); try { return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN; } catch (IllegalArgumentException e) { // ignore and return default } } return false; } private int prefTernaryIndent() { Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION); try { if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE) return 1; else return prefContinuationIndent(); } catch (IllegalArgumentException e) { // ignore and return default } } return prefContinuationIndent(); } private int prefCaseIndent() { Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { if (DefaultCodeFormatterConstants.TRUE.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_SWITCH))) return prefBlockIndent(); else return 0; } return 0; // sun standard } private int prefAssignmentIndent() { return prefBlockIndent(); } private int prefCaseBlockIndent() { if (true) return prefBlockIndent(); Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { if (DefaultCodeFormatterConstants.TRUE.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_CASES))) return prefBlockIndent(); else return 0; } return prefBlockIndent(); // sun standard } private int prefSimpleIndent() { return prefBlockIndent(); } private int prefBracketIndent() { return prefBlockIndent(); } private boolean prefMethodDeclDeepIndent() { Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION); try { return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN; } catch (IllegalArgumentException e) { // ignore and return default } } return true; } private int prefMethodDeclIndent() { Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION); try { if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE) return 1; else return prefContinuationIndent(); } catch (IllegalArgumentException e) { // ignore and return default } } return 1; } private boolean prefMethodCallDeepIndent() { Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION); try { return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN; } catch (IllegalArgumentException e) { // ignore and return default } } return false; // sensible default } private int prefMethodCallIndent() { Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION); try { if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE) return 1; else return prefContinuationIndent(); } catch (IllegalArgumentException e) { // ignore and return default } } return 1; // sensible default } private boolean prefParenthesisDeepIndent() { if (true) // don't do parenthesis deep indentation return false; Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION); try { return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN; } catch (IllegalArgumentException e) { // ignore and return default } } return false; // sensible default } private int prefParenthesisIndent() { return prefContinuationIndent(); } private int prefBlockIndent() { return 1; // sensible default } private boolean prefIndentBracesForBlocks() { Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_BLOCK); return option.equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED); } return false; // sensible default } private boolean prefIndentBracesForArrays() { Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_ARRAY_INITIALIZER); return option.equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED); } return false; // sensible default } private boolean prefIndentBracesForMethods() { Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_METHOD_DECLARATION); return option.equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED); } return false; // sensible default } private int prefContinuationIndent() { Plugin plugin= JavaCore.getPlugin(); if (plugin != null) { String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION); try { return Integer.parseInt(option); } catch (NumberFormatException e) { // ignore and return default } } return 2; // sensible default } }