Skip to content

Commit 740759e

Browse files
authored
Get selections running through the new back end. (#1300)
Get selections running through the new back end. This mostly ports similar code directly over from the old back end, including a couple of quirks in its behavior around whitespace. I considered simplifying that, but I think it's better to have fewer changes during this already huge migration. Because the new back end adds and removes trailing commas, it treats all commas as whitespace. When a selection is in whitespace, it will be snapped to the nearest non-whitespace code. These two interact in that a selection right around a comma may snap over it because the comma is considered whitespace. I thought about making it smarter but I didn't want to rathole too long. Instead, I left a comment in CodeWriter.write() with some context and we can revisit it when the overall rewrite is further along. We may end up just deleting that comment later if we decide the behavior here is good enough (I think it is), but this way to remember to think about it.
1 parent 6eb25b5 commit 740759e

15 files changed

+461
-43
lines changed

lib/src/back_end/code_writer.dart

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,33 @@ class CodeWriter {
5959
/// The options for the current innermost piece being formatted.
6060
_PieceOptions get _options => _pieceOptions.last;
6161

62+
/// The offset in the formatted code where the selection starts.
63+
///
64+
/// This is `null` until the piece containing the selection start is reached
65+
/// at which point it gets set. It remains `null` if there is no selection.
66+
int? _selectionStart;
67+
68+
/// The offset in the formatted code where the selection ends.
69+
///
70+
/// This is `null` until the piece containing the selection end is reached
71+
/// at which point it gets set. It remains `null` if there is no selection.
72+
int? _selectionEnd;
73+
6274
CodeWriter(this._pageWidth, this._pieceStates);
6375

6476
/// Returns the finished code produced by formatting the tree of pieces and
6577
/// the final score.
66-
(String, Score) finish() {
78+
Solution finish() {
6779
_finishLine();
68-
return (
69-
_buffer.toString(),
70-
Score(isValid: !_containsInvalidNewline, overflow: _overflow, cost: _cost)
71-
);
80+
return Solution(
81+
_pieceStates,
82+
_buffer.toString(),
83+
Score(
84+
isValid: !_containsInvalidNewline,
85+
overflow: _overflow,
86+
cost: _cost),
87+
_selectionStart,
88+
_selectionEnd);
7289
}
7390

7491
/// Notes that a newline has been written.
@@ -92,6 +109,14 @@ class CodeWriter {
92109
/// If [text] contains any internal newlines, the caller is responsible for
93110
/// also calling [handleNewline()].
94111
void write(String text) {
112+
// TODO(tall): Calling this directly from pieces outside of TextPiece may
113+
// not handle selections as gracefully as we could. A selection marker may
114+
// get pushed past the text written here. Currently, this is only called
115+
// directly for commas in list-like things, and `;` in for loops. In
116+
// general, it's better for all text written to the output to live inside
117+
// TextPieces because that will preserve selection markers. Consider doing
118+
// something smarter for commas in lists and semicolons in for loops.
119+
95120
_buffer.write(text);
96121
_column += text.length;
97122
}
@@ -204,6 +229,18 @@ class CodeWriter {
204229
if (piece != null) format(piece);
205230
}
206231

232+
/// Sets [selectionStart] to be [start] code units into the output.
233+
void startSelection(int start) {
234+
assert(_selectionStart == null);
235+
_selectionStart = _buffer.length + start;
236+
}
237+
238+
/// Sets [selectionEnd] to be [end] code units into the output.
239+
void endSelection(int end) {
240+
assert(_selectionEnd == null);
241+
_selectionEnd = _buffer.length + end;
242+
}
243+
207244
void _finishLine() {
208245
// If the completed line is too long, track the overflow.
209246
if (_column >= _pageWidth) {

lib/src/back_end/solution.dart

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,26 @@ class Solution implements Comparable<Solution> {
7272
/// The score resulting from the selected piece states.
7373
final Score score;
7474

75-
factory Solution(Piece root, int pageWidth, PieceStateSet state) {
75+
/// The offset in [text] where the selection starts, or `null` if there is
76+
/// no selection.
77+
final int? selectionStart;
78+
79+
/// The offset in [text] where the selection ends, or `null` if there is
80+
/// no selection.
81+
final int? selectionEnd;
82+
83+
factory Solution.initial(Piece root, int pageWidth, List<Piece> pieces) {
84+
return Solution._(root, pageWidth, PieceStateSet(pieces));
85+
}
86+
87+
factory Solution._(Piece root, int pageWidth, PieceStateSet state) {
7688
var writer = CodeWriter(pageWidth, state);
7789
writer.format(root);
78-
var (text, score) = writer.finish();
79-
return Solution._(state, text, score);
90+
return writer.finish();
8091
}
8192

82-
Solution._(this._state, this.text, this.score);
93+
Solution(this._state, this.text, this.score, this.selectionStart,
94+
this.selectionEnd);
8395

8496
/// When called on a [Solution] with some unselected piece states, chooses a
8597
/// piece and yields further solutions for each state that piece can have.
@@ -89,7 +101,7 @@ class Solution implements Comparable<Solution> {
89101

90102
return [
91103
for (var state in piece.states)
92-
Solution(root, pageWidth, _state.cloneWith(piece, state))
104+
Solution._(root, pageWidth, _state.cloneWith(piece, state))
93105
];
94106
}
95107

lib/src/back_end/solver.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class Solver {
3232

3333
/// Finds the best set of line splits for [piece] and returns the resulting
3434
/// formatted code.
35-
String format(Piece piece) {
35+
Solution format(Piece piece) {
3636
// Collect all of the pieces with states that can be selected.
3737
var unsolvedPieces = <Piece>[];
3838

@@ -44,14 +44,13 @@ class Solver {
4444

4545
traverse(piece);
4646

47-
var solution = _solve(piece, unsolvedPieces);
48-
return solution.text;
47+
return _solve(piece, unsolvedPieces);
4948
}
5049

5150
/// Finds the best solution for the piece tree starting at [root] with
5251
/// selectable [pieces].
5352
Solution _solve(Piece root, List<Piece> pieces) {
54-
var solution = Solution(root, _pageWidth, PieceStateSet(pieces));
53+
var solution = Solution.initial(root, _pageWidth, pieces);
5554
_queue.add(solution);
5655

5756
// The lowest cost solution found so far that does overflow.

lib/src/front_end/ast_node_visitor.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -913,11 +913,9 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
913913
@override
914914
void visitScriptTag(ScriptTag node) {
915915
// The lexeme includes the trailing newline. Strip it off since the
916-
// formatter ensures it gets a newline after it. Since the script tag must
917-
// come at the top of the file, we don't have to worry about preceding
918-
// comments or whitespace.
919-
// TODO(new-ir): Update selection if inside the script tag.
920-
pieces.write(node.scriptTag.lexeme.trim());
916+
// formatter ensures it gets a newline after it.
917+
pieces.writeText(node.scriptTag.lexeme.trim(),
918+
offset: node.scriptTag.offset);
921919
}
922920

923921
@override

lib/src/front_end/comment_writer.dart

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,8 @@ mixin CommentWriter {
141141
type = CommentType.block;
142142
}
143143

144-
var sourceComment = SourceComment(text, type, flushLeft: flushLeft);
145-
146-
// TODO(tall): If this comment contains either of the selection endpoints,
147-
// mark them in the comment.
144+
var sourceComment = SourceComment(text, type,
145+
offset: comment.offset, flushLeft: flushLeft);
148146

149147
comments._add(linesBefore, sourceComment);
150148

@@ -194,7 +192,14 @@ class SourceComment {
194192
/// re-indented.
195193
final bool flushLeft;
196194

197-
SourceComment(this.text, this.type, {required this.flushLeft});
195+
/// The number of code points in the original source code preceding the start
196+
/// of this comment.
197+
///
198+
/// Used to track selection markers within the comment.
199+
final int offset;
200+
201+
SourceComment(this.text, this.type,
202+
{required this.flushLeft, required this.offset});
198203

199204
/// Whether this comment contains a mandatory newline, either because it's a
200205
/// comment that should be on its own line or a lexeme with a newline inside

lib/src/front_end/piece_factory.dart

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -470,16 +470,10 @@ mixin PieceFactory implements CommentWriter {
470470
writeCommentsBefore(token);
471471

472472
if (before != null) before();
473-
writeLexeme(token.lexeme);
473+
pieces.writeToken(token);
474474
if (after != null) after();
475475
}
476476

477-
/// Writes the raw [lexeme] to the current text piece.
478-
void writeLexeme(String lexeme) {
479-
// TODO(tall): Preserve selection.
480-
pieces.write(lexeme);
481-
}
482-
483477
/// Writes a comma after [node], if there is one.
484478
void commaAfter(AstNode node, {bool trailing = false}) {
485479
var nextToken = node.endToken.next!;
@@ -489,7 +483,7 @@ mixin PieceFactory implements CommentWriter {
489483
// If there isn't a comma there, it must be a place where a trailing
490484
// comma can appear, so synthesize it. During formatting, we will decide
491485
// whether to include it.
492-
writeLexeme(',');
486+
pieces.writeText(',');
493487
}
494488
}
495489
}

0 commit comments

Comments
 (0)