Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/configuration-editing/src/configurationEditingMain.ts
3292 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { getLocation, JSONPath, parse, visit, Location } from 'jsonc-parser';
7
import * as vscode from 'vscode';
8
import { SettingsDocument } from './settingsDocumentHelper';
9
import { provideInstalledExtensionProposals } from './extensionsProposals';
10
import './importExportProfiles';
11
12
export function activate(context: vscode.ExtensionContext): void {
13
//settings.json suggestions
14
context.subscriptions.push(registerSettingsCompletions());
15
16
//extensions suggestions
17
context.subscriptions.push(...registerExtensionsCompletions());
18
19
// launch.json variable suggestions
20
context.subscriptions.push(registerVariableCompletions('**/launch.json'));
21
22
// task.json variable suggestions
23
context.subscriptions.push(registerVariableCompletions('**/tasks.json'));
24
25
// Workspace file launch/tasks variable completions
26
context.subscriptions.push(registerVariableCompletions('**/*.code-workspace'));
27
28
// keybindings.json/package.json context key suggestions
29
context.subscriptions.push(registerContextKeyCompletions());
30
}
31
32
function registerSettingsCompletions(): vscode.Disposable {
33
return vscode.languages.registerCompletionItemProvider({ language: 'jsonc', pattern: '**/settings.json' }, {
34
provideCompletionItems(document, position, token) {
35
return new SettingsDocument(document).provideCompletionItems(position, token);
36
}
37
});
38
}
39
40
function registerVariableCompletions(pattern: string): vscode.Disposable {
41
return vscode.languages.registerCompletionItemProvider({ language: 'jsonc', pattern }, {
42
provideCompletionItems(document, position, _token) {
43
const location = getLocation(document.getText(), document.offsetAt(position));
44
if (isCompletingInsidePropertyStringValue(document, location, position)) {
45
if (document.fileName.endsWith('.code-workspace') && !isLocationInsideTopLevelProperty(location, ['launch', 'tasks'])) {
46
return [];
47
}
48
49
let range = document.getWordRangeAtPosition(position, /\$\{[^"\}]*\}?/);
50
if (!range || range.start.isEqual(position) || range.end.isEqual(position) && document.getText(range).endsWith('}')) {
51
range = new vscode.Range(position, position);
52
}
53
54
return [
55
{ label: 'workspaceFolder', detail: vscode.l10n.t("The path of the folder opened in VS Code") },
56
{ label: 'workspaceFolderBasename', detail: vscode.l10n.t("The name of the folder opened in VS Code without any slashes (/)") },
57
{ label: 'fileWorkspaceFolderBasename', detail: vscode.l10n.t("The current opened file workspace folder name without any slashes (/)") },
58
{ label: 'relativeFile', detail: vscode.l10n.t("The current opened file relative to ${workspaceFolder}") },
59
{ label: 'relativeFileDirname', detail: vscode.l10n.t("The current opened file's dirname relative to ${workspaceFolder}") },
60
{ label: 'file', detail: vscode.l10n.t("The current opened file") },
61
{ label: 'cwd', detail: vscode.l10n.t("The task runner's current working directory on startup") },
62
{ label: 'lineNumber', detail: vscode.l10n.t("The current selected line number in the active file") },
63
{ label: 'selectedText', detail: vscode.l10n.t("The current selected text in the active file") },
64
{ label: 'fileDirname', detail: vscode.l10n.t("The current opened file's dirname") },
65
{ label: 'fileDirnameBasename', detail: vscode.l10n.t("The current opened file's folder name") },
66
{ label: 'fileExtname', detail: vscode.l10n.t("The current opened file's extension") },
67
{ label: 'fileBasename', detail: vscode.l10n.t("The current opened file's basename") },
68
{ label: 'fileBasenameNoExtension', detail: vscode.l10n.t("The current opened file's basename with no file extension") },
69
{ label: 'defaultBuildTask', detail: vscode.l10n.t("The name of the default build task. If there is not a single default build task then a quick pick is shown to choose the build task.") },
70
{ label: 'pathSeparator', detail: vscode.l10n.t("The character used by the operating system to separate components in file paths. Is also aliased to '/'.") },
71
{ label: 'extensionInstallFolder', detail: vscode.l10n.t("The path where an extension is installed."), param: 'publisher.extension' },
72
].map(variable => ({
73
label: `\${${variable.label}}`,
74
range,
75
insertText: variable.param ? new vscode.SnippetString(`\${${variable.label}:`).appendPlaceholder(variable.param).appendText('}') : (`\${${variable.label}}`),
76
detail: variable.detail
77
}));
78
}
79
80
return [];
81
}
82
});
83
}
84
85
function isCompletingInsidePropertyStringValue(document: vscode.TextDocument, location: Location, pos: vscode.Position) {
86
if (location.isAtPropertyKey) {
87
return false;
88
}
89
const previousNode = location.previousNode;
90
if (previousNode && previousNode.type === 'string') {
91
const offset = document.offsetAt(pos);
92
return offset > previousNode.offset && offset < previousNode.offset + previousNode.length;
93
}
94
return false;
95
}
96
97
function isLocationInsideTopLevelProperty(location: Location, values: string[]) {
98
return values.includes(location.path[0] as string);
99
}
100
101
interface IExtensionsContent {
102
recommendations: string[];
103
}
104
105
function registerExtensionsCompletions(): vscode.Disposable[] {
106
return [registerExtensionsCompletionsInExtensionsDocument(), registerExtensionsCompletionsInWorkspaceConfigurationDocument()];
107
}
108
109
function registerExtensionsCompletionsInExtensionsDocument(): vscode.Disposable {
110
return vscode.languages.registerCompletionItemProvider({ pattern: '**/extensions.json' }, {
111
provideCompletionItems(document, position, _token) {
112
const location = getLocation(document.getText(), document.offsetAt(position));
113
if (location.path[0] === 'recommendations') {
114
const range = getReplaceRange(document, location, position);
115
const extensionsContent = <IExtensionsContent>parse(document.getText());
116
return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], '', range, false);
117
}
118
return [];
119
}
120
});
121
}
122
123
function registerExtensionsCompletionsInWorkspaceConfigurationDocument(): vscode.Disposable {
124
return vscode.languages.registerCompletionItemProvider({ pattern: '**/*.code-workspace' }, {
125
provideCompletionItems(document, position, _token) {
126
const location = getLocation(document.getText(), document.offsetAt(position));
127
if (location.path[0] === 'extensions' && location.path[1] === 'recommendations') {
128
const range = getReplaceRange(document, location, position);
129
const extensionsContent = <IExtensionsContent>parse(document.getText())['extensions'];
130
return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], '', range, false);
131
}
132
return [];
133
}
134
});
135
}
136
137
function getReplaceRange(document: vscode.TextDocument, location: Location, position: vscode.Position) {
138
const node = location.previousNode;
139
if (node) {
140
const nodeStart = document.positionAt(node.offset), nodeEnd = document.positionAt(node.offset + node.length);
141
if (nodeStart.isBeforeOrEqual(position) && nodeEnd.isAfterOrEqual(position)) {
142
return new vscode.Range(nodeStart, nodeEnd);
143
}
144
}
145
return new vscode.Range(position, position);
146
}
147
148
vscode.languages.registerDocumentSymbolProvider({ pattern: '**/launch.json', language: 'jsonc' }, {
149
provideDocumentSymbols(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.SymbolInformation[]> {
150
const result: vscode.SymbolInformation[] = [];
151
let name: string = '';
152
let lastProperty = '';
153
let startOffset = 0;
154
let depthInObjects = 0;
155
156
visit(document.getText(), {
157
onObjectProperty: (property, _offset, _length) => {
158
lastProperty = property;
159
},
160
onLiteralValue: (value: any, _offset: number, _length: number) => {
161
if (lastProperty === 'name') {
162
name = value;
163
}
164
},
165
onObjectBegin: (offset: number, _length: number) => {
166
depthInObjects++;
167
if (depthInObjects === 2) {
168
startOffset = offset;
169
}
170
},
171
onObjectEnd: (offset: number, _length: number) => {
172
if (name && depthInObjects === 2) {
173
result.push(new vscode.SymbolInformation(name, vscode.SymbolKind.Object, new vscode.Range(document.positionAt(startOffset), document.positionAt(offset))));
174
}
175
depthInObjects--;
176
},
177
});
178
179
return result;
180
}
181
}, { label: 'Launch Targets' });
182
183
function registerContextKeyCompletions(): vscode.Disposable {
184
type ContextKeyInfo = { key: string; type?: string; description?: string };
185
186
const paths = new Map<vscode.DocumentFilter, JSONPath[]>([
187
[{ language: 'jsonc', pattern: '**/keybindings.json' }, [
188
['*', 'when']
189
]],
190
[{ language: 'json', pattern: '**/package.json' }, [
191
['contributes', 'menus', '*', '*', 'when'],
192
['contributes', 'views', '*', '*', 'when'],
193
['contributes', 'viewsWelcome', '*', 'when'],
194
['contributes', 'keybindings', '*', 'when'],
195
['contributes', 'keybindings', 'when'],
196
]]
197
]);
198
199
return vscode.languages.registerCompletionItemProvider(
200
[...paths.keys()],
201
{
202
async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) {
203
204
const location = getLocation(document.getText(), document.offsetAt(position));
205
206
if (location.isAtPropertyKey) {
207
return;
208
}
209
210
let isValidLocation = false;
211
for (const [key, value] of paths) {
212
if (vscode.languages.match(key, document)) {
213
if (value.some(location.matches.bind(location))) {
214
isValidLocation = true;
215
break;
216
}
217
}
218
}
219
220
if (!isValidLocation || !isCompletingInsidePropertyStringValue(document, location, position)) {
221
return;
222
}
223
224
const replacing = document.getWordRangeAtPosition(position, /[a-zA-Z.]+/) || new vscode.Range(position, position);
225
const inserting = replacing.with(undefined, position);
226
227
const data = await vscode.commands.executeCommand<ContextKeyInfo[]>('getContextKeyInfo');
228
if (token.isCancellationRequested || !data) {
229
return;
230
}
231
232
const result = new vscode.CompletionList();
233
for (const item of data) {
234
const completion = new vscode.CompletionItem(item.key, vscode.CompletionItemKind.Constant);
235
completion.detail = item.type;
236
completion.range = { replacing, inserting };
237
completion.documentation = item.description;
238
result.items.push(completion);
239
}
240
return result;
241
}
242
}
243
);
244
}
245
246