/******************************************************************************* * 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.ui.WebUI; //import net.sourceforge.phpeclipse.PHPeclipsePlugin; import org.eclipse.core.runtime.Plugin; //incastrix //import org.eclipse.jface.text.Assert; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.TextUtilities; 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)); indent.insert(0, 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(); WebUI plugin = WebUI.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); if (prevToken == Symbols.TokenEOF && nextToken == Symbols.TokenEOF) { ITypedRegion partition = TextUtilities.getPartition(fDocument, IPHPPartitions.PHP_PARTITIONING, offset, true); if (partition.getType().equals(IPHPPartitions.PHP_SINGLELINE_COMMENT)) { fAlign = fScanner.getPosition(); } else { fAlign = JavaHeuristicScanner.NOT_FOUND; } return JavaHeuristicScanner.NOT_FOUND; } 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 = 1; //prefCaseIndent() is for Java 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); } else { fIndent = prefTernaryIndent(); } return fPosition; case Symbols.TokenEOF: return 0; case Symbols.TokenEQUAL: // indent assignments fIndent= prefAssignmentIndent(); return fPosition; } } } /** * 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(); WebUI plugin = WebUI.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 } }