X-Git-Url: http://secure.phpeclipse.com diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/JavaIndenter.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/JavaIndenter.java new file mode 100644 index 0000000..d6b5cf1 --- /dev/null +++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/JavaIndenter.java @@ -0,0 +1,1520 @@ +/******************************************************************************* + * 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; +//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(); + 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); + 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(); + 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 + } + +}