| Index: lib/src/source_visitor.dart
|
| diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
|
| index e0780ff342e09053dc5a1966cae3ddcd2934bd49..7906fe463d7d2594a5555c63e2c24d2d78b29985 100644
|
| --- a/lib/src/source_visitor.dart
|
| +++ b/lib/src/source_visitor.dart
|
| @@ -21,28 +21,38 @@ class SourceVisitor implements AstVisitor {
|
| /// Cached line info for calculating blank lines.
|
| LineInfo _lineInfo;
|
|
|
| - /// The source being formatted (used in interpolation handling)
|
| - final String _source;
|
| + /// The source being formatted.
|
| + final SourceCode _source;
|
| +
|
| + /// `true` if the visitor has written past the beginning of the selection in
|
| + /// the original source text.
|
| + bool _passedSelectionStart = false;
|
| +
|
| + /// `true` if the visitor has written past the end of the selection in the
|
| + /// original source text.
|
| + bool _passedSelectionEnd = false;
|
|
|
| /// Initialize a newly created visitor to write source code representing
|
| /// the visited nodes to the given [writer].
|
| - SourceVisitor(DartFormatter formatter, this._lineInfo, this._source,
|
| - StringBuffer outputBuffer)
|
| - : _writer = new LineWriter(formatter, outputBuffer);
|
| + SourceVisitor(DartFormatter formatter, this._lineInfo, SourceCode source)
|
| + : _source = source,
|
| + _writer = new LineWriter(formatter, source);
|
|
|
| - /// Run the visitor on [node], writing all of the formatted output to the
|
| - /// output buffer.
|
| + /// Runs the visitor on [node], formatting its contents.
|
| + ///
|
| + /// Returns a [SourceCode] containing the resulting formatted source and
|
| + /// updated selection, if any.
|
| ///
|
| /// This is the only method that should be called externally. Everything else
|
| /// is effectively private.
|
| - void run(AstNode node) {
|
| + SourceCode run(AstNode node) {
|
| visit(node);
|
|
|
| // Output trailing comments.
|
| writePrecedingCommentsAndNewlines(node.endToken.next);
|
|
|
| - // Finish off the last line.
|
| - _writer.end();
|
| + // Finish writing and return the complete result.
|
| + return _writer.end();
|
| }
|
|
|
| visitAdjacentStrings(AdjacentStrings node) {
|
| @@ -1073,7 +1083,8 @@ class SourceVisitor implements AstVisitor {
|
| // formatter ensures it gets a newline after it. Since the script tag must
|
| // come at the top of the file, we don't have to worry about preceding
|
| // comments or whitespace.
|
| - _writer.write(node.scriptTag.lexeme.trim());
|
| + _writeText(node.scriptTag.lexeme.trim(), node.offset);
|
| +
|
| oneOrTwoNewlines();
|
| }
|
|
|
| @@ -1097,7 +1108,7 @@ class SourceVisitor implements AstVisitor {
|
| // comments are written first.
|
| writePrecedingCommentsAndNewlines(node.literal);
|
|
|
| - _writeStringLiteral(node.literal.lexeme);
|
| + _writeStringLiteral(node.literal.lexeme, node.offset);
|
| }
|
|
|
| visitStringInterpolation(StringInterpolation node) {
|
| @@ -1109,7 +1120,8 @@ class SourceVisitor implements AstVisitor {
|
| // contents of interpolated strings. Instead, it treats the entire thing as
|
| // a single (possibly multi-line) chunk of text.
|
| _writeStringLiteral(
|
| - _source.substring(node.beginToken.offset, node.endToken.end));
|
| + _source.text.substring(node.beginToken.offset, node.endToken.end),
|
| + node.offset);
|
| }
|
|
|
| visitSuperConstructorInvocation(SuperConstructorInvocation node) {
|
| @@ -1552,15 +1564,18 @@ class SourceVisitor implements AstVisitor {
|
| ///
|
| /// Splits multiline strings into separate chunks so that the line splitter
|
| /// can handle them correctly.
|
| - void _writeStringLiteral(String string) {
|
| + void _writeStringLiteral(String string, int offset) {
|
| // Split each line of a multiline string into separate chunks.
|
| var lines = string.split("\n");
|
|
|
| - _writer.write(lines.first);
|
| + _writeText(lines.first, offset);
|
| + offset += lines.first.length;
|
|
|
| for (var line in lines.skip(1)) {
|
| _writer.writeWhitespace(Whitespace.newlineFlushLeft);
|
| - _writer.write(line);
|
| + offset++;
|
| + _writeText(line, offset);
|
| + offset += line.length;
|
| }
|
| }
|
|
|
| @@ -1623,7 +1638,7 @@ class SourceVisitor implements AstVisitor {
|
|
|
| if (before != null) before();
|
|
|
| - _writer.write(token.lexeme);
|
| + _writeText(token.lexeme, token.offset);
|
|
|
| if (after != null) after();
|
| }
|
| @@ -1661,10 +1676,20 @@ class SourceVisitor implements AstVisitor {
|
| previousLine = commentLine;
|
| }
|
|
|
| - comments.add(new SourceComment(comment.toString().trim(),
|
| + var sourceComment = new SourceComment(comment.toString().trim(),
|
| commentLine - previousLine,
|
| isLineComment: comment.type == TokenType.SINGLE_LINE_COMMENT,
|
| - isStartOfLine: _startColumn(comment) == 1));
|
| + isStartOfLine: _startColumn(comment) == 1);
|
| +
|
| + // If this comment contains either of the selection endpoints, mark them
|
| + // in the comment.
|
| + var start = _getSelectionStartWithin(comment.offset, comment.length);
|
| + if (start != null) sourceComment.startSelection(start);
|
| +
|
| + var end = _getSelectionEndWithin(comment.offset, comment.length);
|
| + if (end != null) sourceComment.endSelection(end);
|
| +
|
| + comments.add(sourceComment);
|
|
|
| previousLine = _endLine(comment);
|
| comment = comment.next;
|
| @@ -1673,6 +1698,80 @@ class SourceVisitor implements AstVisitor {
|
| _writer.writeComments(comments, tokenLine - previousLine, token.lexeme);
|
| }
|
|
|
| + /// Write [text] to the current chunk, given that it starts at [offset] in
|
| + /// the original source.
|
| + ///
|
| + /// Also outputs the selection endpoints if needed.
|
| + void _writeText(String text, int offset) {
|
| + _writer.write(text);
|
| +
|
| + // If this text contains either of the selection endpoints, mark them in
|
| + // the chunk.
|
| + var start = _getSelectionStartWithin(offset, text.length);
|
| + if (start != null) {
|
| + _writer.startSelectionFromEnd(text.length - start);
|
| + }
|
| +
|
| + var end = _getSelectionEndWithin(offset, text.length);
|
| + if (end != null) {
|
| + _writer.endSelectionFromEnd(text.length - end);
|
| + }
|
| + }
|
| +
|
| + /// Returns the number of characters past [offset] in the source where the
|
| + /// selection start appears if it appears before `offset + length`.
|
| + ///
|
| + /// Returns `null` if the selection start has already been processed or is
|
| + /// not within that range.
|
| + int _getSelectionStartWithin(int offset, int length) {
|
| + // If there is no selection, do nothing.
|
| + if (_source.selectionStart == null) return null;
|
| +
|
| + // If we've already passed it, don't consider it again.
|
| + if (_passedSelectionStart) return null;
|
| +
|
| + var start = _source.selectionStart - offset;
|
| +
|
| + // If it started in whitespace before this text, push it forward to the
|
| + // beginning of the non-whitespace text.
|
| + if (start < 0) start = 0;
|
| +
|
| + // If we haven't reached it yet, don't consider it.
|
| + if (start >= length) return null;
|
| +
|
| + // We found it.
|
| + _passedSelectionStart = true;
|
| +
|
| + return start;
|
| + }
|
| +
|
| + /// Returns the number of characters past [offset] in the source where the
|
| + /// selection endpoint appears if it appears before `offset + length`.
|
| + ///
|
| + /// Returns `null` if the selection endpoint has already been processed or is
|
| + /// not within that range.
|
| + int _getSelectionEndWithin(int offset, int length) {
|
| + // If there is no selection, do nothing.
|
| + if (_source.selectionLength == null) return null;
|
| +
|
| + // If we've already passed it, don't consider it again.
|
| + if (_passedSelectionEnd) return null;
|
| +
|
| + var end = _source.selectionStart + _source.selectionLength - offset;
|
| +
|
| + // If it started in whitespace before this text, push it forward to the
|
| + // beginning of the non-whitespace text.
|
| + if (end < 0) end = 0;
|
| +
|
| + // If we haven't reached it yet, don't consider it.
|
| + if (end >= length) return null;
|
| +
|
| + // We found it.
|
| + _passedSelectionEnd = true;
|
| +
|
| + return end;
|
| + }
|
| +
|
| /// Gets the 1-based line number that the beginning of [token] lies on.
|
| int _startLine(Token token) => _lineInfo.getLocation(token.offset).lineNumber;
|
|
|
|
|