[analysis_server] Set Container() as final tabstop/selection in Flutter snippets

Change-Id: I0f1fd31a051440ccc119188cccdb3168c9e58e20
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/245988
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Danny Tuppeny 2022-05-25 18:57:28 +00:00 committed by Commit Bot
parent 4d9b28dc08
commit fa8a33f60e
17 changed files with 135 additions and 52 deletions

View file

@ -109,7 +109,7 @@ a:focus, a:hover {
<body>
<h1>Analysis Server API Specification</h1>
<h1 style="color:#999999">Version
1.33.0
1.33.1
</h1>
<p>
This document contains a specification of the API provided by the
@ -236,6 +236,11 @@ a:focus, a:hover {
ignoring the item or treating it with some default/fallback handling.
</p>
<h3>Changelog</h3>
<h4>1.33.1</h4>
<ul>
<li><tt>SourceChange</tt> now has an optional <tt>selectionLength</tt> that may be
provided when <tt>selection</tt> is.</li>
</ul>
<h4>1.33.0</h4>
<ul>
<li>Requests <tt>getSuggestions2</tt> and <tt>getSuggestionDetails2</tt>
@ -5875,6 +5880,12 @@ a:focus, a:hover {
The position that should be selected after the edits have been
applied.
</p>
</dd><dt class="field"><b>selectionLength: int<span style="color:#999999"> (optional)</span></b></dt><dd>
<p>
The length of the selection (starting at Position) that should be selected after
the edits have been applied.
</p>
</dd><dt class="field"><b>id: String<span style="color:#999999"> (optional)</span></b></dt><dd>
<p>

View file

@ -8,7 +8,7 @@
// ignore_for_file: constant_identifier_names
const String PROTOCOL_VERSION = '1.33.0';
const String PROTOCOL_VERSION = '1.33.1';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';

View file

@ -153,6 +153,7 @@ lsp.WorkspaceEdit createWorkspaceEdit(
change.linkedEditGroups,
lineInfo,
selectionOffset: change.selection?.offset,
selectionLength: change.selectionLength,
);
// Compile the edits into a TextDocumentEdit for this file.
@ -908,6 +909,7 @@ lsp.SnippetTextEdit snippetTextEditFromEditGroups(
required List<server.LinkedEditGroup> editGroups,
required int editOffset,
required int? selectionOffset,
required int? selectionLength,
}) {
return lsp.SnippetTextEdit(
insertTextFormat: lsp.InsertTextFormat.Snippet,
@ -918,6 +920,7 @@ lsp.SnippetTextEdit snippetTextEditFromEditGroups(
editGroups: editGroups,
editOffset: editOffset,
selectionOffset: selectionOffset,
selectionLength: selectionLength,
),
);
}
@ -982,6 +985,8 @@ lsp.CompletionItem snippetToCompletionItem(
lineInfo,
selectionOffset:
changes.selection?.file == file ? changes.selection?.offset : null,
selectionLength:
changes.selection?.file == file ? changes.selectionLength : null,
);
// For LSP, we need to provide the main edit and other edits separately. The
@ -1477,6 +1482,7 @@ List<lsp.SnippetTextEdit> toSnippetTextEdits(
List<server.LinkedEditGroup> editGroups,
LineInfo lineInfo, {
required int? selectionOffset,
required int? selectionLength,
}) {
final snippetEdits = <lsp.SnippetTextEdit>[];
@ -1497,6 +1503,7 @@ List<lsp.SnippetTextEdit> toSnippetTextEdits(
editGroups: editGroups,
editOffset: edit.offset + offsetDelta,
selectionOffset: selectionOffset,
selectionLength: selectionLength,
));
offsetDelta += edit.replacement.length - edit.length;

View file

@ -21,6 +21,7 @@ String buildSnippetStringForEditGroups(
required List<server.LinkedEditGroup> editGroups,
required int editOffset,
int? selectionOffset,
int? selectionLength,
}) =>
_buildSnippetString(
text,
@ -29,6 +30,7 @@ String buildSnippetStringForEditGroups(
editGroupsOffset: editOffset,
selectionOffset:
selectionOffset != null ? selectionOffset - editOffset : null,
selectionLength: selectionLength,
);
/// Builds an LSP snippet string with supplied ranges as tab stops.

View file

@ -366,12 +366,11 @@ mixin FlutterWidgetSnippetProducerMixin on FlutterSnippetProducer {
bodyWriter: () {
builder.writeln('{');
builder.write(' return ');
builder.writeType(_getType(classContainer));
builder.writeln('(');
builder.write(' ');
builder.selectHere();
builder.writeln();
builder.writeln(' );');
builder.selectAll(() {
builder.writeType(_getType(classContainer));
builder.write('()');
});
builder.writeln(';');
builder.writeln(' }');
},
);

View file

@ -1622,6 +1622,7 @@ final Matcher isServerService = MatchesEnum('ServerService', ['LOG', 'STATUS']);
/// "edits": List<SourceFileEdit>
/// "linkedEditGroups": List<LinkedEditGroup>
/// "selection": optional Position
/// "selectionLength": optional int
/// "id": optional String
/// }
final Matcher isSourceChange =
@ -1631,6 +1632,7 @@ final Matcher isSourceChange =
'linkedEditGroups': isListOf(isLinkedEditGroup)
}, optionalFields: {
'selection': isPosition,
'selectionLength': isInt,
'id': isString
}));

View file

@ -2909,9 +2909,7 @@ class \${1:MyWidget} extends StatefulWidget {
class _\${1:MyWidget}State extends State<\${1:MyWidget}> {
@override
Widget build(BuildContext context) {
return Container(
\$0
);
return \${0:Container()};
}
}
@ -2968,9 +2966,7 @@ class _\${1:MyWidget}State extends State<\${1:MyWidget}>
@override
Widget build(BuildContext context) {
return Container(
\$0
);
return \${0:Container()};
}
}
@ -3006,9 +3002,7 @@ class \${1:MyWidget} extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
\$0
);
return \${0:Container()};
}
}
@ -3042,9 +3036,7 @@ class \${1:MyWidget} extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
\$0
);
return \${0:Container()};
}
}
@ -3072,9 +3064,7 @@ class \${1:MyWidget} extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
\$0
);
return \${0:Container()};
}
}
''');
@ -3100,9 +3090,7 @@ class \${1:MyWidget} extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
\$0
);
return \${0:Container()};
}
}
''');

View file

@ -104,9 +104,7 @@ class MyWidget extends StatefulWidget {
class _MyWidgetState extends State<MyWidget> {
@override
Widget build(BuildContext context) {
return Container(
);
return Container();
}
}''');
}
@ -142,13 +140,12 @@ class MyWidget extends StatefulWidget {
class _MyWidgetState extends State<MyWidget> {
@override
Widget build(BuildContext context) {
return Container(
);
return Container();
}
}''');
expect(snippet.change.selection!.file, testFile);
expect(snippet.change.selection!.offset, 373);
expect(snippet.change.selection!.offset, 356);
expect(snippet.change.selectionLength, 11);
expect(snippet.change.linkedEditGroups.map((group) => group.toJson()), [
{
'positions': [
@ -224,9 +221,7 @@ class _MyWidgetState extends State<MyWidget>
@override
Widget build(BuildContext context) {
return Container(
);
return Container();
}
}''');
}
@ -279,13 +274,12 @@ class _MyWidgetState extends State<MyWidget>
@override
Widget build(BuildContext context) {
return Container(
);
return Container();
}
}''');
expect(snippet.change.selection!.file, testFile);
expect(snippet.change.selection!.offset, 776);
expect(snippet.change.selection!.offset, 759);
expect(snippet.change.selectionLength, 11);
expect(snippet.change.linkedEditGroups.map((group) => group.toJson()), [
{
'positions': [
@ -336,9 +330,7 @@ class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
);
return Container();
}
}''');
}
@ -369,13 +361,12 @@ class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
);
return Container();
}
}''');
expect(snippet.change.selection!.file, testFile);
expect(snippet.change.selection!.offset, 259);
expect(snippet.change.selection!.offset, 242);
expect(snippet.change.selectionLength, 11);
expect(snippet.change.linkedEditGroups.map((group) => group.toJson()), [
{
'positions': [

View file

@ -55,6 +55,12 @@ public class SourceChange {
*/
private final Position selection;
/**
* The length of the selection (starting at Position) that should be selected after the edits have
* been applied.
*/
private final Integer selectionLength;
/**
* The optional identifier of the change kind. The identifier remains stable even if the message
* changes, or is parameterized.
@ -64,11 +70,12 @@ public class SourceChange {
/**
* Constructor for {@link SourceChange}.
*/
public SourceChange(String message, List<SourceFileEdit> edits, List<LinkedEditGroup> linkedEditGroups, Position selection, String id) {
public SourceChange(String message, List<SourceFileEdit> edits, List<LinkedEditGroup> linkedEditGroups, Position selection, Integer selectionLength, String id) {
this.message = message;
this.edits = edits;
this.linkedEditGroups = linkedEditGroups;
this.selection = selection;
this.selectionLength = selectionLength;
this.id = id;
}
@ -81,6 +88,7 @@ public class SourceChange {
ObjectUtilities.equals(other.edits, edits) &&
ObjectUtilities.equals(other.linkedEditGroups, linkedEditGroups) &&
ObjectUtilities.equals(other.selection, selection) &&
ObjectUtilities.equals(other.selectionLength, selectionLength) &&
ObjectUtilities.equals(other.id, id);
}
return false;
@ -91,8 +99,9 @@ public class SourceChange {
List<SourceFileEdit> edits = SourceFileEdit.fromJsonArray(jsonObject.get("edits").getAsJsonArray());
List<LinkedEditGroup> linkedEditGroups = LinkedEditGroup.fromJsonArray(jsonObject.get("linkedEditGroups").getAsJsonArray());
Position selection = jsonObject.get("selection") == null ? null : Position.fromJson(jsonObject.get("selection").getAsJsonObject());
Integer selectionLength = jsonObject.get("selectionLength") == null ? null : jsonObject.get("selectionLength").getAsInt();
String id = jsonObject.get("id") == null ? null : jsonObject.get("id").getAsString();
return new SourceChange(message, edits, linkedEditGroups, selection, id);
return new SourceChange(message, edits, linkedEditGroups, selection, selectionLength, id);
}
public static List<SourceChange> fromJsonArray(JsonArray jsonArray) {
@ -143,6 +152,14 @@ public class SourceChange {
return selection;
}
/**
* The length of the selection (starting at Position) that should be selected after the edits have
* been applied.
*/
public Integer getSelectionLength() {
return selectionLength;
}
@Override
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder();
@ -150,6 +167,7 @@ public class SourceChange {
builder.append(edits);
builder.append(linkedEditGroups);
builder.append(selection);
builder.append(selectionLength);
builder.append(id);
return builder.toHashCode();
}
@ -170,6 +188,9 @@ public class SourceChange {
if (selection != null) {
jsonObject.add("selection", selection.toJson());
}
if (selectionLength != null) {
jsonObject.addProperty("selectionLength", selectionLength);
}
if (id != null) {
jsonObject.addProperty("id", id);
}
@ -188,6 +209,8 @@ public class SourceChange {
builder.append(StringUtils.join(linkedEditGroups, ", ") + ", ");
builder.append("selection=");
builder.append(selection + ", ");
builder.append("selectionLength=");
builder.append(selectionLength + ", ");
builder.append("id=");
builder.append(id);
builder.append("]");

View file

@ -7,7 +7,7 @@
<body>
<h1>Analysis Server API Specification</h1>
<h1 style="color:#999999">Version
<version>1.33.0</version>
<version>1.33.1</version>
</h1>
<p>
This document contains a specification of the API provided by the
@ -134,6 +134,11 @@
ignoring the item or treating it with some default/fallback handling.
</p>
<h3>Changelog</h3>
<h4>1.33.1</h4>
<ul>
<li><tt>SourceChange</tt> now has an optional <tt>selectionLength</tt> that may be
provided when <tt>selection</tt> is.</li>
</ul>
<h4>1.33.0</h4>
<ul>
<li>Requests <tt>getSuggestions2</tt> and <tt>getSuggestionDetails2</tt>

View file

@ -4276,6 +4276,7 @@ class RemoveContentOverlay implements HasToJson {
/// "edits": List<SourceFileEdit>
/// "linkedEditGroups": List<LinkedEditGroup>
/// "selection": optional Position
/// "selectionLength": optional int
/// "id": optional String
/// }
///
@ -4294,6 +4295,10 @@ class SourceChange implements HasToJson {
/// The position that should be selected after the edits have been applied.
Position? selection;
/// The length of the selection (starting at Position) that should be
/// selected after the edits have been applied.
int? selectionLength;
/// The optional identifier of the change kind. The identifier remains stable
/// even if the message changes, or is parameterized.
String? id;
@ -4302,6 +4307,7 @@ class SourceChange implements HasToJson {
{List<SourceFileEdit>? edits,
List<LinkedEditGroup>? linkedEditGroups,
this.selection,
this.selectionLength,
this.id})
: edits = edits ?? <SourceFileEdit>[],
linkedEditGroups = linkedEditGroups ?? <LinkedEditGroup>[];
@ -4342,6 +4348,11 @@ class SourceChange implements HasToJson {
selection = Position.fromJson(
jsonDecoder, jsonPath + '.selection', json['selection']);
}
int? selectionLength;
if (json.containsKey('selectionLength')) {
selectionLength = jsonDecoder.decodeInt(
jsonPath + '.selectionLength', json['selectionLength']);
}
String? id;
if (json.containsKey('id')) {
id = jsonDecoder.decodeString(jsonPath + '.id', json['id']);
@ -4350,6 +4361,7 @@ class SourceChange implements HasToJson {
edits: edits,
linkedEditGroups: linkedEditGroups,
selection: selection,
selectionLength: selectionLength,
id: id);
} else {
throw jsonDecoder.mismatch(jsonPath, 'SourceChange', json);
@ -4369,6 +4381,10 @@ class SourceChange implements HasToJson {
if (selection != null) {
result['selection'] = selection.toJson();
}
var selectionLength = this.selectionLength;
if (selectionLength != null) {
result['selectionLength'] = selectionLength;
}
var id = this.id;
if (id != null) {
result['id'] = id;
@ -4411,6 +4427,7 @@ class SourceChange implements HasToJson {
listEqual(linkedEditGroups, other.linkedEditGroups,
(LinkedEditGroup a, LinkedEditGroup b) => a == b) &&
selection == other.selection &&
selectionLength == other.selectionLength &&
id == other.id;
}
return false;
@ -4422,6 +4439,7 @@ class SourceChange implements HasToJson {
edits,
linkedEditGroups,
selection,
selectionLength,
id,
);
}

View file

@ -8,7 +8,7 @@
// ignore_for_file: constant_identifier_names
const String PROTOCOL_VERSION = '1.33.0';
const String PROTOCOL_VERSION = '1.33.1';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';

View file

@ -2003,6 +2003,12 @@ a:focus, a:hover {
The position that should be selected after the edits have been
applied.
</p>
</dd><dt class="field"><b>selectionLength: int<span style="color:#999999"> (optional)</span></b></dt><dd>
<p>
The length of the selection (starting at Position) that should be selected after
the edits have been applied.
</p>
</dd><dt class="field"><b>id: String<span style="color:#999999"> (optional)</span></b></dt><dd>
<p>

View file

@ -4276,6 +4276,7 @@ class RemoveContentOverlay implements HasToJson {
/// "edits": List<SourceFileEdit>
/// "linkedEditGroups": List<LinkedEditGroup>
/// "selection": optional Position
/// "selectionLength": optional int
/// "id": optional String
/// }
///
@ -4294,6 +4295,10 @@ class SourceChange implements HasToJson {
/// The position that should be selected after the edits have been applied.
Position? selection;
/// The length of the selection (starting at Position) that should be
/// selected after the edits have been applied.
int? selectionLength;
/// The optional identifier of the change kind. The identifier remains stable
/// even if the message changes, or is parameterized.
String? id;
@ -4302,6 +4307,7 @@ class SourceChange implements HasToJson {
{List<SourceFileEdit>? edits,
List<LinkedEditGroup>? linkedEditGroups,
this.selection,
this.selectionLength,
this.id})
: edits = edits ?? <SourceFileEdit>[],
linkedEditGroups = linkedEditGroups ?? <LinkedEditGroup>[];
@ -4342,6 +4348,11 @@ class SourceChange implements HasToJson {
selection = Position.fromJson(
jsonDecoder, jsonPath + '.selection', json['selection']);
}
int? selectionLength;
if (json.containsKey('selectionLength')) {
selectionLength = jsonDecoder.decodeInt(
jsonPath + '.selectionLength', json['selectionLength']);
}
String? id;
if (json.containsKey('id')) {
id = jsonDecoder.decodeString(jsonPath + '.id', json['id']);
@ -4350,6 +4361,7 @@ class SourceChange implements HasToJson {
edits: edits,
linkedEditGroups: linkedEditGroups,
selection: selection,
selectionLength: selectionLength,
id: id);
} else {
throw jsonDecoder.mismatch(jsonPath, 'SourceChange', json);
@ -4369,6 +4381,10 @@ class SourceChange implements HasToJson {
if (selection != null) {
result['selection'] = selection.toJson();
}
var selectionLength = this.selectionLength;
if (selectionLength != null) {
result['selectionLength'] = selectionLength;
}
var id = this.id;
if (id != null) {
result['id'] = id;
@ -4411,6 +4427,7 @@ class SourceChange implements HasToJson {
listEqual(linkedEditGroups, other.linkedEditGroups,
(LinkedEditGroup a, LinkedEditGroup b) => a == b) &&
selection == other.selection &&
selectionLength == other.selectionLength &&
id == other.id;
}
return false;
@ -4422,6 +4439,7 @@ class SourceChange implements HasToJson {
edits,
linkedEditGroups,
selection,
selectionLength,
id,
);
}

View file

@ -109,6 +109,10 @@ class ChangeBuilderImpl implements ChangeBuilder {
var selection = _selection;
if (selection != null) {
change.selection = selection;
var selectionRange = _selectionRange;
if (selectionRange != null) {
change.selectionLength = selectionRange.length;
}
}
return change;
}

View file

@ -867,6 +867,7 @@ final Matcher isRequestErrorCode = MatchesEnum('RequestErrorCode', [
/// "edits": List<SourceFileEdit>
/// "linkedEditGroups": List<LinkedEditGroup>
/// "selection": optional Position
/// "selectionLength": optional int
/// "id": optional String
/// }
final Matcher isSourceChange =
@ -876,6 +877,7 @@ final Matcher isSourceChange =
'linkedEditGroups': isListOf(isLinkedEditGroup)
}, optionalFields: {
'selection': isPosition,
'selectionLength': isInt,
'id': isString
}));

View file

@ -6,7 +6,7 @@
</head>
<body>
<h1>Common Types</h1>
<version>1.4.4</version>
<version>1.4.5</version>
<p>
This document contains a specification of the types that are common between
the analysis server wire protocol and the analysis server plugin wire
@ -1566,6 +1566,13 @@
applied.
</p>
</field>
<field name="selectionLength" optional="true">
<ref>int</ref>
<p>
The length of the selection (starting at Position) that should be selected after
the edits have been applied.
</p>
</field>
<field name="id" optional="true">
<ref>String</ref>
<p>