/******************************************************************************* * Copyright (c) 2000, 2011 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 * * Contributors: * IBM Corporation - initial API and implementation * Sergey Prigogin (Google) * Anton Leherbauer (Wind River Systems) *******************************************************************************/ package org.eclipse.cdt.internal.ui.text; import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.preferences.DefaultScope; import org.eclipse.core.runtime.preferences.IPreferencesService; import org.eclipse.core.runtime.preferences.IScopeContext; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.internal.corext.util.CodeFormatterUtil; /** * Uses the {@link org.eclipse.cdt.internal.ui.text.CHeuristicScanner} to * get the indentation level for a certain position in a document. * * <p> * An instance holds some internal position in the document and is therefore * not thread-safe. * </p> */ public final class CIndenter { /** * The CDT Core preferences. */ 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 prefCaseBlockIndent; final int prefAssignmentIndent; final int prefSimpleIndent; final int prefBracketIndent; final boolean prefMethodDeclDeepIndent; final boolean prefMethodDeclFirstParameterDeepIndent; final int prefMethodDeclIndent; final boolean prefMethodCallDeepIndent; final boolean prefMethodCallFirstParameterDeepIndent; final int prefMethodCallIndent; final boolean prefParenthesisDeepIndent; final int prefParenthesisIndent; final int prefBlockIndent; final int prefMethodBodyIndent; final int prefTypeIndent; final int prefAccessSpecifierIndent; final int prefAccessSpecifierExtraSpaces; final int prefNamespaceBodyIndent; final boolean prefIndentBracesForBlocks; final boolean prefIndentBracesForArrays; final boolean prefIndentBracesForMethods; final boolean prefIndentBracesForTypes; final int prefContinuationIndent; final boolean prefHasTemplates; final String prefTabChar; private final IPreferencesService preferenceService; private final IScopeContext[] preferenceContexts; private final ICProject fProject; /** * 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 */ private String getCoreFormatterOption(String key) { return getCoreFormatterOption(key, null); } private String getCoreFormatterOption(String key, String defaultValue) { return preferenceService.getString(CCorePlugin.PLUGIN_ID, key, defaultValue, preferenceContexts); } private int getCoreFormatterOption(String key, int defaultValue) { return preferenceService.getInt(CCorePlugin.PLUGIN_ID, key, defaultValue, preferenceContexts); } CorePrefs(ICProject project) { preferenceService = Platform.getPreferencesService(); preferenceContexts = project != null ? new IScopeContext[] { new ProjectScope(project.getProject()), InstanceScope.INSTANCE, DefaultScope.INSTANCE } : new IScopeContext[] { InstanceScope.INSTANCE, DefaultScope.INSTANCE }; fProject= project; prefUseTabs= prefUseTabs(); prefTabSize= prefTabSize(); prefIndentationSize= prefIndentationSize(); prefArrayDimensionsDeepIndent= prefArrayDimensionsDeepIndent(); prefContinuationIndent= prefContinuationIndent(); prefBlockIndent= prefBlockIndent(); prefArrayIndent= prefArrayIndent(); prefArrayDeepIndent= prefArrayDeepIndent(); prefTernaryDeepAlign= false; prefTernaryIndent= prefContinuationIndent(); prefCaseIndent= prefCaseIndent(); prefCaseBlockIndent= prefCaseBlockIndent(); prefAssignmentIndent= prefAssignmentIndent(); prefIndentBracesForBlocks= prefIndentBracesForBlocks(); prefSimpleIndent= prefSimpleIndent(); prefBracketIndent= prefBracketIndent(); prefMethodDeclDeepIndent= prefMethodDeclDeepIndent(); prefMethodDeclFirstParameterDeepIndent= prefMethodDeclFirstParameterDeepIndent(); prefMethodDeclIndent= prefMethodDeclIndent(); prefMethodCallDeepIndent= prefMethodCallDeepIndent(); prefMethodCallFirstParameterDeepIndent= prefMethodCallFirstParameterDeepIndent(); prefMethodCallIndent= prefMethodCallIndent(); prefParenthesisDeepIndent= prefParenthesisDeepIndent(); prefParenthesisIndent= prefParenthesisIndent(); prefMethodBodyIndent= prefMethodBodyIndent(); prefTypeIndent= prefTypeIndent(); prefAccessSpecifierIndent= prefAccessSpecifierIndent(); prefAccessSpecifierExtraSpaces= prefAccessSpecifierExtraSpaces(); prefNamespaceBodyIndent= prefNamespaceBodyIndent(); prefIndentBracesForArrays= prefIndentBracesForArrays(); prefIndentBracesForMethods= prefIndentBracesForMethods(); prefIndentBracesForTypes= prefIndentBracesForTypes(); prefHasTemplates= hasTemplates(); prefTabChar= getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR); } private boolean prefUseTabs() { return !CCorePlugin.SPACE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)); } private int prefTabSize() { return CodeFormatterUtil.getTabWidth(fProject); } private int prefIndentationSize() { return CodeFormatterUtil.getIndentWidth(fProject); } private boolean prefArrayDimensionsDeepIndent() { return true; // sensible default, no formatter setting } private int prefArrayIndent() { String option= getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_INITIALIZER_LIST); 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_INITIALIZER_LIST); try { return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN; } catch (IllegalArgumentException e) { // ignore and return default } return true; } private int prefCaseIndent() { if (DefaultCodeFormatterConstants.TRUE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_SWITCH))) return 1; else return 0; } private int prefCaseBlockIndent() { if (DefaultCodeFormatterConstants.TRUE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_CASES))) return 1; else return 0; } private int prefAssignmentIndent() { return prefContinuationIndent(); } 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 { int indentStyle = DefaultCodeFormatterConstants.getIndentStyle(option); return indentStyle == DefaultCodeFormatterConstants.INDENT_ON_COLUMN; } catch (IllegalArgumentException e) { // ignore and return default } return false; } private boolean prefMethodDeclFirstParameterDeepIndent() { String option= getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION); try { int indentStyle = DefaultCodeFormatterConstants.getIndentStyle(option); int wrappingStyle = DefaultCodeFormatterConstants.getWrappingStyle(option); return indentStyle == DefaultCodeFormatterConstants.INDENT_ON_COLUMN && (wrappingStyle == DefaultCodeFormatterConstants.WRAP_COMPACT_FIRST_BREAK || wrappingStyle == DefaultCodeFormatterConstants.WRAP_ONE_PER_LINE); } catch (IllegalArgumentException e) { // ignore and return default } return false; } 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 { int indentStyle = DefaultCodeFormatterConstants.getIndentStyle(option); return indentStyle == DefaultCodeFormatterConstants.INDENT_ON_COLUMN; } catch (IllegalArgumentException e) { // ignore and return default } return false; // sensible default } private boolean prefMethodCallFirstParameterDeepIndent() { String option= getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION); try { int indentStyle = DefaultCodeFormatterConstants.getIndentStyle(option); int wrappingStyle = DefaultCodeFormatterConstants.getWrappingStyle(option); return indentStyle == DefaultCodeFormatterConstants.INDENT_ON_COLUMN && (wrappingStyle == DefaultCodeFormatterConstants.WRAP_COMPACT_FIRST_BREAK || wrappingStyle == DefaultCodeFormatterConstants.WRAP_ONE_PER_LINE); } 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() { // don't do parenthesis deep indentation // 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; } 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_ACCESS_SPECIFIER); if (DefaultCodeFormatterConstants.FALSE.equals(option)) return 0; return 1; // sensible default } private int prefAccessSpecifierIndent() { if (DefaultCodeFormatterConstants.TRUE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_ACCESS_SPECIFIER_COMPARE_TO_TYPE_HEADER))) return 1; else return 0; } private int prefAccessSpecifierExtraSpaces() { return getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_ACCESS_SPECIFIER_EXTRA_SPACES, 0); } private int prefNamespaceBodyIndent() { if (DefaultCodeFormatterConstants.TRUE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_BODY_DECLARATIONS_COMPARE_TO_NAMESPACE_HEADER))) return prefBlockIndent(); else return 0; } 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_INITIALIZER_LIST)); } 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 hasTemplates() { return true; } } /** The document being scanned. */ private final IDocument fDocument; /** The indentation accumulated by <code>findReferencePosition</code>. */ private int fIndent; /** Extra spaces to add on top of fIndent */ private int fExtraSpaces; /** * The absolute (character-counted) indentation offset for special cases * (method defs, array initializers) */ private int fAlign; /** The stateful scan position 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 CHeuristicScanner fScanner; /** * The CDT Core preferences. */ private final CorePrefs fPrefs; /** * Creates a new instance. * * @param document the document to scan * @param scanner the {@link CHeuristicScanner} to be used for scanning * the document. It must be installed on the same <code>IDocument</code>. */ public CIndenter(IDocument document, CHeuristicScanner scanner) { this(document, scanner, null); } /** * Creates a new instance. * * @param document the document to scan * @param scanner the {@link CHeuristicScanner} to be used for scanning * the document. It must be installed on the same * <code>IDocument</code>. * @param project the C/C++ project to get the formatter preferences from, or * <code>null</code> to use the workspace settings */ public CIndenter(IDocument document, CHeuristicScanner scanner, ICProject 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 StringBuilder 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 StringBuilder getReferenceIndentation(int offset, boolean assumeOpeningBrace) { int unit; if (assumeOpeningBrace) unit= findReferencePosition(offset, Symbols.TokenLBRACE); else unit= findReferencePosition(offset, peekToken(offset)); // if we were unable to find anything, return null if (unit == CHeuristicScanner.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 StringBuilder 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 StringBuilder computeIndentation(int offset, boolean assumeOpeningBrace) { StringBuilder reference= getReferenceIndentation(offset, assumeOpeningBrace); // handle special alignment if (fAlign != CHeuristicScanner.NOT_FOUND) { try { // a special case has been detected. IRegion line= fDocument.getLineInformationOfOffset(fAlign); int lineOffset= line.getOffset(); return createIndent(lineOffset, fAlign, false); } catch (BadLocationException e) { return null; } } if (reference == null) return null; // Add additional indent return createReusingIndent(reference, fIndent, fExtraSpaces); } /** * Computes the indentation for a continuation line at <code>offset</code>. * * @param offset the offset in the document * @return a StringBuilder which reflects the correct indentation for * the line in which offset resides, or <code>null</code> if it cannot be * determined. * @throws BadLocationException */ public StringBuilder computeContinuationLineIndentation(int offset) throws BadLocationException { StringBuilder reference= getLeadingWhitespace(offset); IRegion line= fDocument.getLineInformationOfOffset(offset); String string= fDocument.get(line.getOffset(), offset - line.getOffset()); if (string.trim().isEmpty()) return reference; // Add additional indent return createReusingIndent(reference, fPrefs.prefContinuationIndent, 0); } /** * 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 StringBuilder stripExceedingChars(StringBuilder 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>StringBuilder</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 StringBuilder getLeadingWhitespace(int offset) { StringBuilder indent= new StringBuilder(); 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; } } /** * 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 StringBuilder createIndent(int start, final int indent, final boolean convertSpaceRunsToTabs) { final boolean convertTabs= fPrefs.prefUseTabs && convertSpaceRunsToTabs; final int tabLen= fPrefs.prefTabSize; final StringBuilder ret= new StringBuilder(); 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 * @param extraSpaces additional spaces to add to indentation. * @return the modified <code>buffer</code> reflecting the indentation * adapted to <code>additional</code> */ public StringBuilder createReusingIndent(StringBuilder buffer, int additional, int extraSpaces) { int refLength= computeVisualLength(buffer); int addLength= fPrefs.prefIndentationSize * additional + extraSpaces; // 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 (CCorePlugin.SPACE.equals(fPrefs.prefTabChar)) { tabs= 0; spaces= missing; } else { tabs= tabSize > 0 ? missing / tabSize : 0; spaces= tabSize > 0 ? missing % tabSize : missing; } for (int i= 0; i < tabs; i++) buffer.append('\t'); for (int i= 0; i < spaces; i++) buffer.append(' '); return buffer; } /** * Returns relative indent of continuation lines. * @return a number of indentation units. */ public int getContinuationLineIndent() { return fPrefs.prefContinuationIndent; } /** * 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 CHeuristicScanner#NOT_FOUND} */ public int findReferencePosition(int offset) { return findReferencePosition(offset, peekToken(offset)); } /** * Peeks the next token 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 peekToken(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 CHeuristicScanner#NOT_FOUND} */ public int findReferencePosition(int offset, int nextToken) { boolean danglingElse= false; boolean cancelIndent= false; // If set to true, fIndent is ignored. int extraIndent= 0; // Can be either positive or negative. boolean matchBrace= false; boolean matchParen= false; boolean matchCase= false; boolean matchAccessSpecifier= 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, CHeuristicScanner.UNBOUND); boolean bracelessBlockStart= fScanner.isBracelessBlockStart(prevPos, CHeuristicScanner.UNBOUND); switch (nextToken) { case Symbols.TokenELSE: danglingElse= true; break; case Symbols.TokenCASE: case Symbols.TokenDEFAULT: if (isFirstTokenOnLine) matchCase= true; break; case Symbols.TokenPUBLIC: case Symbols.TokenPROTECTED: case Symbols.TokenPRIVATE: if (isFirstTokenOnLine) matchAccessSpecifier= true; break; case Symbols.TokenLBRACE: // for opening-brace-on-new-line style if (bracelessBlockStart) { extraIndent= fPrefs.prefIndentBracesForBlocks ? 0 : -1; } else if (prevToken == Symbols.TokenCOLON && !fPrefs.prefIndentBracesForBlocks) { extraIndent= -1; } else if ((prevToken == Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET) && !fPrefs.prefIndentBracesForArrays) { cancelIndent= true; } else if ((prevToken == Symbols.TokenRPAREN || prevToken == Symbols.TokenCONST) && fPrefs.prefIndentBracesForMethods) { extraIndent= 1; } else if (prevToken == Symbols.TokenIDENT && fPrefs.prefIndentBracesForTypes) { extraIndent= 1; } break; case Symbols.TokenRBRACE: // closing braces get unindented if (isFirstTokenOnLine || prevToken != Symbols.TokenLBRACE) 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, matchAccessSpecifier); if (cancelIndent) { fIndent = 0; } else if (extraIndent > 0) { fAlign= CHeuristicScanner.NOT_FOUND; fIndent += extraIndent; } else { fIndent += extraIndent; } 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>CHeuristicScanner.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) * @param matchAccessSpecifier whether the position of a class body reference * should be returned (either an earlier public/protected/private * or the class body brace) * @return the reference statement relative to which <code>position</code> * should be indented, or {@link CHeuristicScanner#NOT_FOUND} */ public int findReferencePosition(int offset, boolean danglingElse, boolean matchBrace, boolean matchParen, boolean matchCase, boolean matchAccessSpecifier) { fIndent= 0; // the indentation modification fAlign= CHeuristicScanner.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, matchAccessSpecifier); fIndent--; return pos; } } // Align parentheses 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, matchAccessSpecifier); 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(); } // the only reliable way to get access specifiers aligned (due to many different styles of using // braces in a block) is to go for another access specifier, or the scope opening brace. if (matchAccessSpecifier) { return matchAccessSpecifierAlignment(); } nextToken(); // Skip access specifiers while (fToken == Symbols.TokenCOLON && isAccessSpecifier()) { nextToken(); } int line= fLine; 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; return skipToStatementStart(danglingElse, false); 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(Math.min(offset + 1, fDocument.getLength()), true); case Symbols.TokenEOF: // trap when hitting start of document return CHeuristicScanner.NOT_FOUND; case Symbols.TokenEQUAL: // indent assignments fIndent= fPrefs.prefAssignmentIndent; return fPosition; case Symbols.TokenCOLON: pos= fPosition; if (looksLikeCaseStatement()) { fIndent= fPrefs.prefCaseBlockIndent; return pos; } fPosition= pos; if (looksLikeTypeInheritanceDecl()) { fIndent= fPrefs.prefBlockIndent; return pos; } fPosition= pos; if (looksLikeConstructorInitializer()) { fIndent= fPrefs.prefBlockIndent; return pos; } fPosition= pos; if (isConditional()) { fPosition= offset; fLine= line; return skipToPreviousListItemOrListStart(); } fPosition= pos; return skipToPreviousListItemOrListStart(); case Symbols.TokenQUESTIONMARK: if (fPrefs.prefTernaryDeepAlign) { setFirstElementAlignment(fPosition, offset + 1); } 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.TokenRETURN: case Symbols.TokenTYPEDEF: case Symbols.TokenUSING: fIndent = fPrefs.prefContinuationIndent; return fPosition; case Symbols.TokenCONST: nextToken(); if (fToken != Symbols.TokenRPAREN) { return skipToPreviousListItemOrListStart(); } // could be const method decl //$FALL-THROUGH$ case Symbols.TokenRPAREN: 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; } if (fToken == Symbols.TokenSWITCH) { 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 return skipToPreviousListItemOrListStart(); 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. return skipToPreviousListItemOrListStart(); 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 expression). return skipToPreviousListItemOrListStart(); } } /** * Test whether an identifier encountered during scanning is part of * a type declaration, by scanning backward and ignoring any identifiers, commas, * and colons until we hit <code>class</code>, <code>struct</code>, <code>union</code>, * or <code>enum</code>. If any braces, semicolons, or parentheses are encountered, * this is not a type declaration. * @return the reference offset of the start of the statement */ private int matchTypeDeclaration() { while (true) { nextToken(); if (fToken == Symbols.TokenIDENT || fToken == Symbols.TokenCOMMA || fToken == Symbols.TokenCOLON || fToken == Symbols.TokenPUBLIC || fToken == Symbols.TokenPROTECTED || fToken == Symbols.TokenPRIVATE) { continue; } if (fToken == Symbols.TokenCLASS || fToken == Symbols.TokenSTRUCT || fToken == Symbols.TokenUNION) { // inside a type declaration? Only so if not preceded by '(' or ',' as in // a parameter list. To be safe, only accept ';' or EOF int pos= fPosition; nextToken(); if (fToken == Symbols.TokenSEMICOLON || fToken == Symbols.TokenEOF) { return pos; } else { return CHeuristicScanner.NOT_FOUND; } } else { return CHeuristicScanner.NOT_FOUND; } } } /** * Test whether the colon at the current position marks a case statement * * @return <code>true</code> if this looks like a case statement */ private boolean looksLikeCaseStatement() { nextToken(); switch (fToken) { case Symbols.TokenCASE: // char literal got skipped return true; case Symbols.TokenIDENT: nextToken(); while (skipQualifiers()) { nextToken(); } while (fToken == Symbols.TokenMINUS || fToken == Symbols.TokenPLUS) { nextToken(); } if (fToken == Symbols.TokenCASE) { return true; } break; case Symbols.TokenOTHER: nextToken(); if (fToken == Symbols.TokenCASE) { return true; } break; case Symbols.TokenDEFAULT: return true; } return false; } /** * Test whether the colon at the current position marks a type inheritance decl. * * @return <code>true</code> if this looks like a type inheritance decl. */ private boolean looksLikeTypeInheritanceDecl() { nextToken(); switch (fToken) { case Symbols.TokenIDENT: nextToken(); while (skipQualifiers()) { nextToken(); } switch (fToken) { case Symbols.TokenCLASS: case Symbols.TokenSTRUCT: case Symbols.TokenUNION: return true; } break; } return false; } /** * Test whether the colon at the current position marks a constructor initializer list. * * @return <code>true</code> if this looks like a constructor initializer list. */ private boolean looksLikeConstructorInitializer() { nextToken(); if (fToken != Symbols.TokenRPAREN) { return false; } if (!skipScope()) { return false; } nextToken(); if (fToken == Symbols.TokenTHROW) { nextToken(); if (fToken != Symbols.TokenRPAREN) { return false; } if (!skipScope()) { return false; } nextToken(); } if (fToken != Symbols.TokenIDENT) { return false; } nextToken(); switch (fToken) { case Symbols.TokenCOLON: nextToken(); switch (fToken) { case Symbols.TokenCOLON: // A::A() : case Symbols.TokenPUBLIC: // public: A() : case Symbols.TokenPROTECTED: case Symbols.TokenPRIVATE: return true; } return false; case Symbols.TokenLBRACE: // class A { A() : case Symbols.TokenRBRACE: case Symbols.TokenSEMICOLON: return true; } return false; } /** * Test whether the left brace at the current position marks an enum decl. * * @return <code>true</code> if this looks like an enum decl. */ private boolean looksLikeEnumDeclaration() { int pos = fPosition; nextToken(); if (fToken == Symbols.TokenIDENT) { nextToken(); while (skipQualifiers()) { nextToken(); } } if (fToken == Symbols.TokenENUM) { fPosition = pos; return true; } fPosition = pos; return false; } /** * Test whether the colon at the current position marks an access specifier. * * @return <code>true</code> if current position marks an access specifier */ private boolean isAccessSpecifier() { int pos= fPosition; int token = fToken; nextToken(); switch (fToken) { case Symbols.TokenPUBLIC: case Symbols.TokenPROTECTED: case Symbols.TokenPRIVATE: return true; } fToken = token; fPosition= pos; return false; } /** * 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; int startLine = fLine; while (true) { int prevToken= fToken; nextToken(); 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.TokenFOR: case Symbols.TokenTRY: fIndent += fPrefs.prefIndentBracesForBlocks ? 1 : 0; return fPosition; case Symbols.TokenCLASS: case Symbols.TokenSTRUCT: case Symbols.TokenUNION: isTypeBody= true; break; case Symbols.TokenSWITCH: fIndent= fPrefs.prefCaseIndent; return fPosition; } } if (fToken == Symbols.TokenSEMICOLON && fLine == startLine) { // Skip semicolons on the same line. Otherwise we may never reach beginning of a 'for' // statement. continue; } 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: if (peekToken() == Symbols.TokenFOR) { nextToken(); // Consume 'for' fIndent = fPrefs.prefContinuationIndent; return fPosition; } break; case Symbols.TokenLBRACE: case Symbols.TokenSEMICOLON: case Symbols.TokenEOF: if (isInBlock) fIndent= getBlockIndent(mayBeMethodBody == READ_IDENT, isTypeBody); 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()) { if (looksLikeArrayInitializerIntro()) { continue; // it's an array } if (prevToken == Symbols.TokenSEMICOLON) { // end of type def continue; } } 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; // fall thru pos= fPreviousPos; if (skipScope()) break; else return pos; 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; } 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.prefAccessSpecifierIndent; } else if (isMethodBody) { return fPrefs.prefMethodBodyIndent + (fPrefs.prefIndentBracesForMethods ? 1 : 0); } else { return fIndent; } } /** * Returns <code>true</code> if the colon at the current position is part of a conditional * (ternary) expression, <code>false</code> otherwise. * * @return <code>true</code> if the colon at the current position is part of a conditional */ private boolean isConditional() { while (true) { int previous= fToken; nextToken(); switch (fToken) { // search for case labels, which consist of (possibly qualified) identifiers or numbers case Symbols.TokenIDENT: if (previous == Symbols.TokenIDENT) { return false; } // fall thru continue; case Symbols.TokenDOUBLECOLON: case Symbols.TokenOTHER: case Symbols.TokenMINUS: case Symbols.TokenPLUS: continue; case Symbols.TokenQUESTIONMARK: return true; case Symbols.TokenSEMICOLON: case Symbols.TokenLBRACE: case Symbols.TokenRBRACE: case Symbols.TokenCASE: case Symbols.TokenDEFAULT: case Symbols.TokenPUBLIC: case Symbols.TokenPROTECTED: case Symbols.TokenPRIVATE: case Symbols.TokenCLASS: case Symbols.TokenSTRUCT: case Symbols.TokenUNION: 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.TokenSWITCH: // start 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: skipScope(); break; default: // keep searching continue; } } } /** * Returns as a reference any previous access specifiers (<code>public</code>, * <code>protected</code> or <code>default</code>) or the offset of the brace that * scopes the class body. * Sets <code>fIndent</code> to <code>prefAccessSpecifierIndent</code> upon * a match. * * @return the reference offset for an access specifier (public/protected/private) */ private int matchAccessSpecifierAlignment() { while (true) { nextToken(); switch (fToken) { // invalid cases: another access specifier or an LBRACE must come before an access specifier // -> bail out with the current position case Symbols.TokenLPAREN: case Symbols.TokenLBRACKET: case Symbols.TokenEOF: return fPosition; case Symbols.TokenLBRACE: // opening brace of class body int pos= fPosition; int typeDeclPos= matchTypeDeclaration(); fIndent= fPrefs.prefAccessSpecifierIndent; fExtraSpaces = fPrefs.prefAccessSpecifierExtraSpaces; if (typeDeclPos != CHeuristicScanner.NOT_FOUND) { return typeDeclPos; } return pos; case Symbols.TokenPUBLIC: case Symbols.TokenPROTECTED: case Symbols.TokenPRIVATE: // align with previous access specifier 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, boolean)}. * @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; boolean continuationLineCandidate = fToken == Symbols.TokenEQUAL || fToken == Symbols.TokenSHIFTLEFT || fToken == Symbols.TokenRPAREN; while (true) { int previous = fToken; 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); if ((fToken == Symbols.TokenSEMICOLON || fToken == Symbols.TokenRBRACE || fToken == Symbols.TokenLBRACE && !looksLikeArrayInitializerIntro() && !looksLikeEnumDeclaration()) && continuationLineCandidate) { fIndent = fPrefs.prefContinuationIndent; } else { fAlign= fScanner.findNonWhitespaceForwardInAnyPartition(lineOffset, bound); // If the reference line starts with a colon, skip the colon. if (peekToken(fAlign) == Symbols.TokenCOLON) { fAlign= fScanner.findNonWhitespaceForwardInAnyPartition(fAlign + 1, bound); } } } catch (BadLocationException e) { // Ignore and return just the position } return startPosition; } switch (fToken) { // scopes: skip them case Symbols.TokenRPAREN: continuationLineCandidate = true; //$FALL-THROUGH$ 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, false); case Symbols.TokenSEMICOLON: return fPosition; case Symbols.TokenQUESTIONMARK: if (fPrefs.prefTernaryDeepAlign) { setFirstElementAlignment(fPosition - 1, fPosition + 1); } else { fIndent= fPrefs.prefTernaryIndent; } return fPosition; case Symbols.TokenEQUAL: case Symbols.TokenSHIFTLEFT: continuationLineCandidate = true; break; case Symbols.TokenRETURN: case Symbols.TokenUSING: fIndent = fPrefs.prefContinuationIndent; return fPosition; case Symbols.TokenTYPEDEF: switch (previous) { case Symbols.TokenSTRUCT: case Symbols.TokenUNION: case Symbols.TokenCLASS: case Symbols.TokenENUM: break; default: fIndent = fPrefs.prefContinuationIndent; } return fPosition; case Symbols.TokenEOF: if (continuationLineCandidate) { fIndent = fPrefs.prefContinuationIndent; } 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.prefHasTemplates) return false; int storedPosition= fPosition; int storedToken= fToken; nextToken(); switch (fToken) { case Symbols.TokenIDENT: fPosition = storedPosition; if (skipScope(Symbols.TokenLESSTHAN, Symbols.TokenGREATERTHAN)) return true; break; case Symbols.TokenQUESTIONMARK: case Symbols.TokenGREATERTHAN: fPosition = storedPosition; if (skipScope(Symbols.TokenLESSTHAN, Symbols.TokenGREATERTHAN)) return true; break; } // <> are harder to detect - restore the position if we fail fPosition= storedPosition; fToken= storedToken; return false; default: // programming error Assert.isTrue(false); return false; } } /** * Returns the contents of the current token. * * @return the contents of the current token */ 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 * 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. * @param firstToken <code>true</code> if we are dealing with the first token after * the opening parenthesis. * @return the indent */ private int handleScopeIntroduction(int bound, boolean firstToken) { int pos= fPosition; // store switch (fToken) { // scope introduction: special treat who special is case Symbols.TokenLPAREN: // special: method declaration deep indentation if (looksLikeMethodDecl()) { if (firstToken ? fPrefs.prefMethodDeclFirstParameterDeepIndent : fPrefs.prefMethodDeclDeepIndent) { return setFirstElementAlignment(pos, bound); } else { fIndent= fPrefs.prefMethodDeclIndent; return pos; } } else { fPosition= pos; if (looksLikeMethodCall()) { if (firstToken ? fPrefs.prefMethodCallFirstParameterDeepIndent : 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: final boolean looksLikeArrayInitializerIntro= looksLikeArrayInitializerIntro(); // special: array initializer if (looksLikeArrayInitializerIntro) { if (fPrefs.prefArrayDeepIndent) return setFirstElementAlignment(pos, bound); else fIndent= fPrefs.prefArrayIndent; } else if (isNamespace() || isLinkageSpec()) { fIndent= fPrefs.prefNamespaceBodyIndent; } else if (looksLikeEnumDeclaration()) { fIndent = fPrefs.prefTypeIndent; } else { int typeDeclPos = matchTypeDeclaration(); if (typeDeclPos == CHeuristicScanner.NOT_FOUND) { fIndent= fPrefs.prefBlockIndent; } else { fIndent= fPrefs.prefAccessSpecifierIndent + fPrefs.prefTypeIndent; } } // 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) { fPosition= pos; // restore return skipToStatementStart(true, true); // set to true to match the first if } else { return pos; } case Symbols.TokenLBRACKET: // 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: // programming error 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 C 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 == CHeuristicScanner.NOT_FOUND) { fAlign= firstPossible; } else { try { IRegion lineRegion = fDocument.getLineInformationOfOffset(scopeIntroducerOffset); if (fAlign > lineRegion.getOffset() + lineRegion.getLength()) { fAlign= firstPossible; } } catch (BadLocationException e) { // Ignore. } } return fAlign; } /** * Returns <code>true</code> if the next token received after calling * <code>nextToken</code> is either an equal sign, an opening brace, * a comma or an array designator ('[]'). * * @return <code>true</code> if the next elements look like the start of an array definition */ private boolean looksLikeArrayInitializerIntro() { int pos= fPosition; nextToken(); switch (fToken) { case Symbols.TokenEQUAL: return true; case Symbols.TokenRBRACKET: return skipBrackets(); case Symbols.TokenLBRACE: if (looksLikeArrayInitializerIntro()) { fPosition= pos; return true; } return false; case Symbols.TokenCOMMA: fPosition= pos; return true; } fPosition= pos; return false; } /** * Returns <code>true</code> if the the current token is "namespace", or the current token * is an identifier and the previous token is "namespace". * * @return <code>true</code> if the next elements look like the start of a namespace declaration. */ private boolean isNamespace() { int pos = fPosition; nextToken(); if (fToken == Symbols.TokenNAMESPACE) { fPosition = pos; return true; // Anonymous namespace } else if (fToken == Symbols.TokenIDENT) { nextToken(); // Get previous token if (fToken == Symbols.TokenNAMESPACE) { fPosition = pos; return true; // Named namespace } } fPosition = pos; return false; } /** * Returns <code>true</code> if the current token is keyword "extern". * * @return <code>true</code> if the next elements look like the start of a linkage spec. */ private boolean isLinkageSpec() { int pos = fPosition; nextToken(); if (fToken == Symbols.TokenEXTERN) { fPosition = pos; return true; } fPosition = pos; 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: 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 skipToStatementStart(false, false); return fToken == Symbols.TokenDO; case Symbols.TokenSEMICOLON: skipToStatementStart(false, false); return fToken == Symbols.TokenDO; } return false; } /** * Skips pointer operators if the current token is a pointer operator. * * @return <code>true</code> if a <code>*</code> or <code>&</code> could be scanned, the * current token is left at the operator. */ private boolean skipPointerOperators() { if (fToken == Symbols.TokenOTHER) { CharSequence token= getTokenContent().toString().trim(); if (token.length() == 1 && token.charAt(0) == '*' || token.charAt(0) == '&') { return true; } } else if (fToken == Symbols.TokenCONST) { return true; } 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; } /** * Skips scope qualifiers of identifiers. * * @return <code>true</code> if a qualifier was encountered, the last token * will be an IDENT. */ private boolean skipQualifiers() { if (fToken == Symbols.TokenDOUBLECOLON) { nextToken(); if (fToken == Symbols.TokenIDENT) { 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, CHeuristicScanner.UNBOUND); fPreviousPos= start; fPosition= fScanner.getPosition() + 1; try { fLine= fDocument.getLineOfOffset(fPosition); } catch (BadLocationException e) { fLine= -1; } } /** * Reads the next token in backward direction from the heuristic scanner * and returns that token without changing the current position. */ private int peekToken() { return fScanner.previousToken(fPosition - 1, CHeuristicScanner.UNBOUND); } /** * 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 an optional return type declaration. * * @return <code>true</code> if the current position looks like a method * declaration header. */ private boolean looksLikeMethodDecl() { nextToken(); switch (fToken) { case Symbols.TokenIDENT: // method name int pos= fPosition; nextToken(); // check destructor tilde if (fToken == Symbols.TokenTILDE) { return true; } if (skipQualifiers()) { return true; } // optional brackets for array valued return types while (skipBrackets()) { nextToken(); } while (skipPointerOperators()) { nextToken(); } switch (fToken) { case Symbols.TokenIDENT: return true; case Symbols.TokenSEMICOLON: case Symbols.TokenRBRACE: fPosition= pos; return true; case Symbols.TokenLBRACE: if (fScanner.looksLikeCompositeTypeDefinitionBackward(fPosition, CHeuristicScanner.UNBOUND)) { fPosition= pos; return true; } break; case Symbols.TokenCOMMA: nextToken(); if (fToken == Symbols.TokenRPAREN) { // field initializer if (skipScope()) { return looksLikeMethodDecl(); } } break; case Symbols.TokenCOLON: nextToken(); switch (fToken) { case Symbols.TokenPUBLIC: case Symbols.TokenPROTECTED: case Symbols.TokenPRIVATE: fPosition= pos; return true; case Symbols.TokenRPAREN: // constructor initializer if (skipScope()) { pos = fPosition; nextToken(); // optional throw if (fToken == Symbols.TokenTHROW) { nextToken(); if (fToken != Symbols.TokenRPAREN || !skipScope()) { return false; } } else { fPosition = pos; } return looksLikeMethodDecl(); } break; } } break; case Symbols.TokenARROW: case Symbols.TokenCOMMA: case Symbols.TokenEQUAL: case Symbols.TokenGREATERTHAN: case Symbols.TokenLESSTHAN: case Symbols.TokenMINUS: case Symbols.TokenPLUS: case Symbols.TokenSHIFTRIGHT: case Symbols.TokenSHIFTLEFT: case Symbols.TokenDELETE: case Symbols.TokenNEW: nextToken(); return fToken == Symbols.TokenOPERATOR; case Symbols.TokenRPAREN: nextToken(); if (fToken != Symbols.TokenLPAREN) return false; nextToken(); return fToken == Symbols.TokenOPERATOR; case Symbols.TokenRBRACKET: nextToken(); if (fToken != Symbols.TokenLBRACKET) return false; nextToken(); if (fToken == Symbols.TokenNEW || fToken == Symbols.TokenDELETE) nextToken(); return fToken == Symbols.TokenOPERATOR; case Symbols.TokenOTHER: if (getTokenContent().length() == 1) { nextToken(); if (fToken == Symbols.TokenOPERATOR) return true; } if (getTokenContent().length() == 1) { nextToken(); if (fToken == Symbols.TokenOPERATOR) return true; } } 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) // qualifying name return false; nextToken(); } return fToken == Symbols.TokenNEW; } return false; } /** * Returns <code>true</code> if the current tokens look like beginning of a method * call (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() { nextToken(); if (fToken == Symbols.TokenGREATERTHAN) { if (!skipScope()) return false; 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; } } } }