-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathclient_configuration.dart
334 lines (293 loc) · 12.6 KB
/
client_configuration.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/src/computer/computer_documentation.dart';
import 'package:collection/collection.dart';
import 'package:path/path.dart' as path;
/// Client configuration for Dart CodeLenses.
///
/// Defaults are set per-CodeLens but the user can override these by enabling
/// all, disabling all, or setting their own preferences.
///
/// ```js
/// "dart.codeLens": true, // Enables all CodeLens
///
/// "dart.codeLens": false, // Disables all CodeLens
///
/// "dart.codeLens": {
/// "augmented": false, // Disables augmented but otherwise keeps defaults.
/// }
/// ```
class LspClientCodeLensConfiguration {
final bool? _userOverride;
final Map<String, Object?>? _codeLensSettings;
LspClientCodeLensConfiguration(Object? userPreference)
: _userOverride = userPreference is bool ? userPreference : null,
_codeLensSettings =
userPreference is Map<String, Object?> ? userPreference : null;
/// Whether the "Go to Augmentation" CodeLens is enabled.
bool get augmentation => _getUserPreference('augmentation', true);
/// Whether the "Go to Augmented" CodeLens is enabled.
bool get augmented => _getUserPreference('augmented', true);
bool _getUserPreference(String name, bool defaultValue) {
if (_userOverride != null) {
return _userOverride;
}
return defaultValue
// Default true, enable if not explicit false.
? (_codeLensSettings?[name] != false)
// Default false, enable only if explicit true.
: (_codeLensSettings?[name] == true);
}
}
/// Provides access to both global and resource-specific client configuration.
///
/// Resource-specific config is currently only supported at the WorkspaceFolder
/// level so when looking up config for a resource, the nearest WorkspaceFolders
/// config will be used.
///
/// Settings prefixed with 'preview' are things that may not be completed but
/// may be exposed to users as something they can try out.
///
/// Settings prefixed with 'experimental' are things that may be incomplete or
/// still in development and users are not encouraged to try (but may be useful
/// for Dart developers to enable for development/testing).
class LspClientConfiguration {
final path.Context pathContext;
/// Global settings for the workspace.
///
/// Used as a fallback for resource settings if no specific config is found
/// for the resource.
LspGlobalClientConfiguration _globalSettings = LspGlobalClientConfiguration(
{},
);
/// Settings for each resource.
///
/// Keys are string paths without trailing path separators (eg. 'C:\foo').
final Map<String, LspResourceClientConfiguration> _resourceSettings =
<String, LspResourceClientConfiguration>{};
/// Pattern for stripping trailing slashes that may be been provided by the
/// client (in WorkspaceFolder URIs) for consistent comparisons.
final _trailingSlashPattern = RegExp(r'[\/]+$');
LspClientConfiguration(this.pathContext);
/// Returns the global configuration for the whole workspace.
LspGlobalClientConfiguration get global => _globalSettings;
/// Returns whether or not the provided new configuration changes any values
/// that would affect analysis results.
bool affectsAnalysisResults(LspGlobalClientConfiguration otherConfig) {
// Check whether `TODO` settings have changed.
var oldFlag = _globalSettings.showAllTodos;
var newFlag = otherConfig.showAllTodos;
var oldTypes = _globalSettings.showTodoTypes;
var newTypes = otherConfig.showTodoTypes;
return newFlag != oldFlag ||
!const SetEquality().equals(oldTypes, newTypes);
}
/// Returns whether or not the provided new configuration changes any values
/// that would require analysis roots to be updated.
bool affectsAnalysisRoots(LspGlobalClientConfiguration otherConfig) {
var oldExclusions = _globalSettings.analysisExcludedFolders;
var newExclusions = otherConfig.analysisExcludedFolders;
return !const ListEquality().equals(oldExclusions, newExclusions);
}
/// Returns config for a given resource.
///
/// Because we only support config at the WorkspaceFolder level, this is done
/// by finding the nearest WorkspaceFolder to [resourcePath] and using config
/// for that.
///
/// If no specific config is available, returns [global].
LspResourceClientConfiguration forResource(String resourcePath) {
var workspaceFolder = _getWorkspaceFolderPath(resourcePath);
if (workspaceFolder == null) {
return _globalSettings;
}
return _resourceSettings[_normaliseFolderPath(workspaceFolder)] ??
_globalSettings;
}
/// Replaces all previously known configuration with updated values from the
/// client.
void replace(
Map<String, Object?> globalConfig,
Map<String, Map<String, Object?>> workspaceFolderConfig,
) {
_globalSettings = LspGlobalClientConfiguration(globalConfig);
_resourceSettings
..clear()
..addAll(
workspaceFolderConfig.map(
(key, value) => MapEntry(
_normaliseFolderPath(key),
LspResourceClientConfiguration(value, _globalSettings),
),
),
);
}
/// Gets the path for the WorkspaceFolder closest to [resourcePath].
String? _getWorkspaceFolderPath(String resourcePath) {
var candidates =
_resourceSettings.keys
.where(
(wfPath) =>
wfPath == _normaliseFolderPath(resourcePath) ||
pathContext.isWithin(wfPath, resourcePath),
)
.toList();
candidates.sort((a, b) => -a.length.compareTo(b.length));
return candidates.firstOrNull;
}
/// Normalises a folder path to never have a trailing path separator.
String _normaliseFolderPath(String filePath) =>
filePath.replaceAll(_trailingSlashPattern, '');
}
/// Wraps the client (editor) configuration to provide stronger typing and
/// handling of default values where a setting has not been supplied.
///
/// Settings in this class are only allowed to be configured at the workspace
/// level (they will be ignored at the resource level).
class LspGlobalClientConfiguration extends LspResourceClientConfiguration {
late final codeLens = LspClientCodeLensConfiguration(_settings['codeLens']);
LspGlobalClientConfiguration(Map<String, Object?> settings)
: super(settings, null);
List<String> get analysisExcludedFolders {
// This setting is documented as a string array, but because editors are
// unlikely to provide validation, support single strings for convenience.
var value = _settings['analysisExcludedFolders'];
if (value is String) {
return [value];
} else if (value is List && value.every((s) => s is String)) {
return value.cast<String>();
} else {
return const [];
}
}
/// Whether methods/functions in completion should include parens and argument
/// placeholders when used in an invocation context.
bool get completeFunctionCalls =>
_settings['completeFunctionCalls'] as bool? ?? false;
/// A flag for enabling interactive refactors flagged as experimental.
///
/// This flag is likely to be used by both analysis server developers (working
/// on new refactors) and users that want to test/provide feedback for
/// incomplete refactors.
bool get experimentalRefactors =>
_settings['experimentalRefactors'] as bool? ?? false;
/// Whether or not to include dependencies in `textDocument/workspaceSymbols`.
///
/// If set to `false`, only analyzed files will be searched.
/// Defaults to `true`.
bool get includeDependenciesInWorkspaceSymbols =>
_settings['includeDependenciesInWorkspaceSymbols'] as bool? ?? true;
/// The users preferred kind of documentation for Hovers, Code Completion and
/// other related panels in the UI.
///
/// If the user has not expressed a preference, defaults to
/// [DocumentationPreference.full].
DocumentationPreference get preferredDocumentation {
var value = _settings['documentation'];
return switch (value) {
'none' => DocumentationPreference.none,
'summary' => DocumentationPreference.summary,
_ => DocumentationPreference.full,
};
}
/// A preview flag for enabling commit characters for completions.
///
/// This is a temporary setting to allow this feature to be tested without
/// defaulting to on for everybody.
bool get previewCommitCharacters =>
_settings['previewCommitCharacters'] as bool? ?? false;
// Whether diagnostics should be generated for all `TODO` comments.
bool get showAllTodos =>
_settings['showTodos'] is bool ? _settings['showTodos'] as bool : false;
// A specific set of `TODO` comments that should generate diagnostics.
///
/// Codes are all forced UPPERCASE regardless of what the client supplies.
///
/// [showAllTodos] should be checked first, as this will return an empty
/// set if `showTodos` is a boolean.
Set<String> get showTodoTypes =>
_settings['showTodos'] is List
? (_settings['showTodos'] as List)
.cast<String>()
.map((kind) => kind.toUpperCase())
.toSet()
: const {};
}
/// Wraps the client (editor) configuration for a specific resource.
///
/// Settings in this class are only allowed to be configured either for a
/// resource or for the whole workspace.
///
/// Right now, we treat "resource" to always mean a WorkspaceFolder since no
/// known editors allow per-file configuration and it allows us to keep the
/// settings cached, invalidated only when WorkspaceFolders change.
class LspResourceClientConfiguration {
/// The maximum number of completions to return for completion requests by
/// default.
///
/// This has been set fairly high initially to avoid changing behaviour too
/// much. The Dart-Code extension will override this default with its own
/// to gather feedback and then this can be adjusted accordingly.
static const defaultMaxCompletions = 2000;
final Map<String, Object?> _settings;
final LspResourceClientConfiguration? _fallback;
LspResourceClientConfiguration(this._settings, this._fallback);
/// Whether to enable the SDK formatter.
///
/// If this setting is `false`, the formatter will be unregistered with the
/// client.
bool get enableSdkFormatter =>
_settings['enableSdkFormatter'] as bool? ??
_fallback?.enableSdkFormatter ??
true;
/// Whether to include Snippets in code completion results.
bool get enableSnippets {
// Versions of Dart-Code earlier than v3.36 (1 Mar 2022) send
// enableServerSnippets=false to opt-out of snippets. Later versions map
// this version to the documented 'enableSnippets' setting in middleware.
// Once the number of users on < 3.36 is insignificant, this check can be
// removed. At 24 Mar 2022, approx 9% of users are on < 3.36.
if (_settings['enableServerSnippets'] == false /* explicit false */ ) {
return false;
}
return _settings['enableSnippets'] as bool? ??
_fallback?.enableSnippets ??
true;
}
/// The line length used when formatting documents if not specified in
/// `analysis_options.yaml`.
///
/// If null, the formatters default will be used.
int? get lineLength =>
_settings['lineLength'] as int? ?? _fallback?.lineLength;
/// Requested maximum number of CompletionItems per completion request.
///
/// If more than this are available, ranked items in the list will be
/// truncated and `isIncomplete` is set to `true`.
///
/// Unranked items are never truncated so it's still possible that more than
/// this number of items will be returned.
int get maxCompletionItems =>
_settings['maxCompletionItems'] as int? ??
_fallback?.maxCompletionItems ??
defaultMaxCompletions;
/// Whether to rename files when renaming classes inside them where the file
/// and class name match.
///
/// Values are "always", "prompt", "never". Any other values should be treated
/// like "never".
String get renameFilesWithClasses =>
_settings['renameFilesWithClasses'] as String? ??
_fallback?.renameFilesWithClasses ??
'never';
/// Whether to update imports and other directives when files are renamed.
///
/// This setting works by controlling whether the server registers for
/// `willRenameFiles` requests from the client. Changing the value after
/// initialization will register/unregister appropriately.
bool get updateImportsOnRename =>
_settings['updateImportsOnRename'] as bool? ??
_fallback?.updateImportsOnRename ??
true;
}