[analyzer] Suggest arg names in front of positionals

Fixes https://github.com/dart-lang/sdk/issues/40654.

Change-Id: I329d8b4af9371e8d61c4044bb003f9c22cd72c41
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/182783
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Danny Tuppeny 2021-03-04 18:21:19 +00:00 committed by commit-bot@chromium.org
parent 567d745312
commit 78dd7a4bb3
17 changed files with 438 additions and 42 deletions

View file

@ -109,7 +109,7 @@ a:focus, a:hover {
<body>
<h1>Analysis Server API Specification</h1>
<h1 style="color:#999999">Version
1.32.4
1.32.5
</h1>
<p>
This document contains a specification of the API provided by the
@ -236,6 +236,13 @@ a:focus, a:hover {
ignoring the item or treating it with some default/fallback handling.
</p>
<h3>Changelog</h3>
<h4>1.32.5</h4>
<ul>
<li>Added optional <tt>replacementOffset</tt> and <tt>replacementLength</tt> on
<tt>CompletionSuggestion</tt> to support different per-completion text replacement
ranges (for example when inserting names for arguments, a different range may be
supplied than for completions that are not name labels).</li>
</ul>
<h4>1.32.4</h4>
<ul>
<li>Added <tt>ElementKind.TYPE_ALIAS</tt> and <tt>HighlightRegionType.TYPE_ALIAS</tt>
@ -3483,6 +3490,30 @@ a:focus, a:hover {
is only defined if the displayed text should be different than the
completion. Otherwise it is omitted.
</p>
</dd><dt class="field"><b>replacementOffset: int<span style="color:#999999"> (optional)</span></b></dt><dd>
<p>
The offset of the start of the text to be
replaced. If supplied, this should be used in
preference to the offset provided on the
containing completion results.
This value may be provided independently of
replacementLength (for example if only one
differs from the completion result value).
</p>
</dd><dt class="field"><b>replacementLength: int<span style="color:#999999"> (optional)</span></b></dt><dd>
<p>
The length of the text to be replaced.
If supplied, this should be used in preference
to the offset provided on the
containing completion results.
This value may be provided independently of
replacementOffset (for example if only one
differs from the completion result value).
</p>
</dd><dt class="field"><b>selectionOffset: int</b></dt><dd>
<p>

View file

@ -6,7 +6,7 @@
// To regenerate the file, use the script
// "pkg/analysis_server/tool/spec/generate_files".
const String PROTOCOL_VERSION = '1.32.4';
const String PROTOCOL_VERSION = '1.32.5';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';

View file

@ -272,29 +272,42 @@ class CompletionHandler
return cancelled();
}
final results = serverSuggestions
.map(
(item) => toCompletionItem(
completionCapabilities,
clientSupportedCompletionKinds,
unit.lineInfo,
item,
completionRequest.replacementOffset,
insertLength,
completionRequest.replacementLength,
// TODO(dantup): Including commit characters in every completion
// increases the payload size. The LSP spec is ambigious
// about how this should be handled (and VS Code requires it) but
// this should be removed (or made conditional based on a capability)
// depending on how the spec is updated.
// https://github.com/microsoft/vscode-languageserver-node/issues/673
includeCommitCharacters:
server.clientConfiguration.previewCommitCharacters,
completeFunctionCalls:
server.clientConfiguration.completeFunctionCalls,
),
)
.toList();
final results = serverSuggestions.map(
(item) {
var itemReplacementOffset =
item.replacementOffset ?? completionRequest.replacementOffset;
var itemReplacementLength =
item.replacementLength ?? completionRequest.replacementLength;
var itemInsertLength = insertLength;
// Recompute the insert length if it may be affected by the above.
if (item.replacementOffset != null ||
item.replacementLength != null) {
itemInsertLength = _computeInsertLength(
offset, itemReplacementOffset, itemInsertLength);
}
return toCompletionItem(
completionCapabilities,
clientSupportedCompletionKinds,
unit.lineInfo,
item,
itemReplacementOffset,
itemInsertLength,
itemReplacementLength,
// TODO(dantup): Including commit characters in every completion
// increases the payload size. The LSP spec is ambigious
// about how this should be handled (and VS Code requires it) but
// this should be removed (or made conditional based on a capability)
// depending on how the spec is updated.
// https://github.com/microsoft/vscode-languageserver-node/issues/673
includeCommitCharacters:
server.clientConfiguration.previewCommitCharacters,
completeFunctionCalls:
server.clientConfiguration.completeFunctionCalls,
);
},
).toList();
// Now compute items in suggestion sets.
var includedSuggestionSets = <IncludedSuggestionSet>[];

View file

@ -45,31 +45,34 @@ class ArgListContributor extends DartCompletionContributor {
}
void _addDefaultParamSuggestions(Iterable<ParameterElement> parameters,
[bool appendComma = false]) {
{bool appendComma = false, int replacementLength}) {
var appendColon = !_isInNamedExpression();
var namedArgs = _namedArgs();
for (var parameter in parameters) {
if (parameter.isNamed) {
_addNamedParameterSuggestion(
namedArgs, parameter, appendColon, appendComma);
namedArgs, parameter, appendColon, appendComma,
replacementLength: replacementLength);
}
}
}
void _addNamedParameterSuggestion(List<String> namedArgs,
ParameterElement parameter, bool appendColon, bool appendComma) {
ParameterElement parameter, bool appendColon, bool appendComma,
{int replacementLength}) {
var name = parameter.name;
var willReplace =
request.completionPreference == CompletionPreference.replace &&
request.replacementRange.length > 0;
(replacementLength ?? request.replacementRange.length) > 0;
if (name != null && name.isNotEmpty && !namedArgs.contains(name)) {
builder.suggestNamedArgument(parameter,
// If there's a replacement length and the preference is to replace,
// we should not include colons/commas.
appendColon: appendColon && !willReplace,
appendComma: appendComma && !willReplace);
appendComma: appendComma && !willReplace,
replacementLength: replacementLength);
}
}
@ -80,20 +83,40 @@ class ArgListContributor extends DartCompletionContributor {
var requiredParam =
parameters.where((ParameterElement p) => p.isRequiredPositional);
var requiredCount = requiredParam.length;
// When inserted named args, if there is a replacement starting at the caret
// it will be an identifier that should not be replaced if the completion
// preference is to insert. In this case, override the replacement length
// to 0.
// TODO(jwren): _isAppendingToArgList can be split into two cases (with and
// without preceded), then _isAppendingToArgList,
// _isInsertingToArgListWithNoSynthetic and
// _isInsertingToArgListWithSynthetic could be formatted into a single
// method which returns some enum with 5+ cases.
if (_isEditingNamedArgLabel() || _isAppendingToArgList()) {
if (_isEditingNamedArgLabel() ||
_isAppendingToArgList() ||
_isAddingLabelToPositional()) {
if (requiredCount == 0 || requiredCount < _argCount()) {
// If there's a replacement range that starts at the caret, it will be
// for an identifier that is not the named label and therefore it should
// not be replaced.
var replacementLength =
request.offset == request.target.entity.offset &&
request.replacementRange.length != 0
? 0
: null;
var addTrailingComma = !_isFollowedByAComma() && _isInFlutterCreation();
_addDefaultParamSuggestions(parameters, addTrailingComma);
_addDefaultParamSuggestions(parameters,
appendComma: addTrailingComma,
replacementLength: replacementLength);
}
} else if (_isInsertingToArgListWithNoSynthetic()) {
_addDefaultParamSuggestions(parameters, true);
_addDefaultParamSuggestions(parameters, appendComma: true);
} else if (_isInsertingToArgListWithSynthetic()) {
_addDefaultParamSuggestions(parameters, !_isFollowedByAComma());
_addDefaultParamSuggestions(parameters,
appendComma: !_isFollowedByAComma());
} else {
var argument = request.target.containingNode;
if (argument is NamedExpression) {
@ -126,6 +149,18 @@ class ArgListContributor extends DartCompletionContributor {
}
}
/// Return `true` if the caret is preceeding an arg where a name could be added
/// (turning a positional arg into a named arg).
bool _isAddingLabelToPositional() {
if (argumentList != null) {
var entity = request.target.entity;
if (entity is! NamedExpression && request.offset <= entity.offset) {
return true;
}
}
return false;
}
/// Return `true` if the completion target is at the end of the list of
/// arguments.
bool _isAppendingToArgList() {

View file

@ -654,7 +654,9 @@ class SuggestionBuilder {
/// [appendComma] is `true` then a comma will be included at the end of the
/// completion text.
void suggestNamedArgument(ParameterElement parameter,
{@required bool appendColon, @required bool appendComma}) {
{@required bool appendColon,
@required bool appendComma,
int replacementLength}) {
var name = parameter.name;
var type = parameter.type?.getDisplayString(
withNullability: request.libraryElement.isNonNullableByDefault);
@ -705,7 +707,8 @@ class SuggestionBuilder {
false,
false,
parameterName: name,
parameterType: type);
parameterType: type,
replacementLength: replacementLength);
if (parameter is FieldFormalParameterElement) {
_setDocumentation(suggestion, parameter);
suggestion.element = convertElement(parameter);

View file

@ -52,6 +52,56 @@ class A {
expect(suggestions, hasLength(2));
}
Future<void>
test_ArgumentList_function_named_fromPositionalNumeric_withoutSpace() async {
addTestFile('void f(int a, {int b = 0}) {}'
'void g() { f(2, ^3); }');
await getSuggestions();
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'b: ');
expect(suggestions, hasLength(1));
// Ensure we don't try to replace the following arg.
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
}
Future<void>
test_ArgumentList_function_named_fromPositionalNumeric_withSpace() async {
addTestFile('void f(int a, {int b = 0}) {}'
'void g() { f(2, ^ 3); }');
await getSuggestions();
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'b: ');
expect(suggestions, hasLength(1));
// Ensure we don't try to replace the following arg.
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
}
Future<void>
test_ArgumentList_function_named_fromPositionalVariable_withoutSpace() async {
addTestFile('void f(int a, {int b = 0}) {}'
'var foo = 1;'
'void g() { f(2, ^foo); }');
await getSuggestions();
expect(suggestions, hasLength(1));
// The named arg "b: " should not replace anything.
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'b: ',
replacementOffset: null, replacementLength: 0);
}
Future<void>
test_ArgumentList_function_named_fromPositionalVariable_withSpace() async {
addTestFile('void f(int a, {int b = 0}) {}'
'var foo = 1;'
'void g() { f(2, ^ foo); }');
await getSuggestions();
assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'b: ');
expect(suggestions, hasLength(1));
// Ensure we don't try to replace the following arg.
expect(replacementOffset, equals(completionOffset));
expect(replacementLength, equals(0));
}
Future<void> test_ArgumentList_imported_function_named_param() async {
addTestFile('main() { int.parse("16", ^);}');
await getSuggestions();

View file

@ -43,6 +43,8 @@ class AbstractCompletionDomainTest extends AbstractAnalysisTest {
{bool isDeprecated = false,
bool isPotential = false,
int selectionOffset,
int replacementOffset,
int replacementLength,
ElementKind elementKind}) {
CompletionSuggestion cs;
suggestions.forEach((s) {
@ -70,6 +72,8 @@ class AbstractCompletionDomainTest extends AbstractAnalysisTest {
expect(cs.kind, equals(kind));
expect(cs.selectionOffset, selectionOffset ?? completion.length);
expect(cs.selectionLength, equals(0));
expect(cs.replacementOffset, equals(replacementOffset));
expect(cs.replacementLength, equals(replacementLength));
expect(cs.isDeprecated, equals(isDeprecated));
expect(cs.isPotential, equals(isPotential));
}

View file

@ -250,6 +250,8 @@ final Matcher isCompletionService =
/// "relevance": int
/// "completion": String
/// "displayText": optional String
/// "replacementOffset": optional int
/// "replacementLength": optional int
/// "selectionOffset": int
/// "selectionLength": int
/// "isDeprecated": bool
@ -279,6 +281,8 @@ final Matcher isCompletionSuggestion =
'isPotential': isBool
}, optionalFields: {
'displayText': isString,
'replacementOffset': isInt,
'replacementLength': isInt,
'docSummary': isString,
'docComplete': isString,
'declaringType': isString,

View file

@ -709,16 +709,19 @@ class _MyWidgetState extends State<MyWidget> {
}) async {
final content = '''
class A { const A({int argOne, int argTwo}); }
final varOne = '';
$code
main() { }
''';
final expectedReplaced = '''
class A { const A({int argOne, int argTwo}); }
final varOne = '';
$expectedReplace
main() { }
''';
final expectedInserted = '''
class A { const A({int argOne, int argTwo}); }
final varOne = '';
$expectedInsert
main() { }
''';
@ -743,7 +746,23 @@ main() { }
expectedInsert: '@A(argTwoargOne: 1)',
);
// Inside the identifier also should be expected to replace.
// When adding a name to an existing value, it should always insert.
await check(
'@A(^1)',
'argOne: ',
expectedReplace: '@A(argOne: 1)',
expectedInsert: '@A(argOne: 1)',
);
// When adding a name to an existing variable, it should always insert.
await check(
'@A(argOne: 1, ^varOne)',
'argTwo: ',
expectedReplace: '@A(argOne: 1, argTwo: varOne)',
expectedInsert: '@A(argOne: 1, argTwo: varOne)',
);
// // Inside the identifier also should be expected to replace.
await check(
'@A(arg^One: 1)',
'argTwo',

View file

@ -59,6 +59,21 @@ public class CompletionSuggestion {
*/
private final String displayText;
/**
* The offset of the start of the text to be replaced. If supplied, this should be used in
* preference to the offset provided on the containing completion results. This value may be
* provided independently of replacementLength (for example if only one differs from the completion
* result value).
*/
private final Integer replacementOffset;
/**
* The length of the text to be replaced. If supplied, this should be used in preference to the
* offset provided on the containing completion results. This value may be provided independently
* of replacementOffset (for example if only one differs from the completion result value).
*/
private final Integer replacementLength;
/**
* The offset, relative to the beginning of the completion, of where the selection should be placed
* after insertion.
@ -163,11 +178,13 @@ public class CompletionSuggestion {
/**
* Constructor for {@link CompletionSuggestion}.
*/
public CompletionSuggestion(String kind, int relevance, String completion, String displayText, int selectionOffset, int selectionLength, boolean isDeprecated, boolean isPotential, String docSummary, String docComplete, String declaringType, String defaultArgumentListString, int[] defaultArgumentListTextRanges, Element element, String returnType, List<String> parameterNames, List<String> parameterTypes, Integer requiredParameterCount, Boolean hasNamedParameters, String parameterName, String parameterType) {
public CompletionSuggestion(String kind, int relevance, String completion, String displayText, Integer replacementOffset, Integer replacementLength, int selectionOffset, int selectionLength, boolean isDeprecated, boolean isPotential, String docSummary, String docComplete, String declaringType, String defaultArgumentListString, int[] defaultArgumentListTextRanges, Element element, String returnType, List<String> parameterNames, List<String> parameterTypes, Integer requiredParameterCount, Boolean hasNamedParameters, String parameterName, String parameterType) {
this.kind = kind;
this.relevance = relevance;
this.completion = completion;
this.displayText = displayText;
this.replacementOffset = replacementOffset;
this.replacementLength = replacementLength;
this.selectionOffset = selectionOffset;
this.selectionLength = selectionLength;
this.isDeprecated = isDeprecated;
@ -196,6 +213,8 @@ public class CompletionSuggestion {
other.relevance == relevance &&
ObjectUtilities.equals(other.completion, completion) &&
ObjectUtilities.equals(other.displayText, displayText) &&
ObjectUtilities.equals(other.replacementOffset, replacementOffset) &&
ObjectUtilities.equals(other.replacementLength, replacementLength) &&
other.selectionOffset == selectionOffset &&
other.selectionLength == selectionLength &&
other.isDeprecated == isDeprecated &&
@ -222,6 +241,8 @@ public class CompletionSuggestion {
int relevance = jsonObject.get("relevance").getAsInt();
String completion = jsonObject.get("completion").getAsString();
String displayText = jsonObject.get("displayText") == null ? null : jsonObject.get("displayText").getAsString();
Integer replacementOffset = jsonObject.get("replacementOffset") == null ? null : jsonObject.get("replacementOffset").getAsInt();
Integer replacementLength = jsonObject.get("replacementLength") == null ? null : jsonObject.get("replacementLength").getAsInt();
int selectionOffset = jsonObject.get("selectionOffset").getAsInt();
int selectionLength = jsonObject.get("selectionLength").getAsInt();
boolean isDeprecated = jsonObject.get("isDeprecated").getAsBoolean();
@ -239,7 +260,7 @@ public class CompletionSuggestion {
Boolean hasNamedParameters = jsonObject.get("hasNamedParameters") == null ? null : jsonObject.get("hasNamedParameters").getAsBoolean();
String parameterName = jsonObject.get("parameterName") == null ? null : jsonObject.get("parameterName").getAsString();
String parameterType = jsonObject.get("parameterType") == null ? null : jsonObject.get("parameterType").getAsString();
return new CompletionSuggestion(kind, relevance, completion, displayText, selectionOffset, selectionLength, isDeprecated, isPotential, docSummary, docComplete, declaringType, defaultArgumentListString, defaultArgumentListTextRanges, element, returnType, parameterNames, parameterTypes, requiredParameterCount, hasNamedParameters, parameterName, parameterType);
return new CompletionSuggestion(kind, relevance, completion, displayText, replacementOffset, replacementLength, selectionOffset, selectionLength, isDeprecated, isPotential, docSummary, docComplete, declaringType, defaultArgumentListString, defaultArgumentListTextRanges, element, returnType, parameterNames, parameterTypes, requiredParameterCount, hasNamedParameters, parameterName, parameterType);
}
public static List<CompletionSuggestion> fromJsonArray(JsonArray jsonArray) {
@ -389,6 +410,25 @@ public class CompletionSuggestion {
return relevance;
}
/**
* The length of the text to be replaced. If supplied, this should be used in preference to the
* offset provided on the containing completion results. This value may be provided independently
* of replacementOffset (for example if only one differs from the completion result value).
*/
public Integer getReplacementLength() {
return replacementLength;
}
/**
* The offset of the start of the text to be replaced. If supplied, this should be used in
* preference to the offset provided on the containing completion results. This value may be
* provided independently of replacementLength (for example if only one differs from the completion
* result value).
*/
public Integer getReplacementOffset() {
return replacementOffset;
}
/**
* The number of required parameters for the function or method being suggested. This field is
* omitted if the parameterNames field is omitted.
@ -427,6 +467,8 @@ public class CompletionSuggestion {
builder.append(relevance);
builder.append(completion);
builder.append(displayText);
builder.append(replacementOffset);
builder.append(replacementLength);
builder.append(selectionOffset);
builder.append(selectionLength);
builder.append(isDeprecated);
@ -455,6 +497,12 @@ public class CompletionSuggestion {
if (displayText != null) {
jsonObject.addProperty("displayText", displayText);
}
if (replacementOffset != null) {
jsonObject.addProperty("replacementOffset", replacementOffset);
}
if (replacementLength != null) {
jsonObject.addProperty("replacementLength", replacementLength);
}
jsonObject.addProperty("selectionOffset", selectionOffset);
jsonObject.addProperty("selectionLength", selectionLength);
jsonObject.addProperty("isDeprecated", isDeprecated);
@ -525,6 +573,10 @@ public class CompletionSuggestion {
builder.append(completion + ", ");
builder.append("displayText=");
builder.append(displayText + ", ");
builder.append("replacementOffset=");
builder.append(replacementOffset + ", ");
builder.append("replacementLength=");
builder.append(replacementLength + ", ");
builder.append("selectionOffset=");
builder.append(selectionOffset + ", ");
builder.append("selectionLength=");

View file

@ -7,7 +7,7 @@
<body>
<h1>Analysis Server API Specification</h1>
<h1 style="color:#999999">Version
<version>1.32.4</version>
<version>1.32.5</version>
</h1>
<p>
This document contains a specification of the API provided by the
@ -134,6 +134,13 @@
ignoring the item or treating it with some default/fallback handling.
</p>
<h3>Changelog</h3>
<h4>1.32.5</h4>
<ul>
<li>Added optional <tt>replacementOffset</tt> and <tt>replacementLength</tt> on
<tt>CompletionSuggestion</tt> to support different per-completion text replacement
ranges (for example when inserting names for arguments, a different range may be
supplied than for completions that are not name labels).</li>
</ul>
<h4>1.32.4</h4>
<ul>
<li>Added <tt>ElementKind.TYPE_ALIAS</tt> and <tt>HighlightRegionType.TYPE_ALIAS</tt>

View file

@ -601,6 +601,8 @@ class ChangeContentOverlay implements HasToJson {
/// "relevance": int
/// "completion": String
/// "displayText": optional String
/// "replacementOffset": optional int
/// "replacementLength": optional int
/// "selectionOffset": int
/// "selectionLength": int
/// "isDeprecated": bool
@ -630,6 +632,10 @@ class CompletionSuggestion implements HasToJson {
String _displayText;
int _replacementOffset;
int _replacementLength;
int _selectionOffset;
int _selectionLength;
@ -711,6 +717,36 @@ class CompletionSuggestion implements HasToJson {
_displayText = value;
}
/// The offset of the start of the text to be replaced. If supplied, this
/// should be used in preference to the offset provided on the containing
/// completion results. This value may be provided independently of
/// replacementLength (for example if only one differs from the completion
/// result value).
int get replacementOffset => _replacementOffset;
/// The offset of the start of the text to be replaced. If supplied, this
/// should be used in preference to the offset provided on the containing
/// completion results. This value may be provided independently of
/// replacementLength (for example if only one differs from the completion
/// result value).
set replacementOffset(int value) {
_replacementOffset = value;
}
/// The length of the text to be replaced. If supplied, this should be used
/// in preference to the offset provided on the containing completion
/// results. This value may be provided independently of replacementOffset
/// (for example if only one differs from the completion result value).
int get replacementLength => _replacementLength;
/// The length of the text to be replaced. If supplied, this should be used
/// in preference to the offset provided on the containing completion
/// results. This value may be provided independently of replacementOffset
/// (for example if only one differs from the completion result value).
set replacementLength(int value) {
_replacementLength = value;
}
/// The offset, relative to the beginning of the completion, of where the
/// selection should be placed after insertion.
int get selectionOffset => _selectionOffset;
@ -904,6 +940,8 @@ class CompletionSuggestion implements HasToJson {
bool isDeprecated,
bool isPotential,
{String displayText,
int replacementOffset,
int replacementLength,
String docSummary,
String docComplete,
String declaringType,
@ -921,6 +959,8 @@ class CompletionSuggestion implements HasToJson {
this.relevance = relevance;
this.completion = completion;
this.displayText = displayText;
this.replacementOffset = replacementOffset;
this.replacementLength = replacementLength;
this.selectionOffset = selectionOffset;
this.selectionLength = selectionLength;
this.isDeprecated = isDeprecated;
@ -970,6 +1010,16 @@ class CompletionSuggestion implements HasToJson {
displayText = jsonDecoder.decodeString(
jsonPath + '.displayText', json['displayText']);
}
int replacementOffset;
if (json.containsKey('replacementOffset')) {
replacementOffset = jsonDecoder.decodeInt(
jsonPath + '.replacementOffset', json['replacementOffset']);
}
int replacementLength;
if (json.containsKey('replacementLength')) {
replacementLength = jsonDecoder.decodeInt(
jsonPath + '.replacementLength', json['replacementLength']);
}
int selectionOffset;
if (json.containsKey('selectionOffset')) {
selectionOffset = jsonDecoder.decodeInt(
@ -1070,6 +1120,8 @@ class CompletionSuggestion implements HasToJson {
return CompletionSuggestion(kind, relevance, completion, selectionOffset,
selectionLength, isDeprecated, isPotential,
displayText: displayText,
replacementOffset: replacementOffset,
replacementLength: replacementLength,
docSummary: docSummary,
docComplete: docComplete,
declaringType: declaringType,
@ -1097,6 +1149,12 @@ class CompletionSuggestion implements HasToJson {
if (displayText != null) {
result['displayText'] = displayText;
}
if (replacementOffset != null) {
result['replacementOffset'] = replacementOffset;
}
if (replacementLength != null) {
result['replacementLength'] = replacementLength;
}
result['selectionOffset'] = selectionOffset;
result['selectionLength'] = selectionLength;
result['isDeprecated'] = isDeprecated;
@ -1153,6 +1211,8 @@ class CompletionSuggestion implements HasToJson {
relevance == other.relevance &&
completion == other.completion &&
displayText == other.displayText &&
replacementOffset == other.replacementOffset &&
replacementLength == other.replacementLength &&
selectionOffset == other.selectionOffset &&
selectionLength == other.selectionLength &&
isDeprecated == other.isDeprecated &&
@ -1184,6 +1244,8 @@ class CompletionSuggestion implements HasToJson {
hash = JenkinsSmiHash.combine(hash, relevance.hashCode);
hash = JenkinsSmiHash.combine(hash, completion.hashCode);
hash = JenkinsSmiHash.combine(hash, displayText.hashCode);
hash = JenkinsSmiHash.combine(hash, replacementOffset.hashCode);
hash = JenkinsSmiHash.combine(hash, replacementLength.hashCode);
hash = JenkinsSmiHash.combine(hash, selectionOffset.hashCode);
hash = JenkinsSmiHash.combine(hash, selectionLength.hashCode);
hash = JenkinsSmiHash.combine(hash, isDeprecated.hashCode);

View file

@ -6,7 +6,7 @@
// To regenerate the file, use the script
// "pkg/analysis_server/tool/spec/generate_files".
const String PROTOCOL_VERSION = '1.32.4';
const String PROTOCOL_VERSION = '1.32.5';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';

View file

@ -1015,6 +1015,30 @@ a:focus, a:hover {
is only defined if the displayed text should be different than the
completion. Otherwise it is omitted.
</p>
</dd><dt class="field"><b>replacementOffset: int<span style="color:#999999"> (optional)</span></b></dt><dd>
<p>
The offset of the start of the text to be
replaced. If supplied, this should be used in
preference to the offset provided on the
containing completion results.
This value may be provided independently of
replacementLength (for example if only one
differs from the completion result value).
</p>
</dd><dt class="field"><b>replacementLength: int<span style="color:#999999"> (optional)</span></b></dt><dd>
<p>
The length of the text to be replaced.
If supplied, this should be used in preference
to the offset provided on the
containing completion results.
This value may be provided independently of
replacementOffset (for example if only one
differs from the completion result value).
</p>
</dd><dt class="field"><b>selectionOffset: int</b></dt><dd>
<p>

View file

@ -601,6 +601,8 @@ class ChangeContentOverlay implements HasToJson {
/// "relevance": int
/// "completion": String
/// "displayText": optional String
/// "replacementOffset": optional int
/// "replacementLength": optional int
/// "selectionOffset": int
/// "selectionLength": int
/// "isDeprecated": bool
@ -630,6 +632,10 @@ class CompletionSuggestion implements HasToJson {
String _displayText;
int _replacementOffset;
int _replacementLength;
int _selectionOffset;
int _selectionLength;
@ -711,6 +717,36 @@ class CompletionSuggestion implements HasToJson {
_displayText = value;
}
/// The offset of the start of the text to be replaced. If supplied, this
/// should be used in preference to the offset provided on the containing
/// completion results. This value may be provided independently of
/// replacementLength (for example if only one differs from the completion
/// result value).
int get replacementOffset => _replacementOffset;
/// The offset of the start of the text to be replaced. If supplied, this
/// should be used in preference to the offset provided on the containing
/// completion results. This value may be provided independently of
/// replacementLength (for example if only one differs from the completion
/// result value).
set replacementOffset(int value) {
_replacementOffset = value;
}
/// The length of the text to be replaced. If supplied, this should be used
/// in preference to the offset provided on the containing completion
/// results. This value may be provided independently of replacementOffset
/// (for example if only one differs from the completion result value).
int get replacementLength => _replacementLength;
/// The length of the text to be replaced. If supplied, this should be used
/// in preference to the offset provided on the containing completion
/// results. This value may be provided independently of replacementOffset
/// (for example if only one differs from the completion result value).
set replacementLength(int value) {
_replacementLength = value;
}
/// The offset, relative to the beginning of the completion, of where the
/// selection should be placed after insertion.
int get selectionOffset => _selectionOffset;
@ -904,6 +940,8 @@ class CompletionSuggestion implements HasToJson {
bool isDeprecated,
bool isPotential,
{String displayText,
int replacementOffset,
int replacementLength,
String docSummary,
String docComplete,
String declaringType,
@ -921,6 +959,8 @@ class CompletionSuggestion implements HasToJson {
this.relevance = relevance;
this.completion = completion;
this.displayText = displayText;
this.replacementOffset = replacementOffset;
this.replacementLength = replacementLength;
this.selectionOffset = selectionOffset;
this.selectionLength = selectionLength;
this.isDeprecated = isDeprecated;
@ -970,6 +1010,16 @@ class CompletionSuggestion implements HasToJson {
displayText = jsonDecoder.decodeString(
jsonPath + '.displayText', json['displayText']);
}
int replacementOffset;
if (json.containsKey('replacementOffset')) {
replacementOffset = jsonDecoder.decodeInt(
jsonPath + '.replacementOffset', json['replacementOffset']);
}
int replacementLength;
if (json.containsKey('replacementLength')) {
replacementLength = jsonDecoder.decodeInt(
jsonPath + '.replacementLength', json['replacementLength']);
}
int selectionOffset;
if (json.containsKey('selectionOffset')) {
selectionOffset = jsonDecoder.decodeInt(
@ -1070,6 +1120,8 @@ class CompletionSuggestion implements HasToJson {
return CompletionSuggestion(kind, relevance, completion, selectionOffset,
selectionLength, isDeprecated, isPotential,
displayText: displayText,
replacementOffset: replacementOffset,
replacementLength: replacementLength,
docSummary: docSummary,
docComplete: docComplete,
declaringType: declaringType,
@ -1097,6 +1149,12 @@ class CompletionSuggestion implements HasToJson {
if (displayText != null) {
result['displayText'] = displayText;
}
if (replacementOffset != null) {
result['replacementOffset'] = replacementOffset;
}
if (replacementLength != null) {
result['replacementLength'] = replacementLength;
}
result['selectionOffset'] = selectionOffset;
result['selectionLength'] = selectionLength;
result['isDeprecated'] = isDeprecated;
@ -1153,6 +1211,8 @@ class CompletionSuggestion implements HasToJson {
relevance == other.relevance &&
completion == other.completion &&
displayText == other.displayText &&
replacementOffset == other.replacementOffset &&
replacementLength == other.replacementLength &&
selectionOffset == other.selectionOffset &&
selectionLength == other.selectionLength &&
isDeprecated == other.isDeprecated &&
@ -1184,6 +1244,8 @@ class CompletionSuggestion implements HasToJson {
hash = JenkinsSmiHash.combine(hash, relevance.hashCode);
hash = JenkinsSmiHash.combine(hash, completion.hashCode);
hash = JenkinsSmiHash.combine(hash, displayText.hashCode);
hash = JenkinsSmiHash.combine(hash, replacementOffset.hashCode);
hash = JenkinsSmiHash.combine(hash, replacementLength.hashCode);
hash = JenkinsSmiHash.combine(hash, selectionOffset.hashCode);
hash = JenkinsSmiHash.combine(hash, selectionLength.hashCode);
hash = JenkinsSmiHash.combine(hash, isDeprecated.hashCode);

View file

@ -119,6 +119,8 @@ final Matcher isChangeContentOverlay = LazyMatcher(() => MatchesJsonObject(
/// "relevance": int
/// "completion": String
/// "displayText": optional String
/// "replacementOffset": optional int
/// "replacementLength": optional int
/// "selectionOffset": int
/// "selectionLength": int
/// "isDeprecated": bool
@ -148,6 +150,8 @@ final Matcher isCompletionSuggestion =
'isPotential': isBool
}, optionalFields: {
'displayText': isString,
'replacementOffset': isInt,
'replacementLength': isInt,
'docSummary': isString,
'docComplete': isString,
'declaringType': isString,

View file

@ -6,7 +6,7 @@
</head>
<body>
<h1>Common Types</h1>
<version>1.4.2</version>
<version>1.4.3</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
@ -221,6 +221,32 @@
completion. Otherwise it is omitted.
</p>
</field>
<field name="replacementOffset" optional="true">
<ref>int</ref>
<p>
The offset of the start of the text to be
replaced. If supplied, this should be used in
preference to the offset provided on the
containing completion results.
This value may be provided independently of
replacementLength (for example if only one
differs from the completion result value).
</p>
</field>
<field name="replacementLength" optional="true">
<ref>int</ref>
<p>
The length of the text to be replaced.
If supplied, this should be used in preference
to the offset provided on the
containing completion results.
This value may be provided independently of
replacementOffset (for example if only one
differs from the completion result value).
</p>
</field>
<field name="selectionOffset">
<ref>int</ref>
<p>