/******************************************************************************* * Copyright (c) 2005, 2007 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * *******************************************************************************/ /** * */ package org.eclipse.dltk.javascript.scriptdoc; import org.eclipse.core.runtime.Assert; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.IScriptProject; import org.eclipse.dltk.internal.ui.text.DocumentCharacterIterator; import org.eclipse.dltk.javascript.internal.ui.JavaScriptUI; import org.eclipse.dltk.javascript.internal.ui.text.JsPreferenceInterpreter; import org.eclipse.dltk.javascript.internal.ui.text.Symbols; import org.eclipse.dltk.javascript.ui.text.IJavaScriptPartitions; import org.eclipse.dltk.ui.text.util.TabStyle; 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; public final class JavaIndenter { /** * */ /** * The JDT Core preferences. * * @since 3.2 */ private final class CorePrefs { final boolean prefUseTabs; final int prefTabSize; final int prefIndentationSize; final boolean prefArrayDimensionsDeepIndent; final int prefArrayIndent; final boolean prefArrayDeepIndent; final boolean prefTernaryDeepAlign; final int prefTernaryIndent; final int prefCaseIndent; final int prefAssignmentIndent; final int prefCaseBlockIndent; final int prefSimpleIndent; final int prefBracketIndent; final boolean prefMethodDeclDeepIndent; final int prefMethodDeclIndent; final boolean prefMethodCallDeepIndent; final int prefMethodCallIndent; final boolean prefParenthesisDeepIndent; final int prefParenthesisIndent; final int prefBlockIndent; final int prefMethodBodyIndent; final int prefTypeIndent; final boolean prefIndentBracesForBlocks; final boolean prefIndentBracesForArrays; final boolean prefIndentBracesForMethods; final boolean prefIndentBracesForTypes; final int prefContinuationIndent; final boolean prefHasGenerics; final String prefTabChar; private final IScriptProject fProject; /** * Returns <code>true</code> if the class is used outside the workbench, * <code>false</code> in normal mode * * @return <code>true</code> if the plug-ins are not available */ private boolean isStandalone() { return DLTKCore.getPlugin() == null; } /** * Returns the possibly project-specific core preference defined under * <code>key</code>. * * @param key * the key of the preference * @return the value of the preference * @since 3.1 */ JsPreferenceInterpreter prefs = new JsPreferenceInterpreter( JavaScriptUI.getDefault().getPreferenceStore()); private String getCoreFormatterOption(String key) { if (this.fProject == null) return DLTKCore.getOption(key); return this.fProject.getOption(key, true); } CorePrefs(IScriptProject project) { this.fProject = project; if (isStandalone()) { prefUseTabs = true; prefTabSize = 4; prefIndentationSize = 4; prefArrayDimensionsDeepIndent = true; prefContinuationIndent = 2; prefBlockIndent = 1; prefArrayIndent = prefContinuationIndent; prefArrayDeepIndent = true; prefTernaryDeepAlign = false; prefTernaryIndent = prefContinuationIndent; prefCaseIndent = 0; prefAssignmentIndent = prefBlockIndent; prefCaseBlockIndent = prefBlockIndent; prefIndentBracesForBlocks = false; prefSimpleIndent = (prefIndentBracesForBlocks && prefBlockIndent == 0) ? 1 : prefBlockIndent; prefBracketIndent = prefBlockIndent; prefMethodDeclDeepIndent = true; prefMethodDeclIndent = 1; prefMethodCallDeepIndent = false; prefMethodCallIndent = 1; prefParenthesisDeepIndent = false; prefParenthesisIndent = prefContinuationIndent; prefMethodBodyIndent = 1; prefTypeIndent = 1; prefIndentBracesForArrays = false; prefIndentBracesForMethods = false; prefIndentBracesForTypes = false; prefHasGenerics = false; prefTabChar = "\t"; } else { prefUseTabs = prefUseTabs(); prefTabSize = prefTabSize(); prefIndentationSize = prefIndentationSize(); prefArrayDimensionsDeepIndent = prefArrayDimensionsDeepIndent(); prefContinuationIndent = prefContinuationIndent(); prefBlockIndent = prefBlockIndent(); prefArrayIndent = prefArrayIndent(); prefArrayDeepIndent = prefArrayDeepIndent(); prefTernaryDeepAlign = prefTernaryDeepAlign(); prefTernaryIndent = prefTernaryIndent(); prefCaseIndent = prefCaseIndent(); prefAssignmentIndent = prefAssignmentIndent(); prefCaseBlockIndent = prefCaseBlockIndent(); prefIndentBracesForBlocks = prefIndentBracesForBlocks(); prefSimpleIndent = prefSimpleIndent(); prefBracketIndent = prefBracketIndent(); prefMethodDeclDeepIndent = prefMethodDeclDeepIndent(); prefMethodDeclIndent = prefMethodDeclIndent(); prefMethodCallDeepIndent = prefMethodCallDeepIndent(); prefMethodCallIndent = prefMethodCallIndent(); prefParenthesisDeepIndent = prefParenthesisDeepIndent(); prefParenthesisIndent = prefParenthesisIndent(); prefMethodBodyIndent = prefMethodBodyIndent(); prefTypeIndent = prefTypeIndent(); prefIndentBracesForArrays = prefIndentBracesForArrays(); prefIndentBracesForMethods = prefIndentBracesForMethods(); prefIndentBracesForTypes = prefIndentBracesForTypes(); prefHasGenerics = hasGenerics(); prefTabChar = prefUseTabs() ? "\t" : " "; } } private boolean prefUseTabs() { if (prefs.getTabStyle() == TabStyle.TAB) { return true; } return false; } public int getTabWidth(IScriptProject project) { /* * If the tab-char is SPACE, FORMATTER_INDENTATION_SIZE is not used * by the core formatter. We piggy back the visual tab length * setting in that preference in that case. */ return prefs.getTabSize(); } private int prefTabSize() { return getTabWidth(this.fProject); } public int getIndentWidth(IScriptProject project) { return prefs.getIndentSize(); } private int prefIndentationSize() { return getIndentWidth(this.fProject); } private boolean prefArrayDimensionsDeepIndent() { return true; // sensible default, no formatter setting } private int prefArrayIndent() { String option = getCoreFormatterOption(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() { String option = getCoreFormatterOption(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() { String option = getCoreFormatterOption(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() { String option = getCoreFormatterOption(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() { if (DefaultCodeFormatterConstants.TRUE .equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_SWITCH))) return prefBlockIndent(); else return 0; } private int prefAssignmentIndent() { return prefBlockIndent(); } private int prefCaseBlockIndent() { if (true) return prefBlockIndent(); if (DefaultCodeFormatterConstants.TRUE .equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_CASES))) return prefBlockIndent(); else return 0; } private int prefSimpleIndent() { if (prefIndentBracesForBlocks() && prefBlockIndent() == 0) return 1; else return prefBlockIndent(); } private int prefBracketIndent() { return prefBlockIndent(); } private boolean prefMethodDeclDeepIndent() { String option = getCoreFormatterOption(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() { String option = getCoreFormatterOption(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() { String option = getCoreFormatterOption(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() { String option = getCoreFormatterOption(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; String option = getCoreFormatterOption(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() { String option = getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_STATEMENTS_COMPARE_TO_BLOCK); if (DefaultCodeFormatterConstants.FALSE.equals(option)) return 0; return 1; // sensible default } private int prefMethodBodyIndent() { if (DefaultCodeFormatterConstants.FALSE .equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_STATEMENTS_COMPARE_TO_BODY))) return 0; return 1; // sensible default } private int prefTypeIndent() { String option = getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_BODY_DECLARATIONS_COMPARE_TO_TYPE_HEADER); if (DefaultCodeFormatterConstants.FALSE.equals(option)) return 0; return 1; // sensible default } private boolean prefIndentBracesForBlocks() { return DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED .equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_BLOCK)); } private boolean prefIndentBracesForArrays() { return DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED .equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_ARRAY_INITIALIZER)); } private boolean prefIndentBracesForMethods() { return DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED .equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_METHOD_DECLARATION)); } private boolean prefIndentBracesForTypes() { return DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED .equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_TYPE_DECLARATION)); } private int prefContinuationIndent() { try { return Integer .parseInt(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION)); } catch (NumberFormatException e) { // ignore and return default } return 2; // sensible default } private boolean hasGenerics() { return false; } } /** The document being scanned. */ private final IDocument fDocument; /** The indentation accumulated by <code>findReferencePosition</code>. */ 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 <code>fPosition</code>. */ 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 final JavaHeuristicScanner fScanner; /** * The JDT Core preferences. * * @since 3.2 */ private final CorePrefs fPrefs; /** * 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 * <code>IDocument</code>. * @param javaAutoIndentStrategy * TODO */ /** * 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 * <code>IDocument</code>. * @param project * the java project to get the formatter preferences from, or * <code>null</code> to use the workspace settings * @param javaAutoIndentStrategy * TODO * @since 3.1 */ public JavaIndenter(IDocument document, JavaHeuristicScanner scanner, IScriptProject project) { Assert.isNotNull(document); Assert.isNotNull(scanner); fDocument = document; fScanner = scanner; fPrefs = new CorePrefs(project); } /** * Computes the indentation at the reference point of <code>position</code>. * * @param offset * the offset in the document * @return a String which reflects the indentation at the line in which the * reference position to <code>offset</code> resides, or * <code>null</code> if it cannot be determined */ public StringBuffer getReferenceIndentation(int offset) { return getReferenceIndentation(offset, false); } /** * Computes the indentation at the reference point of <code>position</code>. * * @param offset * the offset in the document * @param assumeOpeningBrace * <code>true</code> if an opening brace should be assumed * @return a String which reflects the indentation at the line in which the * reference position to <code>offset</code> resides, or * <code>null</code> 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 <code>offset</code>. * * @param offset * the offset in the document * @return a String which reflects the correct indentation for the line in * which offset resides, or <code>null</code> if it cannot be * determined */ public StringBuffer computeIndentation(int offset) { return computeIndentation(offset, false); } /** * Computes the indentation at <code>offset</code>. * * @param offset * the offset in the document * @param assumeOpeningBrace * <code>true</code> if an opening brace should be assumed * @return a String which reflects the correct indentation for the line in * which offset resides, or <code>null</code> if it cannot be * determined */ public StringBuffer computeIndentation(int offset, boolean assumeOpeningBrace) { StringBuffer reference = 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, true); } catch (BadLocationException e) { return null; } } if (reference == null) return null; // add additional indent return createReusingIndent(reference, fIndent); } /** * Computes the length of a <code>CharacterSequence</code>, counting a tab * character as the size until the next tab stop and every other character * as one. * * @param indent * the string to measure * @return the visual length in characters */ private int computeVisualLength(CharSequence indent) { final int tabSize = fPrefs.prefTabSize; int length = 0; for (int i = 0; i < indent.length(); i++) { char ch = indent.charAt(i); switch (ch) { case '\t': if (tabSize > 0) { int reminder = length % tabSize; length += tabSize - reminder; } break; case ' ': length++; break; } } return length; } /** * Strips any characters off the end of <code>reference</code> that exceed * <code>indentLength</code>. * * @param reference * the string to measure * @param indentLength * the maximum visual indentation length * @return the stripped <code>reference</code> */ private StringBuffer stripExceedingChars(StringBuffer reference, int indentLength) { final int tabSize = fPrefs.prefTabSize; int measured = 0; int chars = reference.length(); int i = 0; for (; measured < indentLength && i < chars; i++) { char ch = reference.charAt(i); switch (ch) { case '\t': if (tabSize > 0) { int reminder = measured % tabSize; measured += tabSize - reminder; } break; case ' ': measured++; break; } } int deleteFrom = measured > indentLength ? i - 1 : i; return reference.delete(deleteFrom, chars); } /** * Returns the indentation of the line at <code>offset</code> as a * <code>StringBuffer</code>. 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 * <code>offset</code> is located */ private StringBuffer getLeadingWhitespace(int offset) { StringBuffer indent = new StringBuffer(); try { IRegion line = fDocument.getLineInformationOfOffset(offset); int lineOffset = line.getOffset(); ITypedRegion partition = TextUtilities.getPartition(fDocument, IJavaScriptPartitions.JS_PARTITIONING, lineOffset, true); String type = partition.getType(); if ((type.equals(IJavaScriptPartitions.JS_DOC) || type .equals(IJavaScriptPartitions.JS_MULTI_LINE_COMMENT)) && lineOffset > 0) { line = fDocument.getLineInformationOfOffset(partition .getOffset()); 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; } } /** * Creates an indentation string of the length indent - start, consisting of * the content in <code>fDocument</code> in the range [start, indent), with * every character replaced by a space except for tabs, which are kept as * such. * <p> * If <code>convertSpaceRunsToTabs</code> is <code>true</code>, every run of * the number of spaces that make up a tab are replaced by a tab character. * If it is not set, no conversion takes place, but tabs in the original * range are still copied verbatim. * </p> * * @param start * the start of the document region to copy the indent from * @param indent * the exclusive end of the document region to copy the indent * from * @param convertSpaceRunsToTabs * whether to convert consecutive runs of spaces to tabs * @return the indentation corresponding to the document content specified * by <code>start</code> and <code>indent</code> */ private StringBuffer createIndent(int start, final int indent, final boolean convertSpaceRunsToTabs) { final boolean convertTabs = fPrefs.prefUseTabs && convertSpaceRunsToTabs; final int tabLen = fPrefs.prefTabSize; final 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 (convertTabs) { spaces++; if (spaces == tabLen) { ret.append('\t'); spaces = 0; } } else { ret.append(' '); } start++; } // remainder while (spaces-- > 0) ret.append(' '); } catch (BadLocationException e) { } return ret; } /** * Creates a string with a visual length of the given * <code>indentationSize</code>. * * @param buffer * the original indent to reuse if possible * @param additional * the additional indentation units to add or subtract to * reference * @return the modified <code>buffer</code> reflecting the indentation * adapted to <code>additional</code> */ private StringBuffer createReusingIndent(StringBuffer buffer, int additional) { int refLength = computeVisualLength(buffer); int addLength = fPrefs.prefIndentationSize * additional; // may be < // 0 int totalLength = Math.max(0, refLength + addLength); // copy the reference indentation for the indent up to the last tab // stop within the maxCopy area int minLength = Math.min(totalLength, refLength); int tabSize = fPrefs.prefTabSize; int maxCopyLength = tabSize > 0 ? minLength - minLength % tabSize : minLength; // maximum indent to copy stripExceedingChars(buffer, maxCopyLength); // add additional indent int missing = totalLength - maxCopyLength; final int tabs, spaces; if (" ".equals(fPrefs.prefTabChar)) { tabs = 0; spaces = missing; } else if ("\t".equals(fPrefs.prefTabChar)) { tabs = tabSize > 0 ? missing / tabSize : 0; spaces = tabSize > 0 ? missing % tabSize : missing; } else if (DefaultCodeFormatterConstants.MIXED .equals(fPrefs.prefTabChar)) { tabs = tabSize > 0 ? missing / tabSize : 0; spaces = tabSize > 0 ? missing % tabSize : missing; } else { tabs = 0; spaces = missing; // return ; } for (int i = 0; i < tabs; i++) buffer.append('\t'); for (int i = 0; i < spaces; i++) buffer.append(' '); return buffer; } /** * Returns the reference position regarding to indentation for * <code>offset</code>, or <code>NOT_FOUND</code>. This method calls * {@link #findReferencePosition(int, int) findReferencePosition(offset, * nextChar)} where <code>nextChar</code> is the next character after * <code>offset</code>. * * @param offset * the offset for which the reference is computed * @return the reference statement relative to which <code>offset</code> * 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 <code>offset</code> * on the same line as <code>offset</code>. * * @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 * <code>position</code>, or <code>NOT_FOUND</code>. * * <p> * If <code>peekNextChar</code> is <code>true</code>, the next token after * <code>offset</code> 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: * <ul> * <li><code>switch</code> labels are indented relative to the switch block</li> * <li>opening curly braces are aligned correctly with the introducing code</li> * <li>closing curly braces are aligned properly with the introducing code * of the matching opening brace</li> * <li>closing parenthesis' are aligned with their opening peer</li> * <li>the <code>else</code> keyword is aligned with its <code>if</code>, * anything else is aligned normally (i.e. with the base of any introducing * statements).</li> * <li>if there is no token on the same line after <code>offset</code>, the * indentation is the same as for an <code>else</code> keyword</li> * </ul> * * @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 <code>offset</code> * 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 un-indentation 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.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 && !fPrefs.prefIndentBracesForBlocks) unindent = true; else if ((prevToken == Symbols.TokenCOLON || prevToken == Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET) && !fPrefs.prefIndentBracesForArrays) unindent = true; else if (!bracelessBlockStart && fPrefs.prefIndentBracesForMethods) 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 { // don't assume an else could come if we are at the end of file danglingElse = false; } int ref = findReferencePosition(offset, danglingElse, matchBrace, matchParen, matchCase); if (unindent) fIndent--; if (indent) fIndent++; return ref; } /** * Returns the reference position regarding to indentation for * <code>position</code>, or <code>NOT_FOUND</code>.<code>fIndent</code> * 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), * <code>fAlign</code> will contain the absolute position of the alignment * reference in <code>fDocument</code>, otherwise <code>fAlign</code> is set * to <code>JavaHeuristicScanner.NOT_FOUND</code>. * * @param offset * the offset for which the reference is computed * @param danglingElse * whether a dangling else should be assumed at * <code>position</code> * @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 <code>position</code> * 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.TokenGREATERTHAN: 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 JavaHeuristicScanner.NOT_FOUND; case Symbols.TokenEQUAL: // indent assignments fIndent = fPrefs.prefAssignmentIndent; return fPosition; case Symbols.TokenCOLON: // TODO handle ternary deep indentation fIndent = fPrefs.prefCaseBlockIndent; return fPosition; case Symbols.TokenQUESTIONMARK: if (fPrefs.prefTernaryDeepAlign) { setFirstElementAlignment(fPosition, offset + 1); return fPosition; } else { fIndent = fPrefs.prefTernaryIndent; return fPosition; } // indentation for blockless introducers: case Symbols.TokenDO: case Symbols.TokenWHILE: case Symbols.TokenELSE: fIndent = fPrefs.prefSimpleIndent; return fPosition; case Symbols.TokenTRY: return skipToStatementStart(danglingElse, false); 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 = fPrefs.prefSimpleIndent; return fPosition; } fPosition = scope; if (looksLikeMethodDecl()) { return skipToStatementStart(danglingElse, false); } if (fToken == Symbols.TokenCATCH) { return skipToStatementStart(danglingElse, false); } fPosition = scope; if (looksLikeAnonymousTypeDecl()) { 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 <code>if</code> * @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) { final int NOTHING = 0; final int READ_PARENS = 1; final int READ_IDENT = 2; int mayBeMethodBody = NOTHING; boolean isTypeBody = false; boolean skipFirstSEMICOLON = true; while (true) { nextToken(); while (skipFirstSEMICOLON && fToken == Symbols.TokenSEMICOLON) { nextToken(); } skipFirstSEMICOLON = false; if (isInBlock) { switch (fToken) { // exit on all block introducers case Symbols.TokenIF: case Symbols.TokenELSE: case Symbols.TokenCATCH: case Symbols.TokenDO: case Symbols.TokenWHILE: case Symbols.TokenFINALLY: case Symbols.TokenFOR: case Symbols.TokenTRY: return fPosition; case Symbols.TokenSTATIC: mayBeMethodBody = READ_IDENT; // treat static blocks like // methods break; case Symbols.TokenSYNCHRONIZED: // if inside a method declaration, use body indentation // else use block indentation. if (mayBeMethodBody != READ_IDENT) return fPosition; break; case Symbols.TokenCLASS: case Symbols.TokenINTERFACE: case Symbols.TokenENUM: isTypeBody = true; break; case Symbols.TokenSWITCH: fIndent = fPrefs.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: if (isInBlock) fIndent = getBlockIndent(mayBeMethodBody == READ_IDENT, isTypeBody); // else: fIndent set by previous calls 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 { if (isInBlock) fIndent = getBlockIndent(mayBeMethodBody == READ_IDENT, isTypeBody); return pos; // it's not - do as with all the above } // scopes: skip them case Symbols.TokenRPAREN: if (isInBlock) mayBeMethodBody = READ_PARENS; case Symbols.TokenRBRACKET: case Symbols.TokenGREATERTHAN: 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; } case Symbols.TokenIDENT: if (mayBeMethodBody == READ_PARENS) mayBeMethodBody = READ_IDENT; break; default: // keep searching } } } private int getBlockIndent(boolean isMethodBody, boolean isTypeBody) { if (isTypeBody) return fPrefs.prefTypeIndent + (fPrefs.prefIndentBracesForTypes ? 1 : 0); else if (isMethodBody) return fPrefs.prefMethodBodyIndent + (fPrefs.prefIndentBracesForMethods ? 1 : 0); else return fIndent; } /** * 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() { boolean identifierFound = false; while (true) { nextToken(); switch (fToken) { // search for case labels, which consist of (possibly qualified) // identifiers or numbers case Symbols.TokenIDENT: if (identifierFound) return false; identifierFound = true; continue; case Symbols.TokenOTHER: // dots for qualified constants identifierFound = false; continue; case Symbols.TokenCASE: return false; default: return true; } } } /** * Returns as a reference any previous <code>switch</code> labels ( * <code>case</code> or <code>default</code>) or the offset of the brace * that scopes the switch statement. Sets <code>fIndent</code> to * <code>prefCaseIndent</code> upon a match. * * @return the reference offset for a <code>switch</code> 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 = fPrefs.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: case Symbols.TokenGREATERTHAN: 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: case Symbols.TokenGREATERTHAN: 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 (fPrefs.prefTernaryDeepAlign) { setFirstElementAlignment(fPosition - 1, fPosition + 1); return fPosition; } else { fIndent = fPrefs.prefTernaryIndent; return fPosition; } case Symbols.TokenEOF: return 0; } } } /** * Skips a scope and positions the cursor (<code>fPosition</code>) on the * token that opens the scope. Returns <code>true</code> if a matching peer * could be found, <code>false</code> otherwise. The current token when * calling must be one out of <code>Symbols.TokenRPAREN</code>, * <code>Symbols.TokenRBRACE</code>, and <code>Symbols.TokenRBRACKET</code>. * * @return <code>true</code> if a matching peer was found, * <code>false</code> 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); case Symbols.TokenGREATERTHAN: if (!fPrefs.prefHasGenerics) return false; int storedPosition = fPosition; int storedToken = fToken; int storedLine = fLine; int storedPreviousPos = fPreviousPos; nextToken(); switch (fToken) { case Symbols.TokenIDENT: if (!JavaHeuristicScanner.isGenericStarter(getTokenContent())) break; case Symbols.TokenQUESTIONMARK: case Symbols.TokenGREATERTHAN: if (skipScope(Symbols.TokenLESSTHAN, Symbols.TokenGREATERTHAN)) return true; } // <> are harder to detect - restore the position if we fail fPosition = storedPosition; fToken = storedToken; fLine = storedLine; fPreviousPos = storedPreviousPos; return false; default: Assert.isTrue(false); return false; } } /** * Returns the contents of the current token. * * @return the contents of the current token * @since 3.1 */ private CharSequence getTokenContent() { return new DocumentCharacterIterator(fDocument, fPosition, fPreviousPos); } /** * Handles the introduction of a new scope. The current token must be one * out of <code>Symbols.TokenLPAREN</code>, <code>Symbols.TokenLBRACE</code> * , and <code>Symbols.TokenLBRACKET</code>. Returns as the reference * position either the token introducing the scope or - if available - the * first java token after that. * * <p> * Depending on the type of scope introduction, the indentation will align * (deep indenting) with the reference position (<code>fAlign</code> will be * set to the reference position) or <code>fIndent</code> will be set to the * number of indentation units. * </p> * * @param bound * the bound for the search for the first token after the scope * introduction. * @return the indent */ 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 (fPrefs.prefMethodDeclDeepIndent) return setFirstElementAlignment(pos, bound); else { fIndent = fPrefs.prefMethodDeclIndent; return pos; } } else { fPosition = pos; if (looksLikeMethodCall()) { if (fPrefs.prefMethodCallDeepIndent) return setFirstElementAlignment(pos, bound); else { fIndent = fPrefs.prefMethodCallIndent; return pos; } } else if (fPrefs.prefParenthesisDeepIndent) return setFirstElementAlignment(pos, bound); } // normal: return the parenthesis as reference fIndent = fPrefs.prefParenthesisIndent; return pos; case Symbols.TokenLBRACE: pos = fPosition; // store // special: array initializer // if (looksLikeArrayInitializerIntro()) // if (fPrefs.prefArrayDeepIndent) // return setFirstElementAlignment(pos, bound); // else // fIndent = fPrefs.prefArrayIndent; // else fIndent = fPrefs.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 // if (looksLikeArrayInitializerIntro() // && !fPrefs.prefIndentBracesForArrays // || !fPrefs.prefIndentBracesForBlocks) { // fPosition = pos; // restore // return skipToStatementStart(true, true); // set to true to // // match the first // // if // } else { return pos; // } case Symbols.TokenLBRACKET: pos = fPosition; // store // // special: method declaration deep indentation // if (fPrefs.prefArrayDimensionsDeepIndent) { // return setFirstElementAlignment(pos, bound); // } // // // normal: return the bracket as reference // fIndent = fPrefs.prefBracketIndent; return pos; // restore default: Assert.isTrue(false); return -1; // dummy } } /** * Sets the deep indent offset (<code>fAlign</code>) to either the offset * right after <code>scopeIntroducerOffset</code> or - if available - the * first Java token after <code>scopeIntroducerOffset</code>, but before * <code>bound</code>. * * @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 <code>true</code> if the next token received after calling * <code>nextToken</code> is either an equal sign or an array designator * ('[]'). * * @return <code>true</code> 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 <code>if</code> keyword. The current token when * calling this method must be an <code>else</code> keyword. Returns * <code>true</code> if a matching <code>if</code> could be found, * <code>false</code> otherwise. The cursor (<code>fPosition</code>) is set * to the offset of the <code>if</code> token. * * @return <code>true</code> if a matching <code>if</code> token was found, * <code>false</code> 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: case Symbols.TokenGREATERTHAN: 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 <code>do</code> can either be separated from the while by a block, or * by a single statement, which limits our search distance. * * @return <code>true</code> if the <code>while</code> currently in * <code>fToken</code> has a matching <code>do</code>. */ 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 <code>[]</code> * elements. * * @return <code>true</code> if a <code>[]</code> 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 <code>fToken, fPreviousPosition</code> and * <code>fPosition</code> accordingly. */ private void nextToken() { nextToken(fPosition); } /** * Reads the next token in backward direction of <code>start</code> from the * heuristic scanner and sets the fields * <code>fToken, fPreviousPosition</code> and <code>fPosition</code> * accordingly. * * @param start * the start offset from which to scan backwards */ 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 <code>true</code> if the current tokens look like a method * declaration header (i.e. only the return type and method name). The * heuristic calls <code>nextToken</code> 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 <code>true</code> 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, or not typed in yet - 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; // return type name } return false; } /** * Returns <code>true</code> if the current tokens look like an anonymous * type declaration header (i.e. a type name (potentially qualified) and a * new keyword). The heuristic calls <code>nextToken</code> and expects a * possibly qualified identifier (type name) and a new keyword * * @return <code>true</code> if the current position looks like a anonymous * type declaration header. */ private boolean looksLikeAnonymousTypeDecl() { nextToken(); if (fToken == Symbols.TokenIDENT) { // type name nextToken(); while (fToken == Symbols.TokenOTHER) { // dot of qualification nextToken(); if (fToken != Symbols.TokenIDENT) // qualificating name return false; nextToken(); } return fToken == Symbols.TokenNEW; } return false; } /** * Returns <code>true</code> if the current tokens look like a method call * header (i.e. an identifier as opposed to a keyword taking parenthesized * parameters such as <code>if</code>). * <p> * The heuristic calls <code>nextToken</code> and expects an identifier * (method name). * * @return <code>true</code> if the current position looks like a method * call header. */ private boolean looksLikeMethodCall() { // TODO [5.0] add awareness for constructor calls with generic types: // new ArrayList<String>() nextToken(); return fToken == Symbols.TokenIDENT; // method name } /** * Scans tokens for the matching opening peer. The internal cursor ( * <code>fPosition</code>) is set to the offset of the opening peer if * found. * * @param openToken * the opening peer token * @param closeToken * the closing peer token * @return <code>true</code> if a matching token was found, * <code>false</code> 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; } } } }