/******************************************************************************* * Copyright (c) 2014, 2015 Cisco Systems, Inc. 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 com.cisco.yangide.editor.editors.text; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; //import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil; import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; import com.cisco.yangide.editor.YangEditorPlugin; import com.cisco.yangide.ui.YangUIPlugin; /** * Uses the {@link org.eclipse.jdt.internal.ui.text.YangHeuristicScanner} to get the indentation * level for a certain position in a document. * * @author Alexey Kholupko */ public final class YangIndenter { /** * 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 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; CorePrefs() { prefUseTabs = YangEditorPlugin.getDefault().getCombinedPreferenceStore() .getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS); prefTabSize = prefTabSize();// 2;// 4; prefIndentationSize = prefTabSize();// 2;// 4; prefArrayDimensionsDeepIndent = true; prefContinuationIndent = prefTabSize();// 2; prefBlockIndent = 1; prefArrayIndent = prefContinuationIndent; prefArrayDeepIndent = true; prefTernaryDeepAlign = false; prefTernaryIndent = prefContinuationIndent; 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 = getPrefTabChar(); // YangCore.TAB; } private String getPrefTabChar() { if (YangEditorPlugin.getDefault().getCombinedPreferenceStore() .getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS)) { return YangUIPlugin.SPACE; } return YangUIPlugin.TAB; } private int prefTabSize() { return YangEditorPlugin.getDefault().getCombinedPreferenceStore() .getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH); } } /** 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 YangHeuristicScanner fScanner; /** * Core preferences. */ private final CorePrefs fPrefs; /** * Creates a new instance. * * @param document the document to scan * @param scanner the {@link YangHeuristicScanner} to be used for scanning the document. Must be * installed on the same <code>IDocument</code>. */ public YangIndenter(IDocument document, YangHeuristicScanner scanner) { Assert.isNotNull(document); Assert.isNotNull(scanner); fDocument = document; fScanner = scanner; fPrefs = new CorePrefs(); } /** * Computes the indentation at the reference point of <code>position</code>. */ public StringBuffer getReferenceIndentation(int offset) { return getReferenceIndentation(offset, false); } 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 == YangHeuristicScanner.NOT_FOUND) { return null; } return getLeadingWhitespace(unit); } /** * Computes the indentation at <code>offset</code>. */ public StringBuffer computeIndentation(int offset) { return computeIndentation(offset, false); } public StringBuffer computeIndentation(int offset, boolean assumeOpeningBrace) { StringBuffer reference = getReferenceIndentation(offset, assumeOpeningBrace); // handle special alignment if (fAlign != YangHeuristicScanner.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); } /** * 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. */ 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>. */ 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. */ private StringBuffer getLeadingWhitespace(int offset) { StringBuffer indent = new StringBuffer(); try { IRegion line = fDocument.getLineInformationOfOffset(offset); int lineOffset = line.getOffset(); int nonWS = fScanner.findNonWhitespaceForwardInAnyPartition(lineOffset, lineOffset + line.getLength()); indent.append(fDocument.get(lineOffset, nonWS - lineOffset)); return indent; } catch (BadLocationException e) { return indent; } } /** * 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> */ 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>additional</code> indentation units. * * @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); final String MIXED = "mixed"; //$NON-NLS-1$ // add additional indent int missing = totalLength - maxCopyLength; final int tabs, spaces; if (YangUIPlugin.SPACE.equals(fPrefs.prefTabChar)) { tabs = 0; spaces = missing; } else if (YangUIPlugin.TAB.equals(fPrefs.prefTabChar)) { tabs = tabSize > 0 ? missing / tabSize : 0; spaces = tabSize > 0 ? missing % tabSize : missing; } else if (MIXED.equals(fPrefs.prefTabChar)) { tabs = tabSize > 0 ? missing / tabSize : 0; spaces = tabSize > 0 ? missing % tabSize : missing; } else { Assert.isTrue(false); return null; } 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>. */ 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>. */ 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>. */ 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; boolean throwsClause = 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 // 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, YangHeuristicScanner.UNBOUND); boolean bracelessBlockStart = fScanner.isBracelessBlockStart(prevPos, YangHeuristicScanner.UNBOUND); switch (nextToken) { case Symbols.TokenLBRACE: // for opening-brace-on-new-line style if (bracelessBlockStart && !fPrefs.prefIndentBracesForBlocks) { unindent = true; } else if ((prevToken == Symbols.TokenCOLON || prevToken == Symbols.TokenEQUAL) && !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; case Symbols.TokenPLUS: if (isStringContinuation(offset)) { if (isSecondLineOfStringContinuation(offset)) { fAlign = YangHeuristicScanner.NOT_FOUND; fIndent = fPrefs.prefContinuationIndent; } else { int previousLineOffset = fDocument.getLineOffset(fDocument.getLineOfOffset(offset) - 1); fAlign = fScanner.findNonWhitespaceForwardInAnyPartition(previousLineOffset, YangHeuristicScanner.UNBOUND); } return fPosition; } 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, throwsClause); if (unindent) { fIndent--; } if (indent) { fIndent++; } return ref; } /** * Tells whether the given string is a continuation expression. */ private boolean isStringContinuation(int offset) { int nextNonWSCharPosition = fScanner.findNonWhitespaceBackwardInAnyPartition(offset - 1, YangHeuristicScanner.UNBOUND); try { if (fDocument.getChar(nextNonWSCharPosition) == '"') { return true; } else { return false; } } catch (BadLocationException e) { YangEditorPlugin.log(e); return false; } } /** * Checks if extra indentation for second line of string continuation is required. */ private boolean isSecondLineOfStringContinuation(int offset) { try { int offsetLine = fDocument.getLineOfOffset(offset); fPosition = offset; while (true) { nextToken(); switch (fToken) { // scopes: skip them case Symbols.TokenRPAREN: case Symbols.TokenRBRACKET: case Symbols.TokenRBRACE: case Symbols.TokenGREATERTHAN: skipScope(); break; case Symbols.TokenPLUS: if ((offsetLine - fLine) > 1) { return false; } break; case Symbols.TokenCOMMA: case Symbols.TokenLPAREN: case Symbols.TokenLBRACE: case Symbols.TokenEQUAL: int stringStartingOffset = fScanner.findNonWhitespaceForwardInAnyPartition(fPosition + 1, YangHeuristicScanner.UNBOUND); int stringStartingLine = fDocument.getLineOfOffset(stringStartingOffset); if ((offsetLine - stringStartingLine) == 1) { fPosition = stringStartingOffset; return true; } else { return false; } case Symbols.TokenLBRACKET: case Symbols.TokenEOF: if ((offsetLine - fLine) == 1) { return true; } else { return false; } } } } catch (BadLocationException e) { YangEditorPlugin.log(e); return false; } } public int findReferencePosition(int offset, boolean danglingElse, boolean matchBrace, boolean matchParen, boolean matchCase) { return findReferencePosition(offset, danglingElse, matchBrace, matchParen, matchCase, false); } public int findReferencePosition(int offset, boolean danglingElse, boolean matchBrace, boolean matchParen, boolean matchCase, boolean throwsClause) { fIndent = 0; // the indentation modification fAlign = YangHeuristicScanner.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, throwsClause); fIndent--; return pos; } } // align parenthesis' if (matchParen) { if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN)) { fIndent = fPrefs.prefContinuationIndent; 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, throwsClause); fIndent--; return pos; } } 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; } 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 pos = fPosition; 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 YangHeuristicScanner.NOT_FOUND; case Symbols.TokenEQUAL: // indent assignments return handleEqual(); 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.TokenKEYWORD: fIndent = fPrefs.prefSimpleIndent; return fPosition; case Symbols.TokenRBRACKET: fIndent = fPrefs.prefContinuationIndent; return fPosition; case Symbols.TokenRPAREN: if (throwsClause) { fIndent = fPrefs.prefContinuationIndent; return fPosition; } int line = fLine; if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN)) { int scope = fPosition; nextToken(); fPosition = scope; if (looksLikeMethodDecl()) { return skipToStatementStart(danglingElse, false); } fPosition = scope; if (looksLikeAnnotation()) { return skipToStatementStart(danglingElse, false); } } // restore fPosition = offset; fLine = line; return skipToPreviousListItemOrListStart(); case Symbols.TokenPLUS: if (isStringContinuation(fPosition)) { try { if (isSecondLineOfStringContinuation(offset)) { fAlign = YangHeuristicScanner.NOT_FOUND; fIndent = fPrefs.prefContinuationIndent; } else { int previousLineOffset = fDocument.getLineOffset(fDocument.getLineOfOffset(offset) - 1); fAlign = fScanner.findNonWhitespaceForwardInAnyPartition(previousLineOffset, YangHeuristicScanner.UNBOUND); } } catch (BadLocationException e) { YangEditorPlugin.log(e); } return fPosition; } fPosition = offset; 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 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(); } } /** * Checks if the statement at position is itself a continuation of the previous, else sets the * indentation to Continuation Indent. * * @return the position of the token */ private int handleEqual() { try { // If this line is itself continuation of the previous then do nothing IRegion line = fDocument.getLineInformationOfOffset(fPosition); int nonWS = fScanner.findNonWhitespaceBackward(line.getOffset(), YangHeuristicScanner.UNBOUND); if (nonWS != Symbols.TokenEOF) { int tokenAtPreviousLine = fScanner.nextToken(nonWS, nonWS + 1); if (tokenAtPreviousLine != Symbols.TokenSEMICOLON && tokenAtPreviousLine != Symbols.TokenRBRACE && tokenAtPreviousLine != Symbols.TokenLBRACE && tokenAtPreviousLine != Symbols.TokenEOF) { return fPosition; } } } catch (BadLocationException e) { return fPosition; } fIndent = fPrefs.prefContinuationIndent; return fPosition; } /** * 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; while (true) { nextToken(); if (isInBlock) { switch (fToken) { // exit on all block introducers case Symbols.TokenKEYWORD: 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: // XXX custom case, when comments appear above new block case Symbols.TokenOTHER: 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; } //$FALL-THROUGH$ case Symbols.TokenRBRACKET: case Symbols.TokenGREATERTHAN: pos = fPreviousPos; if (skipScope()) { break; } else { return pos; } case Symbols.TokenKEYWORD: // align the WHILE position with its do return fPosition; 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() { while (true) { nextToken(); switch (fToken) { // search for case labels, which consist of (possibly qualified) identifiers or numbers case Symbols.TokenIDENT: case Symbols.TokenOTHER: // dots for qualified constants continue; default: return true; } } } /** * Returns the reference position for a list element: 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) { // TODO // 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.TokenEQUAL: return handleEqual(); 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; nextToken(); switch (fToken) { case Symbols.TokenIDENT: //$FALL-THROUGH$ 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; return false; default: Assert.isTrue(false); return false; } } /** * 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 YANG token after that. */ 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 YANG 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 == YangHeuristicScanner.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 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, YangHeuristicScanner.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 annotation (i.e. an annotation * name (potentially qualified) preceded by an at-sign). * * @return <code>true</code> if the current position looks like an annotation. */ private boolean looksLikeAnnotation() { nextToken(); if (fToken == Symbols.TokenIDENT) { // Annotation name nextToken(); while (fToken == Symbols.TokenOTHER) { // dot of qualification nextToken(); if (fToken != Symbols.TokenIDENT) { return false; } nextToken(); } return fToken == Symbols.TokenAT; } 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() { 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; } } } }