@@ -12,7 +12,7 @@ import 'package:analysis_server/src/lsp/constants.dart';
12
12
import 'package:analysis_server/src/lsp/handlers/handlers.dart' ;
13
13
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart' ;
14
14
import 'package:analysis_server/src/lsp/mapping.dart' ;
15
- import 'package:analysis_server/src/protocol_server.dart' ;
15
+ import 'package:analysis_server/src/protocol_server.dart' hide Position ;
16
16
import 'package:analysis_server/src/services/correction/assist.dart' ;
17
17
import 'package:analysis_server/src/services/correction/assist_internal.dart' ;
18
18
import 'package:analysis_server/src/services/correction/bulk_fix_processor.dart' ;
@@ -92,6 +92,21 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
92
92
});
93
93
}
94
94
95
+ /// Creates a comparer for [CodeActions] that compares the column distance from [pos] .
96
+ Function (CodeAction a, CodeAction b) _codeActionColumnDistanceComparer (
97
+ Position pos) {
98
+ Position posOf (CodeAction action) => action.diagnostics.isNotEmpty
99
+ ? action.diagnostics.first.range.start
100
+ : pos;
101
+
102
+ return (a, b) => _columnDistance (posOf (a), pos)
103
+ .compareTo (_columnDistance (posOf (b), pos));
104
+ }
105
+
106
+ /// Returns the distance (in columns, ignoring lines) between two positions.
107
+ int _columnDistance (Position a, Position b) =>
108
+ (a.character - b.character).abs ();
109
+
95
110
/// Wraps a command in a CodeAction if the client supports it so that a
96
111
/// CodeActionKind can be supplied.
97
112
Either2 <Command , CodeAction > _commandOrCodeAction (
@@ -152,33 +167,60 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
152
167
);
153
168
}
154
169
155
- /// Dedupes actions that perform the same edit and merge their diagnostics
156
- /// together. This avoids duplicates where there are multiple errors on
157
- /// the same line that have the same fix (for example importing a
158
- /// library that fixes multiple unresolved types).
159
- List <CodeAction > _dedupeActions (Iterable <CodeAction > actions) {
160
- final groups = groupBy (actions, (CodeAction action) => action.edit);
161
- return groups.keys.map ((edit) {
162
- final first = groups[edit].first;
163
- // Avoid constructing new CodeActions if there was only one in this group.
164
- if (groups[edit].length == 1 ) {
165
- return first;
170
+ /// Dedupes/merges actions that have the same title, selecting the one nearest [pos] .
171
+ ///
172
+ /// If actions perform the same edit/command, their diagnostics will be merged
173
+ /// together. Otherwise, the additional accounts are just dropped.
174
+ ///
175
+ /// The first diagnostic for an action is used to determine the position (using
176
+ /// its `start` ). If there is no diagnostic, it will be treated as being at [pos] .
177
+ ///
178
+ /// If multiple actions have the same position, one will arbitrarily be chosen.
179
+ List <CodeAction > _dedupeActions (Iterable <CodeAction > actions, Position pos) {
180
+ final groups = groupBy (actions, (CodeAction action) => action.title);
181
+ return groups.keys.map ((title) {
182
+ final actions = groups[title];
183
+
184
+ // If there's only one in the group, just return it.
185
+ if (actions.length == 1 ) {
186
+ return actions.single;
166
187
}
188
+
189
+ // Otherwise, find the action nearest to the caret.
190
+ actions.sort (_codeActionColumnDistanceComparer (pos));
191
+ final first = actions.first;
192
+
193
+ // Get any actions with the same fix (edit/command) for merging diagnostics.
194
+ final others = actions.skip (1 ).where (
195
+ (other) =>
196
+ // Compare either edits or commands based on which the selected action has.
197
+ first.edit != null
198
+ ? first.edit == other.edit
199
+ : first.command != null
200
+ ? first.command == other.command
201
+ : false ,
202
+ );
203
+
167
204
// Build a new CodeAction that merges the diagnostics from each same
168
205
// code action onto a single one.
169
206
return CodeAction (
170
- title: first.title,
171
- kind: first.kind,
172
- // Merge diagnostics from all of the CodeActions.
173
- diagnostics: groups[edit].expand ((r) => r.diagnostics).toList (),
174
- edit: first.edit,
175
- command: first.command);
207
+ title: first.title,
208
+ kind: first.kind,
209
+ // Merge diagnostics from all of the matching CodeActions.
210
+ diagnostics: [
211
+ ...? first.diagnostics,
212
+ for (final other in others) ...? other.diagnostics,
213
+ ],
214
+ edit: first.edit,
215
+ command: first.command,
216
+ );
176
217
}).toList ();
177
218
}
178
219
179
220
Future <List <Either2 <Command , CodeAction >>> _getAssistActions (
180
221
HashSet <CodeActionKind > clientSupportedCodeActionKinds,
181
222
bool clientSupportsLiteralCodeActions,
223
+ Range range,
182
224
int offset,
183
225
int length,
184
226
ResolvedUnitResult unit,
@@ -201,7 +243,8 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
201
243
final assists = await processor.compute ();
202
244
assists.sort (Assist .SORT_BY_RELEVANCE );
203
245
204
- final assistActions = _dedupeActions (assists.map (_createAssistAction));
246
+ final assistActions =
247
+ _dedupeActions (assists.map (_createAssistAction), range.start);
205
248
206
249
return assistActions
207
250
.map ((action) => Either2 <Command , CodeAction >.t2 (action))
@@ -228,7 +271,7 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
228
271
final results = await Future .wait ([
229
272
_getSourceActions (
230
273
kinds, supportsLiterals, supportsWorkspaceApplyEdit, path),
231
- _getAssistActions (kinds, supportsLiterals, offset, length, unit),
274
+ _getAssistActions (kinds, supportsLiterals, range, offset, length, unit),
232
275
_getRefactorActions (kinds, supportsLiterals, path, offset, length, unit),
233
276
_getFixActions (
234
277
kinds, supportsLiterals, supportedDiagnosticTags, range, unit),
@@ -328,7 +371,7 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
328
371
// Append all fix-alls to the very end.
329
372
codeActions.addAll (fixAllCodeActions);
330
373
331
- final dedupedActions = _dedupeActions (codeActions);
374
+ final dedupedActions = _dedupeActions (codeActions, range.start );
332
375
333
376
return dedupedActions
334
377
.map ((action) => Either2 <Command , CodeAction >.t2 (action))
0 commit comments