Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/json-language-features/client/src/languageStatus.ts
3320 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 {
7
window, languages, Uri, Disposable, commands, QuickPickItem,
8
extensions, workspace, Extension, WorkspaceFolder, QuickPickItemKind,
9
ThemeIcon, TextDocument, LanguageStatusSeverity, l10n, DocumentSelector
10
} from 'vscode';
11
import { JSONLanguageStatus, JSONSchemaSettings } from './jsonClient';
12
13
type ShowSchemasInput = {
14
schemas: string[];
15
uri: string;
16
};
17
18
interface ShowSchemasItem extends QuickPickItem {
19
uri?: Uri;
20
buttonCommands?: (() => void)[];
21
}
22
23
function getExtensionSchemaAssociations() {
24
const associations: { fullUri: string; extension: Extension<any>; label: string }[] = [];
25
26
for (const extension of extensions.all) {
27
const jsonValidations = extension.packageJSON?.contributes?.jsonValidation;
28
if (Array.isArray(jsonValidations)) {
29
for (const jsonValidation of jsonValidations) {
30
let uri = jsonValidation.url;
31
if (typeof uri === 'string') {
32
if (uri[0] === '.' && uri[1] === '/') {
33
uri = Uri.joinPath(extension.extensionUri, uri).toString(false);
34
}
35
associations.push({ fullUri: uri, extension, label: jsonValidation.url });
36
}
37
}
38
}
39
}
40
return {
41
findExtension(uri: string): ShowSchemasItem | undefined {
42
for (const association of associations) {
43
if (association.fullUri === uri) {
44
return {
45
label: association.label,
46
detail: l10n.t('Configured by extension: {0}', association.extension.id),
47
uri: Uri.parse(association.fullUri),
48
buttons: [{ iconPath: new ThemeIcon('extensions'), tooltip: l10n.t('Open Extension') }],
49
buttonCommands: [() => commands.executeCommand('workbench.extensions.action.showExtensionsWithIds', [[association.extension.id]])]
50
};
51
}
52
}
53
return undefined;
54
}
55
};
56
}
57
58
//
59
60
function getSettingsSchemaAssociations(uri: string) {
61
const resourceUri = Uri.parse(uri);
62
const workspaceFolder = workspace.getWorkspaceFolder(resourceUri);
63
64
const settings = workspace.getConfiguration('json', resourceUri).inspect<JSONSchemaSettings[]>('schemas');
65
66
const associations: { fullUri: string; workspaceFolder: WorkspaceFolder | undefined; label: string }[] = [];
67
68
const folderSettingSchemas = settings?.workspaceFolderValue;
69
if (workspaceFolder && Array.isArray(folderSettingSchemas)) {
70
for (const setting of folderSettingSchemas) {
71
const uri = setting.url;
72
if (typeof uri === 'string') {
73
let fullUri = uri;
74
if (uri[0] === '.' && uri[1] === '/') {
75
fullUri = Uri.joinPath(workspaceFolder.uri, uri).toString(false);
76
}
77
associations.push({ fullUri, workspaceFolder, label: uri });
78
}
79
}
80
}
81
const userSettingSchemas = settings?.globalValue;
82
if (Array.isArray(userSettingSchemas)) {
83
for (const setting of userSettingSchemas) {
84
const uri = setting.url;
85
if (typeof uri === 'string') {
86
let fullUri = uri;
87
if (workspaceFolder && uri[0] === '.' && uri[1] === '/') {
88
fullUri = Uri.joinPath(workspaceFolder.uri, uri).toString(false);
89
}
90
associations.push({ fullUri, workspaceFolder: undefined, label: uri });
91
}
92
}
93
}
94
return {
95
findSetting(uri: string): ShowSchemasItem | undefined {
96
for (const association of associations) {
97
if (association.fullUri === uri) {
98
return {
99
label: association.label,
100
detail: association.workspaceFolder ? l10n.t('Configured in workspace settings') : l10n.t('Configured in user settings'),
101
uri: Uri.parse(association.fullUri),
102
buttons: [{ iconPath: new ThemeIcon('gear'), tooltip: l10n.t('Open Settings') }],
103
buttonCommands: [() => commands.executeCommand(association.workspaceFolder ? 'workbench.action.openWorkspaceSettingsFile' : 'workbench.action.openSettingsJson', ['json.schemas'])]
104
};
105
}
106
}
107
return undefined;
108
}
109
};
110
}
111
112
function showSchemaList(input: ShowSchemasInput) {
113
114
const extensionSchemaAssocations = getExtensionSchemaAssociations();
115
const settingsSchemaAssocations = getSettingsSchemaAssociations(input.uri);
116
117
const extensionEntries = [];
118
const settingsEntries = [];
119
const otherEntries = [];
120
121
for (const schemaUri of input.schemas) {
122
const extensionEntry = extensionSchemaAssocations.findExtension(schemaUri);
123
if (extensionEntry) {
124
extensionEntries.push(extensionEntry);
125
continue;
126
}
127
const settingsEntry = settingsSchemaAssocations.findSetting(schemaUri);
128
if (settingsEntry) {
129
settingsEntries.push(settingsEntry);
130
continue;
131
}
132
otherEntries.push({ label: schemaUri, uri: Uri.parse(schemaUri) });
133
}
134
135
const items: ShowSchemasItem[] = [...extensionEntries, ...settingsEntries, ...otherEntries];
136
if (items.length === 0) {
137
items.push({
138
label: l10n.t('No schema configured for this file'),
139
buttons: [{ iconPath: new ThemeIcon('gear'), tooltip: l10n.t('Open Settings') }],
140
buttonCommands: [() => commands.executeCommand('workbench.action.openSettingsJson', ['json.schemas'])]
141
});
142
}
143
144
items.push({ label: '', kind: QuickPickItemKind.Separator });
145
items.push({ label: l10n.t('Learn more about JSON schema configuration...'), uri: Uri.parse('https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings') });
146
147
const quickPick = window.createQuickPick<ShowSchemasItem>();
148
quickPick.placeholder = items.length ? l10n.t('Select the schema to use for {0}', input.uri) : undefined;
149
quickPick.items = items;
150
quickPick.show();
151
quickPick.onDidAccept(() => {
152
const uri = quickPick.selectedItems[0].uri;
153
if (uri) {
154
commands.executeCommand('vscode.open', uri);
155
quickPick.dispose();
156
}
157
});
158
quickPick.onDidTriggerItemButton(b => {
159
const index = b.item.buttons?.indexOf(b.button);
160
if (index !== undefined && index >= 0 && b.item.buttonCommands && b.item.buttonCommands[index]) {
161
b.item.buttonCommands[index]();
162
}
163
});
164
}
165
166
export function createLanguageStatusItem(documentSelector: DocumentSelector, statusRequest: (uri: string) => Promise<JSONLanguageStatus>): Disposable {
167
const statusItem = languages.createLanguageStatusItem('json.projectStatus', documentSelector);
168
statusItem.name = l10n.t('JSON Validation Status');
169
statusItem.severity = LanguageStatusSeverity.Information;
170
171
const showSchemasCommand = commands.registerCommand('_json.showAssociatedSchemaList', showSchemaList);
172
173
const activeEditorListener = window.onDidChangeActiveTextEditor(() => {
174
updateLanguageStatus();
175
});
176
177
async function updateLanguageStatus() {
178
const document = window.activeTextEditor?.document;
179
if (document) {
180
try {
181
statusItem.text = '$(loading~spin)';
182
statusItem.detail = l10n.t('Loading JSON info');
183
statusItem.command = undefined;
184
185
const schemas = (await statusRequest(document.uri.toString())).schemas;
186
statusItem.detail = undefined;
187
if (schemas.length === 0) {
188
statusItem.text = l10n.t('No schema validation');
189
statusItem.detail = l10n.t('no JSON schema configured');
190
} else if (schemas.length === 1) {
191
statusItem.text = l10n.t('Schema validated');
192
statusItem.detail = l10n.t('JSON schema configured');
193
} else {
194
statusItem.text = l10n.t('Schema validated');
195
statusItem.detail = l10n.t('multiple JSON schemas configured');
196
}
197
statusItem.command = {
198
command: '_json.showAssociatedSchemaList',
199
title: l10n.t('Show Schemas'),
200
arguments: [{ schemas, uri: document.uri.toString() } satisfies ShowSchemasInput]
201
};
202
} catch (e) {
203
statusItem.text = l10n.t('Unable to compute used schemas: {0}', e.message);
204
statusItem.detail = undefined;
205
statusItem.command = undefined;
206
}
207
} else {
208
statusItem.text = l10n.t('Unable to compute used schemas: No document');
209
statusItem.detail = undefined;
210
statusItem.command = undefined;
211
}
212
}
213
214
updateLanguageStatus();
215
216
return Disposable.from(statusItem, activeEditorListener, showSchemasCommand);
217
}
218
219
export function createLimitStatusItem(newItem: (limit: number) => Disposable) {
220
let statusItem: Disposable | undefined;
221
const activeLimits: Map<TextDocument, number> = new Map();
222
223
const toDispose: Disposable[] = [];
224
toDispose.push(window.onDidChangeActiveTextEditor(textEditor => {
225
statusItem?.dispose();
226
statusItem = undefined;
227
const doc = textEditor?.document;
228
if (doc) {
229
const limit = activeLimits.get(doc);
230
if (limit !== undefined) {
231
statusItem = newItem(limit);
232
}
233
}
234
}));
235
toDispose.push(workspace.onDidCloseTextDocument(document => {
236
activeLimits.delete(document);
237
}));
238
239
function update(document: TextDocument, limitApplied: number | false) {
240
if (limitApplied === false) {
241
activeLimits.delete(document);
242
if (statusItem && document === window.activeTextEditor?.document) {
243
statusItem.dispose();
244
statusItem = undefined;
245
}
246
} else {
247
activeLimits.set(document, limitApplied);
248
if (document === window.activeTextEditor?.document) {
249
if (!statusItem || limitApplied !== activeLimits.get(document)) {
250
statusItem?.dispose();
251
statusItem = newItem(limitApplied);
252
}
253
}
254
}
255
}
256
return {
257
update,
258
dispose() {
259
statusItem?.dispose();
260
toDispose.forEach(d => d.dispose());
261
toDispose.length = 0;
262
statusItem = undefined;
263
activeLimits.clear();
264
}
265
};
266
}
267
268
const openSettingsCommand = 'workbench.action.openSettings';
269
const configureSettingsLabel = l10n.t('Configure');
270
271
export function createDocumentSymbolsLimitItem(documentSelector: DocumentSelector, settingId: string, limit: number): Disposable {
272
const statusItem = languages.createLanguageStatusItem('json.documentSymbolsStatus', documentSelector);
273
statusItem.name = l10n.t('JSON Outline Status');
274
statusItem.severity = LanguageStatusSeverity.Warning;
275
statusItem.text = l10n.t('Outline');
276
statusItem.detail = l10n.t('only {0} document symbols shown for performance reasons', limit);
277
statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel };
278
return Disposable.from(statusItem);
279
}
280
281
282
283