/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.ui.internal.text.dart;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.Block;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.DoStatement;
import com.google.dart.engine.ast.Expression;
import com.google.dart.engine.ast.ForStatement;
import com.google.dart.engine.ast.IfStatement;
import com.google.dart.engine.ast.Statement;
import com.google.dart.engine.ast.WhileStatement;
import com.google.dart.engine.ast.visitor.NodeLocator;
import com.google.dart.engine.ast.visitor.SimpleAstVisitor;
import com.google.dart.engine.context.AnalysisException;
import com.google.dart.engine.error.AnalysisErrorListener;
import com.google.dart.engine.internal.context.RecordingErrorListener;
import com.google.dart.engine.parser.Parser;
import com.google.dart.engine.scanner.CharSequenceReader;
import com.google.dart.engine.scanner.Keyword;
import com.google.dart.engine.scanner.KeywordToken;
import com.google.dart.engine.scanner.Scanner;
import com.google.dart.engine.scanner.Token;
import com.google.dart.engine.scanner.TokenType;
import com.google.dart.tools.ui.DartToolsPlugin;
import com.google.dart.tools.ui.DartX;
import com.google.dart.tools.ui.PreferenceConstants;
import com.google.dart.tools.ui.internal.text.functions.DartHeuristicScanner;
import com.google.dart.tools.ui.internal.text.functions.DartIndenter;
import com.google.dart.tools.ui.internal.text.functions.FastDartPartitionScanner;
import com.google.dart.tools.ui.internal.text.functions.Symbols;
import com.google.dart.tools.ui.internal.util.CodeFormatterUtil;
import com.google.dart.tools.ui.text.DartPartitions;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.rules.FastPartitioner;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.texteditor.ITextEditorExtension3;
/**
* Auto indent strategy sensitive to brackets.
*
* @coverage dart.editor.ui.text.dart
*/
public class DartAutoIndentStrategy_OLD extends DefaultIndentLineAutoEditStrategy {
private static class CompilationUnitInfo {
char[] buffer;
int delta;
CompilationUnitInfo(char[] buffer, int delta) {
this.buffer = buffer;
this.delta = delta;
}
}
private class NodeBracketer extends SimpleAstVisitor<Void> {
private boolean result = true;
private CompilationUnitInfo info;
private IDocument document;
private int offset, length;
NodeBracketer(IDocument document, CompilationUnitInfo info, int offset, int length) {
this.document = document;
this.info = info;
this.offset = offset;
this.length = length;
}
@Override
public Void visitBlock(Block node) {
result = getBlockBalance(document, offset, fPartitioning) <= 0;
return null;
}
@Override
public Void visitDoStatement(DoStatement doStatement) {
IRegion doRegion = createRegion(doStatement, info.delta);
Statement body = doStatement.getBody();
IRegion bodyRegion = createRegion(body, info.delta);
if (doRegion.getOffset() + doRegion.getLength() <= offset
&& offset + length <= bodyRegion.getOffset()) {
result = body != null;
}
return null;
}
@Override
public Void visitForStatement(ForStatement node) {
Expression expression = node.getCondition();
IRegion expressionRegion = createRegion(expression, info.delta);
Statement body = node.getBody();
IRegion bodyRegion = createRegion(body, info.delta);
// between expression and body statement
if (expressionRegion.getOffset() + expressionRegion.getLength() <= offset
&& offset + length <= bodyRegion.getOffset()) {
result = body != null;
}
return null;
}
@Override
public Void visitIfStatement(IfStatement ifStatement) {
Expression expression = ifStatement.getCondition();
IRegion expressionRegion = createRegion(expression, info.delta);
Statement thenStatement = ifStatement.getThenStatement();
IRegion thenRegion = createRegion(thenStatement, info.delta);
// between expression and then statement
if (expressionRegion.getOffset() + expressionRegion.getLength() <= offset
&& offset + length <= thenRegion.getOffset()) {
result = thenStatement != null;
return null;
}
Statement elseStatement = ifStatement.getElseStatement();
IRegion elseRegion = createRegion(elseStatement, info.delta);
if (elseStatement != null) {
int sourceOffset = thenRegion.getOffset() + thenRegion.getLength();
int sourceLength = elseRegion.getOffset() - sourceOffset;
IRegion elseToken = getToken(document, new Region(sourceOffset, sourceLength), Keyword.ELSE);
result = elseToken != null && elseToken.getOffset() + elseToken.getLength() <= offset
&& offset + length < elseRegion.getOffset();
}
return null;
}
@Override
public Void visitWhileStatement(WhileStatement node) {
Expression expression = node.getCondition();
IRegion expressionRegion = createRegion(expression, info.delta);
Statement body = node.getBody();
IRegion bodyRegion = createRegion(body, info.delta);
// between expression and body statement
if (expressionRegion.getOffset() + expressionRegion.getLength() <= offset
&& offset + length <= bodyRegion.getOffset()) {
result = body != null;
}
return null;
}
boolean getResult() {
return result;
}
}
/** The line comment introducer. Value is "{@value} " */
private static final String LINE_COMMENT = "//"; //$NON-NLS-1$
// /**
// * Computes an insert position for an opening brace if <code>offset</code> maps to a position in
// * <code>document</code> with a expression in parenthesis that will take a block after the closing
// * parenthesis.
// *
// * @param document the document being modified
// * @param offset the offset of the caret position, relative to the line start.
// * @param partitioning the document partitioning
// * @param max the max position
// * @return an insert position relative to the line start if <code>line</code> contains a
// * parenthesized expression that can be followed by a block, -1 otherwise
// */
// private static int computeAnonymousPosition(IDocument document, int offset, String partitioning,
// int max) {
// if (true) {
// return -1;
// }
// // find the opening parenthesis for every closing parenthesis on the current
// // line after offset
// // return the position behind the closing parenthesis if it looks like a
// // method declaration
// // or an expression for an if, while, for, catch statement
// DartHeuristicScanner scanner = new DartHeuristicScanner(document);
// int pos = offset;
// int length = max;
// int scanTo = scanner.scanForward(pos, length, '}');
// if (scanTo == -1) {
// scanTo = length;
// }
//
// int closingParen = findClosingParenToLeft(scanner, pos) - 1; // loc before rparen
// boolean hasNewToken = looksLikeAnonymousClassDef(document, partitioning, scanner, pos);
// int openingParen = -1;
// while (true) {
// int startScan = closingParen + 1;
// closingParen = scanner.scanForward(startScan, scanTo, ')');
// if (closingParen == -1) {
// if (hasNewToken && openingParen != -1) {
// return openingParen + 1;
// }
// break;
// }
//
// openingParen = scanner.findOpeningPeer(closingParen - 1, '(', ')');
//
// // no way an expression at the beginning of the document can mean anything
// if (openingParen < 1) {
// break;
// }
//
// // only select insert positions for parenthesis currently embracing the
// // caret
// if (openingParen > pos) {
// continue;
// }
//
// if (looksLikeAnonymousClassDef(document, partitioning, scanner, openingParen - 1)) {
// return closingParen + 1;
// }
// if (looksLikeArgument(scanner, openingParen - 1, max)) {
// return closingParen + 1;
// }
// }
//
// return -1;
// }
private static IRegion createRegion(AstNode node, int delta) {
return node == null ? null : new Region(node.getOffset() + delta, node.getLength());
}
// /**
// * Finds a closing parenthesis to the left of <code>position</code> in document, where that
// * parenthesis is only separated by whitespace from <code>position</code>. If no such parenthesis
// * can be found, <code>position</code> is returned.
// *
// * @param scanner the heuristic scanner set up on the document
// * @param position the first character position in <code>document</code> to be considered
// * @return the position of a closing parenthesis left to <code>position</code> separated only by
// * whitespace, or <code>position</code> if no parenthesis can be found
// */
// private static int findClosingParenToLeft(DartHeuristicScanner scanner, int position) {
// if (position < 1) {
// return position;
// }
//
// if (scanner.previousToken(position - 1, DartHeuristicScanner.UNBOUND) == Symbols.TokenRPAREN) {
// return scanner.getPosition() + 1;
// }
// return position;
// }
/**
* Returns the block balance, i.e. zero if the blocks are balanced at <code>offset</code>, a
* negative number if there are more closing than opening braces, and a positive number if there
* are more opening than closing braces.
*
* @param document the document
* @param offset the offset
* @param partitioning the partitioning
* @return the block balance
*/
private static int getBlockBalance(IDocument document, int offset, String partitioning) {
if (offset < 1) {
return -1;
}
if (offset >= document.getLength()) {
return 1;
}
int begin = offset;
int end = offset - 1;
DartHeuristicScanner scanner = new DartHeuristicScanner(document);
while (true) {
begin = scanner.findOpeningPeer(begin - 1, '{', '}');
end = scanner.findClosingPeer(end + 1, '{', '}');
if (begin == -1 && end == -1) {
return 0;
}
if (begin == -1) {
return -1;
}
if (end == -1) {
return 1;
}
}
}
private static CompilationUnitInfo getCompilationUnitForMethod(IDocument document, int offset) {
try {
DartHeuristicScanner scanner = new DartHeuristicScanner(document);
IRegion sourceRange = scanner.findSurroundingBlock(offset);
if (sourceRange == null) {
return null;
}
String source = document.get(sourceRange.getOffset(), sourceRange.getLength());
StringBuffer contents = new StringBuffer();
contents.append("class ____x { ");
contents.append("____f() { "); //$NON-NLS-1$
final int methodOffset = contents.length();
contents.append(source);
contents.append('}');
contents.append('}');
char[] buffer = contents.toString().toCharArray();
return new CompilationUnitInfo(buffer, sourceRange.getOffset() - methodOffset);
} catch (BadLocationException e) {
DartToolsPlugin.log(e);
}
return null;
}
/**
* Returns the indentation of the line <code>line</code> in <code>document</code>. The returned
* string may contain pairs of leading slashes that are considered part of the indentation. The
* space before the asterisk in a Dart doc comment is not considered part of the indentation.
*
* @param document the document
* @param line the line
* @return the indentation of <code>line</code> in <code>document</code>
* @throws BadLocationException if the document is changed concurrently
*/
private static String getCurrentIndent(Document document, int line) throws BadLocationException {
IRegion region = document.getLineInformation(line);
int from = region.getOffset();
int endOffset = region.getOffset() + region.getLength();
// go behind line comments
int to = from;
while (to < endOffset - 2 && document.get(to, 2).equals(LINE_COMMENT)) {
to += 2;
}
while (to < endOffset) {
char ch = document.getChar(to);
if (!Character.isWhitespace(ch)) {
break;
}
to++;
}
// don't count the space before Dart doc, asterisk-style comment lines
if (to > from && to < endOffset - 1 && document.get(to - 1, 2).equals(" *")) { //$NON-NLS-1$
String type = TextUtilities.getContentType(
document,
DartPartitions.DART_PARTITIONING,
to,
true);
if (type.equals(DartPartitions.DART_DOC)
|| type.equals(DartPartitions.DART_MULTI_LINE_COMMENT)) {
to--;
}
}
return document.get(from, to - from);
}
private static IPreferenceStore getPreferenceStore() {
return DartToolsPlugin.getDefault().getCombinedPreferenceStore();
}
private static IRegion getToken(IDocument document, IRegion scanRegion, Keyword keyword) {
try {
String source = document.get(scanRegion.getOffset(), scanRegion.getLength());
Token token = internalScan(source, AnalysisErrorListener.NULL_LISTENER);
while (token != null && token.getType() != TokenType.EOF) {
if (token instanceof KeywordToken) {
if (((KeywordToken) token).getKeyword() == keyword) {
break;
}
}
token = token.getNext();
}
if (token.getType() == TokenType.EOF) {
return null;
}
int tokenOffset = token.getOffset();
int tokenLength = token.getEnd() - tokenOffset;
return new Region(tokenOffset + scanRegion.getOffset(), tokenLength);
} catch (Throwable x) {
return null;
}
}
private static String getWhitespaceRight(IDocument document, int index)
throws BadLocationException {
StringBuffer result = new StringBuffer();
while (true) {
char c = document.getChar(index++);
if (!Character.isWhitespace(c)) {
break;
}
result.append(c);
}
return result.toString();
}
/**
* Installs a Dart partitioner with <code>document</code>.
*
* @param document the document
*/
private static void installDartStuff(Document document) {
String[] types = new String[] {
DartPartitions.DART_DOC, DartPartitions.DART_MULTI_LINE_COMMENT,
DartPartitions.DART_SINGLE_LINE_COMMENT, DartPartitions.DART_SINGLE_LINE_DOC,
DartPartitions.DART_STRING, DartPartitions.DART_MULTI_LINE_STRING,
IDocument.DEFAULT_CONTENT_TYPE};
FastPartitioner partitioner = new FastPartitioner(new FastDartPartitionScanner(), types);
partitioner.connect(document);
document.setDocumentPartitioner(DartPartitions.DART_PARTITIONING, partitioner);
}
private static CompilationUnit internalParseCompilationUnit(String contents)
throws AnalysisException {
RecordingErrorListener errorListener = new RecordingErrorListener();
Token token = internalScan(contents, errorListener);
Parser parser = new Parser(null, errorListener);
CompilationUnit unit = parser.parseCompilationUnit(token);
// AnalysisError[] errors = errorListener.getErrors(null);
// unit.setParsingErrors(errors);
return unit;
}
private static Token internalScan(String contents, AnalysisErrorListener errorListener)
throws AnalysisException {
Scanner scanner = new Scanner(null, new CharSequenceReader(contents), errorListener);
return scanner.tokenize();
}
// /**
// * Checks whether <code>position</code> resides in a default (Dart) partition of
// * <code>document</code>.
// *
// * @param document the document being modified
// * @param position the position to be checked
// * @param partitioning the document partitioning
// * @return <code>true</code> if <code>position</code> is in the default partition of
// * <code>document</code>, <code>false</code> otherwise
// */
// private static boolean isDefaultPartition(IDocument document, int position, String partitioning) {
// Assert.isTrue(position >= 0);
// Assert.isTrue(position <= document.getLength());
//
// try {
// ITypedRegion region = TextUtilities.getPartition(document, partitioning, position, false);
// return region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE);
//
// } catch (BadLocationException e) {
// }
//
// return false;
// }
// /**
// * Checks whether the content of <code>document</code> in the range ( <code>offset</code>,
// * <code>length</code>) contains the <code>new</code> keyword.
// *
// * @param document the document being modified
// * @param offset the first character position in <code>document</code> to be considered
// * @param length the length of the character range to be considered
// * @param partitioning the document partitioning
// * @return <code>true</code> if the specified character range contains a <code>new</code> keyword,
// * <code>false</code> otherwise.
// */
// private static boolean isNewMatch(IDocument document, int offset, int length, String partitioning) {
// Assert.isTrue(length >= 0);
// Assert.isTrue(offset >= 0);
// Assert.isTrue(offset + length < document.getLength() + 1);
//
// try {
// String text = document.get(offset, length);
// int pos = text.indexOf("new"); //$NON-NLS-1$
//
// while (pos != -1 && !isDefaultPartition(document, pos + offset, partitioning)) {
// pos = text.indexOf("new", pos + 2); //$NON-NLS-1$
// }
//
// if (pos < 0) {
// return false;
// }
//
// if (pos != 0 && Character.isJavaIdentifierPart(text.charAt(pos - 1))) {
// return false;
// }
//
// if (pos + 3 < length && Character.isJavaIdentifierPart(text.charAt(pos + 3))) {
// return false;
// }
//
// return true;
//
// } catch (BadLocationException e) {
// }
// return false;
// }
// /**
// * Checks whether the content of <code>document</code> at <code>position</code> looks like an
// * anonymous class definition. <code>position</code> must be to the left of the opening
// * parenthesis of the definition's parameter list.
// *
// * @param document the document being modified
// * @param partitioning the document partitioning
// * @param scanner the scanner
// * @param position the first character position in <code>document</code> to be considered
// * @return <code>true</code> if the content of <code>document</code> looks like an anonymous class
// * definition, <code>false</code> otherwise
// */
// private static boolean looksLikeAnonymousClassDef(IDocument document, String partitioning,
// DartHeuristicScanner scanner, int position) {
// int previousCommaParenEqual = scanner.scanBackward(
// position - 1,
// DartHeuristicScanner.UNBOUND,
// new char[] {',', '(', '='});
// if (previousCommaParenEqual == -1 || position < previousCommaParenEqual + 5) {
// // 2 for borders, 3 for "new"
// return false;
// }
//
// if (isNewMatch(
// document,
// previousCommaParenEqual + 1,
// position - previousCommaParenEqual - 2,
// partitioning)) {
// return true;
// }
//
// return false;
// }
// private static boolean looksLikeArgument(DartHeuristicScanner scanner, int position, int max) {
// int rpLoc = scanner.findOpeningPeer(position, '(', ')');
// int lpLoc = scanner.findClosingPeer(rpLoc + 1, max, '(', ')');
// return lpLoc != DartHeuristicScanner.NOT_FOUND;
// }
/**
* Installs a Dart partitioner with <code>document</code>.
*
* @param document the document
*/
private static void removeDartStuff(Document document) {
document.setDocumentPartitioner(DartPartitions.DART_PARTITIONING, null);
}
/**
* Skips the scope opened by <code>token</code>.
*
* @param scanner the scanner
* @param start the start position
* @param token the token
* @return the position after the scope or <code>DartHeuristicScanner.NOT_FOUND</code>
*/
private static int skipScope(DartHeuristicScanner scanner, int start, int token) {
int openToken = token;
int closeToken;
switch (token) {
case Symbols.TokenLPAREN:
closeToken = Symbols.TokenRPAREN;
break;
case Symbols.TokenLBRACKET:
closeToken = Symbols.TokenRBRACKET;
break;
case Symbols.TokenLBRACE:
closeToken = Symbols.TokenRBRACE;
break;
default:
Assert.isTrue(false);
return -1; // dummy
}
int depth = 1;
int p = start;
while (true) {
int tok = scanner.nextToken(p, DartHeuristicScanner.UNBOUND);
p = scanner.getPosition();
if (tok == openToken) {
depth++;
} else if (tok == closeToken) {
depth--;
if (depth == 0) {
return p + 1;
}
} else if (tok == Symbols.TokenEOF) {
return DartHeuristicScanner.NOT_FOUND;
}
}
}
private boolean fCloseBrace;
private boolean fIsSmartMode;
private boolean fIsSmartTab;
private String fPartitioning;
/**
* The viewer.
*/
private final ISourceViewer fViewer;
/**
* Creates a new Dart auto indent strategy for the given document partitioning.
*
* @param partitioning the document partitioning // * @param project the project to get formatting
* preferences from, or null to use default // * preferences
* @param viewer the source viewer that this strategy is attached to
*/
public DartAutoIndentStrategy_OLD(String partitioning, ISourceViewer viewer) {
fPartitioning = partitioning;
fViewer = viewer;
}
@Override
public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
try {
if (c.doit == false) {
return;
}
clearCachedValues();
if (!fIsSmartMode) {
super.customizeDocumentCommand(d, c);
return;
}
if (!fIsSmartTab && isRepresentingTab(c.text)) {
return;
}
if (c.length == 0 && c.text != null && isLineDelimiter(d, c.text)) {
smartIndentAfterNewLine(d, c);
} else if (c.text.length() == 1) {
smartIndentOnKeypress(d, c);
} else if (c.text.length() > 1
&& getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SMART_PASTE)) {
if (fViewer == null || fViewer.getTextWidget() == null
|| !fViewer.getTextWidget().getBlockSelection()) {
smartPaste(d, c); // no smart backspace for paste
}
}
} catch (IllegalArgumentException e) {
// ignore
}
}
protected boolean computeSmartMode() {
IWorkbenchPage page = DartToolsPlugin.getActivePage();
if (page != null) {
IEditorPart part = page.getActiveEditor();
if (part instanceof ITextEditorExtension3) {
ITextEditorExtension3 extension = (ITextEditorExtension3) part;
return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT;
}
}
return false;
}
/**
* Indents line <code>line</code> in <code>document</code> with <code>indent</code>. Leaves
* leading comment signs alone.
*
* @param document the document
* @param line the line
* @param indent the indentation to insert
* @param tabLength the length of a tab
* @throws BadLocationException on concurrent document modification
*/
private void addIndent(Document document, int line, CharSequence indent, int tabLength)
throws BadLocationException {
IRegion region = document.getLineInformation(line);
int insert = region.getOffset();
int endOffset = region.getOffset() + region.getLength();
// Compute insert after all leading line comment markers
int newInsert = insert;
while (newInsert < endOffset - 2 && document.get(newInsert, 2).equals(LINE_COMMENT)) {
newInsert += 2;
}
// Heuristic to check whether it is commented code or just a comment
if (newInsert > insert) {
int whitespaceCount = 0;
int i = newInsert;
while (i < endOffset - 1) {
char ch = document.get(i, 1).charAt(0);
if (!Character.isWhitespace(ch)) {
break;
}
whitespaceCount = whitespaceCount + computeVisualLength(ch, tabLength);
i++;
}
if (whitespaceCount != 0 && whitespaceCount >= CodeFormatterUtil.getIndentWidth(null)) {
insert = newInsert;
}
}
// Insert indent
document.replace(insert, 0, indent.toString());
}
private void clearCachedValues() {
IPreferenceStore preferenceStore = getPreferenceStore();
fCloseBrace = preferenceStore.getBoolean(PreferenceConstants.EDITOR_CLOSE_BRACES);
fIsSmartTab = preferenceStore.getBoolean(PreferenceConstants.EDITOR_SMART_TAB);
fIsSmartMode = computeSmartMode();
}
private boolean closeBrace() {
return fCloseBrace;
}
private String computeForcedCascadePrefix(DartIndenter indenter, IDocument document, int offset,
String newText) throws BadLocationException {
int currentLine = document.getLineOfOffset(offset);
if (currentLine > 0) {
IRegion prevLineRegion = document.getLineInformation(currentLine - 1);
String prevLine = document.get(prevLineRegion.getOffset(), prevLineRegion.getLength());
String prevLine2 = prevLine.trim();
String newText2 = newText.trim();
if (newText2.startsWith("..")) {
String prevIndent = getWhitespaceRight(document, prevLineRegion.getOffset());
if (prevLine2.startsWith("..")) {
return prevIndent;
}
return prevIndent + indenter.getCascadeIndent();
}
}
// don't force
return null;
}
/**
* Returns the visual length of a given character taking into account the visual tabulator length.
*
* @param ch the character to measure
* @param tabLength the length of a tab
* @return the visual length of <code>ch</code>
*/
private int computeVisualLength(char ch, int tabLength) {
if (ch == '\t') {
return tabLength;
} else {
return 1;
}
}
/**
* Returns the visual length of a given <code>CharSequence</code> taking into account the visual
* tabulator length.
*
* @param seq the string to measure
* @param tabLength the length of a tab
* @return the visual length of <code>seq</code>
*/
private int computeVisualLength(CharSequence seq, int tabLength) {
int size = 0;
if (seq != null) {
for (int i = 0; i < seq.length(); i++) {
char ch = seq.charAt(i);
if (ch == '\t') {
if (tabLength != 0) {
size += tabLength - size % tabLength;
// else: size stays the same
}
} else {
size++;
}
}
}
return size;
}
/**
* Cuts the visual equivalent of <code>toDelete</code> characters out of the indentation of line
* <code>line</code> in <code>document</code>. Leaves leading comment signs alone.
*
* @param document the document
* @param line the line
* @param toDelete the number of space equivalents to delete
* @param tabLength the length of a tab
* @throws BadLocationException on concurrent document modification
*/
private void cutIndent(Document document, int line, int toDelete, int tabLength)
throws BadLocationException {
IRegion region = document.getLineInformation(line);
int from = region.getOffset();
int endOffset = region.getOffset() + region.getLength();
// go behind line comments
while (from < endOffset - 2 && document.get(from, 2).equals(LINE_COMMENT)) {
from += 2;
}
int to = from;
while (toDelete > 0 && to < endOffset) {
char ch = document.getChar(to);
if (!Character.isWhitespace(ch)) {
break;
}
toDelete -= computeVisualLength(ch, tabLength);
if (toDelete >= 0) {
to++;
} else {
break;
}
}
document.replace(from, to - from, ""); //$NON-NLS-1$
}
private int getBracketCount(IDocument d, int startOffset, int endOffset,
boolean ignoreCloseBrackets) throws BadLocationException {
int bracketCount = 0;
while (startOffset < endOffset) {
char curr = d.getChar(startOffset);
startOffset++;
switch (curr) {
case '/':
if (startOffset < endOffset) {
char next = d.getChar(startOffset);
if (next == '*') {
// a comment starts, advance to the comment end
startOffset = getCommentEnd(d, startOffset + 1, endOffset);
} else if (next == '/') {
// '//'-comment: nothing to do anymore on this line
startOffset = endOffset;
}
}
break;
case '*':
if (startOffset < endOffset) {
char next = d.getChar(startOffset);
if (next == '/') {
// we have been in a comment: forget what we read before
bracketCount = 0;
startOffset++;
}
}
break;
case '{':
bracketCount++;
ignoreCloseBrackets = false;
break;
case '}':
if (!ignoreCloseBrackets) {
bracketCount--;
}
break;
case '"':
case '\'':
startOffset = getStringEnd(d, startOffset, endOffset, curr);
break;
default:
}
}
return bracketCount;
}
private int getCommentEnd(IDocument d, int offset, int endOffset) throws BadLocationException {
while (offset < endOffset) {
char curr = d.getChar(offset);
offset++;
if (curr == '*') {
if (offset < endOffset && d.getChar(offset) == '/') {
return offset + 1;
}
}
}
return endOffset;
}
private String getIndentOfLine(IDocument d, int line) throws BadLocationException {
if (line > -1) {
int start = d.getLineOffset(line);
int end = start + d.getLineLength(line) - 1;
int whiteEnd = findEndOfWhiteSpace(d, start, end);
return d.get(start, whiteEnd - start);
} else {
return ""; //$NON-NLS-1$
}
}
private int getPeerPosition(IDocument document, DocumentCommand command) {
if (document.getLength() == 0) {
return 0;
}
/*
* Search for scope closers in the pasted text and find their opening peers in the document.
*/
Document pasted = new Document(command.text);
installDartStuff(pasted);
int firstPeer = command.offset;
DartHeuristicScanner pScanner = new DartHeuristicScanner(pasted);
DartHeuristicScanner dScanner = new DartHeuristicScanner(document);
// add scope relevant after context to peer search
int afterToken = dScanner.nextToken(
command.offset + command.length,
DartHeuristicScanner.UNBOUND);
try {
switch (afterToken) {
case Symbols.TokenRBRACE:
pasted.replace(pasted.getLength(), 0, "}"); //$NON-NLS-1$
break;
case Symbols.TokenRPAREN:
pasted.replace(pasted.getLength(), 0, ")"); //$NON-NLS-1$
break;
case Symbols.TokenRBRACKET:
pasted.replace(pasted.getLength(), 0, "]"); //$NON-NLS-1$
break;
}
} catch (BadLocationException e) {
// cannot happen
Assert.isTrue(false);
}
int pPos = 0; // paste text position (increasing from 0)
int dPos = Math.max(0, command.offset - 1); // document position (decreasing
// from paste offset)
while (true) {
int token = pScanner.nextToken(pPos, DartHeuristicScanner.UNBOUND);
pPos = pScanner.getPosition();
switch (token) {
case Symbols.TokenLBRACE:
case Symbols.TokenLBRACKET:
case Symbols.TokenLPAREN:
pPos = skipScope(pScanner, pPos, token);
if (pPos == DartHeuristicScanner.NOT_FOUND) {
return firstPeer;
}
break; // closed scope -> keep searching
case Symbols.TokenRBRACE:
int peer = dScanner.findOpeningPeer(dPos, '{', '}');
dPos = peer - 1;
if (peer == DartHeuristicScanner.NOT_FOUND) {
return firstPeer;
}
firstPeer = peer;
break; // keep searching
case Symbols.TokenRBRACKET:
peer = dScanner.findOpeningPeer(dPos, '[', ']');
dPos = peer - 1;
if (peer == DartHeuristicScanner.NOT_FOUND) {
return firstPeer;
}
firstPeer = peer;
break; // keep searching
case Symbols.TokenRPAREN:
peer = dScanner.findOpeningPeer(dPos, '(', ')');
dPos = peer - 1;
if (peer == DartHeuristicScanner.NOT_FOUND) {
return firstPeer;
}
firstPeer = peer;
break; // keep searching
case Symbols.TokenCASE:
case Symbols.TokenDEFAULT:
DartIndenter indenter = new DartIndenter(document, dScanner, null);
peer = indenter.findReferencePosition(dPos, false, false, false, true);
if (peer == DartHeuristicScanner.NOT_FOUND) {
return firstPeer;
}
firstPeer = peer;
break; // keep searching
case Symbols.TokenEOF:
return firstPeer;
default:
// keep searching
}
}
}
private int getStringEnd(IDocument d, int offset, int endOffset, char ch)
throws BadLocationException {
// TODO this needs to be rewritten to use DartScanner
// and be sensitive to comments and strings in interpolation expressions
// and to raw strings
boolean multiline = false;
if (offset + 2 <= endOffset) {
if (d.getChar(offset) == ch && d.getChar(offset + 1) == ch) {
offset += 2;
multiline = true;
}
}
while (offset < endOffset) {
char curr = d.getChar(offset);
offset++;
if (curr == '\\') {
// ignore escaped characters
offset++;
} else if (curr == ch) {
if (!multiline) {
return offset;
} else if (offset + 2 <= endOffset) {
if (d.getChar(offset) == ch && d.getChar(offset + 1) == ch) {
return offset + 2;
}
}
}
}
return endOffset;
}
/**
* The preference setting for the visual tabulator display.
*
* @return the number of spaces displayed for a tabulator in the editor
*/
private int getVisualTabLengthPreference() {
return CodeFormatterUtil.getTabWidth();
}
// private boolean isAfterClassPrologue(IDocument d, int p) {
// DartHeuristicScanner scanner = new DartHeuristicScanner(d);
// DartIndenter indenter = new DartIndenter(d, scanner, null);
// return indenter.isAfterClassPrologue(p);
// }
private boolean hasMultiLineStringQuotes(String lineContent) {
return lineContent.contains("'''") || lineContent.contains("\"\"\"");
}
private boolean isClosed(IDocument document, int offset, int length) {
CompilationUnitInfo info = getCompilationUnitForMethod(document, offset);
if (info == null) {
return false;
}
String source = new String(info.buffer);
CompilationUnit compilationUnit;
try {
compilationUnit = internalParseCompilationUnit(source);
} catch (AnalysisException e) {
DartToolsPlugin.log("Parser Exception", e);
return true;
}
if (compilationUnit == null) {
// TODO Try a different structure: remove the method wrapper ____f
// and reparse the source
return true;
}
final int relativeOffset = offset - info.delta;
AstNode node = new NodeLocator(relativeOffset).searchWithin(compilationUnit);
if (length == 0) {
while (node != null
&& (relativeOffset == node.getOffset() || relativeOffset == node.getEnd())) {
node = node.getParent();
}
}
if (node == null) {
return false;
}
NodeBracketer bracketer = new NodeBracketer(document, info, offset, length);
node.accept(bracketer);
return bracketer.getResult();
}
/**
* The preference setting that tells whether to insert spaces when pressing the Tab key.
*
* @return <code>true</code> if spaces are inserted when pressing the Tab key
*/
private boolean isInsertingSpacesForTab() {
DartX.todo(); // Restore pref lookup
return true;
// return JavaScriptCore.SPACE.equals(getCoreOption(fProject,
// DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR));
}
private boolean isLineDelimiter(IDocument document, String text) {
String[] delimiters = document.getLegalLineDelimiters();
if (delimiters != null) {
return TextUtilities.equals(delimiters, text) > -1;
}
return false;
}
// /**
// * If "p" is right after "{" and the next token is "," or ")" - i.e. tokens that we expect to
// * follow a closure in an argument list.
// */
// private boolean looksLikeAfterLBraceForClosure(IDocument d, int p) {
// DartHeuristicScanner scanner = new DartHeuristicScanner(d);
// int len = d.getLength();
// int token = scanner.nextToken(p, len);
// if (token == Symbols.TokenCOMMA || token == Symbols.TokenRPAREN) {
// return true;
// }
// return false;
// }
/**
* Tells whether the given inserted string represents hitting the Tab key.
*
* @param text the text to check
* @return <code>true</code> if the text represents hitting the Tab key
*/
private boolean isRepresentingTab(String text) {
if (text == null) {
return false;
}
if (isInsertingSpacesForTab()) {
if (text.length() == 0 || text.length() > getVisualTabLengthPreference()) {
return false;
}
for (int i = 0; i < text.length(); i++) {
if (text.charAt(i) != ' ') {
return false;
}
}
return true;
} else {
return text.length() == 1 && text.charAt(0) == '\t';
}
}
private boolean shouldCloseUnbalancedBrace(IDocument d, int p, int lineEnd) {
DartHeuristicScanner scanner = new DartHeuristicScanner(d);
if (scanner.findNonWhitespaceForward(p, lineEnd) == -1) {
return true;
}
int token = scanner.nextToken(p, lineEnd);
return token == Symbols.TokenCOMMA || token == Symbols.TokenRPAREN
|| token == Symbols.TokenRBRACKET;
}
private void smartIndentAfterClosingBracket(IDocument d, DocumentCommand c) {
if (c.offset == -1 || d.getLength() == 0) {
return;
}
try {
int p = c.offset == d.getLength() ? c.offset - 1 : c.offset;
int line = d.getLineOfOffset(p);
int start = d.getLineOffset(line);
int whiteend = findEndOfWhiteSpace(d, start, c.offset);
DartHeuristicScanner scanner = new DartHeuristicScanner(d);
DartIndenter indenter = new DartIndenter(d, scanner, null);
// shift only when line does not contain any text up to the closing
// bracket
if (whiteend == c.offset) {
// evaluate the line with the opening bracket that matches out closing
// bracket
int reference = indenter.findReferencePosition(c.offset, false, true, false, false);
int indLine = d.getLineOfOffset(reference);
if (indLine != -1 && indLine != line) {
// take the indent of the found line
StringBuffer replaceText = new StringBuffer(getIndentOfLine(d, indLine));
// add the rest of the current line including the just added close
// bracket
replaceText.append(d.get(whiteend, c.offset - whiteend));
replaceText.append(c.text);
// modify document command
c.length += c.offset - start;
c.offset = start;
c.text = replaceText.toString();
}
}
} catch (BadLocationException e) {
DartToolsPlugin.log(e);
}
}
private void smartIndentAfterNewLine(IDocument d, DocumentCommand c) {
DartHeuristicScanner scanner = new DartHeuristicScanner(d);
DartIndenter indenter = new DartIndenter(d, scanner, null);
StringBuffer indent = indenter.computeIndentation(c.offset);
if (indent == null) {
indent = new StringBuffer();
}
int docLength = d.getLength();
if (c.offset == -1 || docLength == 0) {
return;
}
try {
int p = c.offset;
int line = d.getLineOfOffset(p);
StringBuffer buf = new StringBuffer(c.text + indent);
IRegion reg = d.getLineInformation(line);
int lineEnd = reg.getOffset() + reg.getLength();
int contentStart = findEndOfWhiteSpace(d, c.offset, lineEnd);
c.length = Math.max(contentStart - c.offset, 0);
int start = reg.getOffset();
ITypedRegion region = TextUtilities.getPartition(d, fPartitioning, start, true);
if (DartPartitions.DART_DOC.equals(region.getType())) {
start = d.getLineInformationOfOffset(region.getOffset()).getOffset();
}
// insert closing brace on new line after an unclosed opening brace
if (getBracketCount(d, start, c.offset, true) > 0 && closeBrace()
&& !isClosed(d, c.offset, c.length)) {
c.caretOffset = c.offset + buf.length();
c.shiftsCaret = false;
// // copy old content of line behind insertion point to new line
// // unless we think we are inserting an unnamed function argument
// if (!isAfterClassPrologue(d, p)) {
// if (!looksLikeAfterLBraceForClosure(d, p)) {
// if (lineEnd - contentStart > 0) {
// c.length = lineEnd - c.offset;
// buf.append(d.get(contentStart, lineEnd - contentStart).toCharArray());
// }
// }
// }
if (shouldCloseUnbalancedBrace(d, p, lineEnd)) {
buf.append(TextUtilities.getDefaultLineDelimiter(d));
StringBuffer reference = null;
int nonWS = findEndOfWhiteSpace(d, start, lineEnd);
if (nonWS < c.offset && d.getChar(nonWS) == '{') {
reference = new StringBuffer(d.get(start, nonWS - start));
} else {
reference = indenter.getReferenceIndentation(c.offset, false);
}
if (reference != null) {
buf.append(reference);
}
buf.append('}');
}
}
// insert extra line upon new line between two braces
else if (c.offset > start && contentStart < lineEnd && d.getChar(contentStart) == '}') {
int firstCharPos = scanner.findNonWhitespaceBackward(c.offset - 1, start);
if (firstCharPos != DartHeuristicScanner.NOT_FOUND && d.getChar(firstCharPos) == '{') {
c.caretOffset = c.offset + buf.length();
c.shiftsCaret = false;
StringBuffer reference = null;
int nonWS = findEndOfWhiteSpace(d, start, lineEnd);
if (nonWS < c.offset && d.getChar(nonWS) == '{') {
reference = new StringBuffer(d.get(start, nonWS - start));
} else {
reference = indenter.getReferenceIndentation(c.offset);
}
buf.append(TextUtilities.getDefaultLineDelimiter(d));
if (reference != null) {
buf.append(reference);
}
}
}
c.text = buf.toString();
} catch (BadLocationException e) {
DartToolsPlugin.log(e);
}
}
private void smartIndentAfterOpeningBracket(IDocument d, DocumentCommand c) {
if (c.offset < 1 || d.getLength() == 0) {
return;
}
DartHeuristicScanner scanner = new DartHeuristicScanner(d);
int p = c.offset == d.getLength() ? c.offset - 1 : c.offset;
try {
// current line
int line = d.getLineOfOffset(p);
int lineOffset = d.getLineOffset(line);
// make sure we don't have any leading comments etc.
if (d.get(lineOffset, p - lineOffset).trim().length() != 0) {
return;
}
// line of last code
int pos = scanner.findNonWhitespaceBackward(p, DartHeuristicScanner.UNBOUND);
if (pos == -1) {
return;
}
int lastLine = d.getLineOfOffset(pos);
// only shift if the last line is further up and is a brace-less block candidate
if (lastLine < line) {
DartIndenter indenter = new DartIndenter(d, scanner, null);
StringBuffer indent = indenter.computeIndentation(p, true);
String toDelete = d.get(lineOffset, c.offset - lineOffset);
if (indent != null && !indent.toString().equals(toDelete)) {
c.text = indent.append(c.text).toString();
c.length += c.offset - lineOffset;
c.offset = lineOffset;
}
}
} catch (BadLocationException e) {
DartToolsPlugin.log(e);
}
}
private void smartIndentOnKeypress(IDocument document, DocumentCommand command) {
switch (command.text.charAt(0)) {
case '}':
smartIndentAfterClosingBracket(document, command);
break;
case '{':
smartIndentAfterOpeningBracket(document, command);
break;
case 'e':
smartIndentUponE(document, command);
break;
case 't':
smartIndentUponT(document, command);
break;
case ':':
smartIndentUponColon(document, command);
}
}
private void smartIndentUponColon(IDocument d, DocumentCommand c) {
if (c.offset < 1 || d.getLength() == 0) {
return;
}
DartHeuristicScanner scanner = new DartHeuristicScanner(d);
int p = c.offset == d.getLength() ? c.offset - 1 : c.offset;
try {
// get current line
int line = d.getLineOfOffset(p);
int lineOffset = d.getLineOffset(line);
// make sure we don't have any leading comments etc.
String initialContent = d.get(lineOffset, p - lineOffset).trim();
if (initialContent.length() != 0) {
return;
}
// previous of last code
int pos = scanner.findNonWhitespaceBackward(p, DartHeuristicScanner.UNBOUND);
if (pos == -1) {
return;
}
int tok = scanner.previousToken(p - 1, DartHeuristicScanner.UNBOUND);
if (tok != DartHeuristicScanner.TokenRPAREN) {
return;
}
int lparenLoc = scanner.findOpeningPeer(scanner.getPosition(), '(', ')');
// try to identify a constructor decl name just before lparenLoc
tok = scanner.previousToken(lparenLoc - 1, DartHeuristicScanner.UNBOUND);
if (tok != DartHeuristicScanner.TokenIDENT) {
return;
}
tok = scanner.previousToken(scanner.getPosition() - 1, DartHeuristicScanner.UNBOUND);
if (tok == DartHeuristicScanner.TokenOTHER) { // period is Other
tok = scanner.previousToken(lparenLoc, DartHeuristicScanner.UNBOUND);
if (tok != DartHeuristicScanner.TokenIDENT) {
return;
}
}
DartIndenter indenter = new DartIndenter(d, scanner, null);
int whiteend = findEndOfWhiteSpace(d, lineOffset, c.offset);
// shift only when line does not contain any text up to the closing bracket
if (whiteend == c.offset) {
// adjust position of colon being inserted to 4 spaces in relative to previous line
int reference = indenter.findReferencePosition(c.offset, false, true, false, false);
int indLine = d.getLineOfOffset(reference);
if (indLine != -1 && indLine != line) {
// take the indent of the found line
StringBuffer replaceText = new StringBuffer(getIndentOfLine(d, indLine));
int additionalIndentLevels = 3;
String indent = CodeFormatterUtil.createIndentString(additionalIndentLevels);
replaceText.append(indent);
// add the rest of the current line including the just added colon
replaceText.append(d.get(whiteend, c.offset - whiteend));
replaceText.append(c.text);
// modify document command
c.length += c.offset - lineOffset;
c.offset = lineOffset;
c.text = replaceText.toString();
}
}
} catch (BadLocationException e) {
DartToolsPlugin.log(e);
}
}
private void smartIndentUponE(IDocument d, DocumentCommand c) {
if (c.offset < 4 || d.getLength() == 0) {
return;
}
try {
String content = d.get(c.offset - 3, 3);
if (content.equals("els")) { //$NON-NLS-1$
DartHeuristicScanner scanner = new DartHeuristicScanner(d);
int p = c.offset - 3;
// current line
int line = d.getLineOfOffset(p);
int lineOffset = d.getLineOffset(line);
// make sure we don't have any leading comments etc.
if (d.get(lineOffset, p - lineOffset).trim().length() != 0) {
return;
}
// line of last code
int pos = scanner.findNonWhitespaceBackward(p - 1, DartHeuristicScanner.UNBOUND);
if (pos == -1) {
return;
}
int lastLine = d.getLineOfOffset(pos);
// only shift if the last line is further up and is a brace-less block candidate
if (lastLine < line) {
DartIndenter indenter = new DartIndenter(d, scanner, null);
int ref = indenter.findReferencePosition(p, true, false, false, false);
if (ref == DartHeuristicScanner.NOT_FOUND) {
return;
}
int refLine = d.getLineOfOffset(ref);
String indent = getIndentOfLine(d, refLine);
if (indent != null) {
c.text = indent.toString() + "else"; //$NON-NLS-1$
c.length += c.offset - lineOffset;
c.offset = lineOffset;
}
}
return;
}
if (content.equals("cas")) { //$NON-NLS-1$
smartReindentSwitchCase(d, c, "case", 3);
return;
}
} catch (BadLocationException e) {
DartToolsPlugin.log(e);
}
}
private void smartIndentUponT(IDocument d, DocumentCommand c) {
int numCharsBeforeT = 6;
if (c.offset < numCharsBeforeT || d.getLength() == 0) {
return;
}
try {
String content = d.get(c.offset - numCharsBeforeT, numCharsBeforeT);
if (content.equals("defaul")) { //$NON-NLS-1$
smartReindentSwitchCase(d, c, "default", numCharsBeforeT); //$NON-NLS-1$
return;
}
} catch (BadLocationException e) {
DartToolsPlugin.log(e);
}
}
private void smartPaste(IDocument document, DocumentCommand command) {
int newOffset = command.offset;
int newLength = command.length;
String newText = command.text;
try {
DartHeuristicScanner scanner = new DartHeuristicScanner(document);
DartIndenter indenter = new DartIndenter(document, scanner, null);
int offset = newOffset;
// reference position to get the indent from
int refOffset = indenter.findReferencePosition(offset);
if (refOffset == DartHeuristicScanner.NOT_FOUND) {
return;
}
int peerOffset = getPeerPosition(document, command);
peerOffset = indenter.findReferencePosition(peerOffset);
if (peerOffset != DartHeuristicScanner.NOT_FOUND) {
refOffset = Math.min(refOffset, peerOffset);
}
// eat any WS before the insertion to the beginning of the line
int firstLine = 1; // don't format the first line per default, as it has
// other content before it
IRegion line = document.getLineInformationOfOffset(offset);
String notSelected = document.get(line.getOffset(), offset - line.getOffset());
if (notSelected.trim().length() == 0) {
newLength += notSelected.length();
newOffset = line.getOffset();
firstLine = 0;
}
// prefix: the part we need for formatting but won't paste
IRegion refLine = document.getLineInformationOfOffset(refOffset);
String prefix = document.get(refLine.getOffset(), newOffset - refLine.getOffset());
// I don't see a good solution for pasting cascades.
// For now, if we paste cascade after other cascade, just force the same indentation.
String forcedCascadePrefix = computeForcedCascadePrefix(indenter, document, offset, newText);
// handle the indentation computation inside a temporary document
Document temp = new Document(prefix + newText);
DocumentRewriteSession session = temp.startRewriteSession(DocumentRewriteSessionType.STRICTLY_SEQUENTIAL);
scanner = new DartHeuristicScanner(temp);
indenter = new DartIndenter(temp, scanner, null);
installDartStuff(temp);
// indent the first and second line
// compute the relative indentation difference from the second line
// (as the first might be partially selected) and use the value to
// indent all other lines.
StringBuffer addition = new StringBuffer();
int insertLength = 0;
int firstLineOriginalIndent = 0;
int firstLineIndent = 0;
int first = document.computeNumberOfLines(prefix) + firstLine; // don't format first line
int lines = temp.getNumberOfLines();
int tabLength = getVisualTabLengthPreference();
boolean isInMultiLineString = false;
{
IRegion r = temp.getLineInformation(0);
isInMultiLineString = hasMultiLineStringQuotes(temp.get(r.getOffset(), r.getLength()));
}
for (int l = first; l < lines; l++) { // we don't change the number of lines while adding indents
IRegion r = temp.getLineInformation(l);
int lineOffset = r.getOffset();
int lineLength = r.getLength();
String lineContent = temp.get(lineOffset, lineLength);
if (lineLength == 0) {
continue;
}
// indent the first pasted line
String current = getCurrentIndent(temp, l);
if (l == first) {
firstLineOriginalIndent = computeVisualLength(current, tabLength);
}
// unless it is a line comment
if (current.startsWith(LINE_COMMENT)) {
continue;
}
StringBuffer correct;
if (l == first) {
if (forcedCascadePrefix != null) {
correct = new StringBuffer(forcedCascadePrefix);
} else {
correct = indenter.computeIndentation(lineOffset);
}
firstLineIndent = computeVisualLength(correct, tabLength);
} else {
correct = new StringBuffer();
int secondIndent = firstLineIndent + computeVisualLength(current, tabLength)
- firstLineOriginalIndent;
if (secondIndent > 0) {
correct.append(StringUtils.repeat(' ', secondIndent));
}
}
if (correct == null) {
return; // bail out
}
insertLength = subtractIndent(correct, current, addition, tabLength);
// relatively indent all pasted lines
if (!isInMultiLineString) {
if (insertLength > 0) {
addIndent(temp, l, addition, tabLength);
} else if (insertLength < 0) {
cutIndent(temp, l, -insertLength, tabLength);
}
}
if (hasMultiLineStringQuotes(lineContent)) {
isInMultiLineString = !isInMultiLineString;
}
}
removeDartStuff(temp);
temp.stopRewriteSession(session);
newText = temp.get(prefix.length(), temp.getLength() - prefix.length());
// if a tab causes indentation to the current level, allow it to add another level
if (!(newText.trim().isEmpty() && isRepresentingTab(command.text))) {
command.offset = newOffset;
command.length = newLength;
command.text = newText;
}
} catch (Throwable e) {
DartToolsPlugin.log(e);
}
}
private void smartReindentSwitchCase(IDocument d, DocumentCommand c, String keyword, int delta)
throws BadLocationException {
DartHeuristicScanner scanner = new DartHeuristicScanner(d);
int p = c.offset - delta;
// current line
int line = d.getLineOfOffset(p);
int lineOffset = d.getLineOffset(line);
// make sure we don't have any leading comments etc.
if (d.get(lineOffset, p - lineOffset).trim().length() != 0) {
return;
}
// line of last code
int pos = scanner.findNonWhitespaceBackward(p - 1, DartHeuristicScanner.UNBOUND);
if (pos == -1) {
return;
}
int lastLine = d.getLineOfOffset(pos);
// only shift if the last line is further up and is a brace-less block candidate
if (lastLine < line) {
DartIndenter indenter = new DartIndenter(d, scanner, null);
int ref = indenter.findReferencePosition(p, false, false, false, true);
if (ref == DartHeuristicScanner.NOT_FOUND) {
return;
}
int refLine = d.getLineOfOffset(ref);
int nextToken = scanner.nextToken(ref, DartHeuristicScanner.UNBOUND);
String indent;
if (nextToken == Symbols.TokenCASE || nextToken == Symbols.TokenDEFAULT) {
indent = getIndentOfLine(d, refLine);
} else {
// at the brace of the switch
indent = indenter.computeIndentation(p).toString();
}
if (indent != null) {
c.text = indent.toString() + keyword; //$NON-NLS-1$
c.length += c.offset - lineOffset;
c.offset = lineOffset;
}
}
return;
}
/**
* Computes the difference of two indentations and returns the difference in length of current and
* correct. If the return value is positive, <code>addition</code> is initialized with a substring
* of that length of <code>correct</code>.
*
* @param correct the correct indentation
* @param current the current indentation (might contain non-whitespace)
* @param difference a string buffer - if the return value is positive, it will be cleared and set
* to the substring of <code>current</code> of that length
* @param tabLength the length of a tab
* @return the difference in length of <code>correct</code> and <code>current</code>
*/
private int subtractIndent(CharSequence correct, CharSequence current, StringBuffer difference,
int tabLength) {
int c1 = computeVisualLength(correct, tabLength);
int c2 = computeVisualLength(current, tabLength);
int diff = c1 - c2;
if (diff <= 0) {
return diff;
}
difference.setLength(0);
int len = 0, i = 0;
while (len < diff) {
char c = correct.charAt(i++);
difference.append(c);
len += computeVisualLength(c, tabLength);
}
return diff;
}
}