Path: blob/main/extensions/json-language-features/client/src/languageStatus.ts
3320 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import {6window, languages, Uri, Disposable, commands, QuickPickItem,7extensions, workspace, Extension, WorkspaceFolder, QuickPickItemKind,8ThemeIcon, TextDocument, LanguageStatusSeverity, l10n, DocumentSelector9} from 'vscode';10import { JSONLanguageStatus, JSONSchemaSettings } from './jsonClient';1112type ShowSchemasInput = {13schemas: string[];14uri: string;15};1617interface ShowSchemasItem extends QuickPickItem {18uri?: Uri;19buttonCommands?: (() => void)[];20}2122function getExtensionSchemaAssociations() {23const associations: { fullUri: string; extension: Extension<any>; label: string }[] = [];2425for (const extension of extensions.all) {26const jsonValidations = extension.packageJSON?.contributes?.jsonValidation;27if (Array.isArray(jsonValidations)) {28for (const jsonValidation of jsonValidations) {29let uri = jsonValidation.url;30if (typeof uri === 'string') {31if (uri[0] === '.' && uri[1] === '/') {32uri = Uri.joinPath(extension.extensionUri, uri).toString(false);33}34associations.push({ fullUri: uri, extension, label: jsonValidation.url });35}36}37}38}39return {40findExtension(uri: string): ShowSchemasItem | undefined {41for (const association of associations) {42if (association.fullUri === uri) {43return {44label: association.label,45detail: l10n.t('Configured by extension: {0}', association.extension.id),46uri: Uri.parse(association.fullUri),47buttons: [{ iconPath: new ThemeIcon('extensions'), tooltip: l10n.t('Open Extension') }],48buttonCommands: [() => commands.executeCommand('workbench.extensions.action.showExtensionsWithIds', [[association.extension.id]])]49};50}51}52return undefined;53}54};55}5657//5859function getSettingsSchemaAssociations(uri: string) {60const resourceUri = Uri.parse(uri);61const workspaceFolder = workspace.getWorkspaceFolder(resourceUri);6263const settings = workspace.getConfiguration('json', resourceUri).inspect<JSONSchemaSettings[]>('schemas');6465const associations: { fullUri: string; workspaceFolder: WorkspaceFolder | undefined; label: string }[] = [];6667const folderSettingSchemas = settings?.workspaceFolderValue;68if (workspaceFolder && Array.isArray(folderSettingSchemas)) {69for (const setting of folderSettingSchemas) {70const uri = setting.url;71if (typeof uri === 'string') {72let fullUri = uri;73if (uri[0] === '.' && uri[1] === '/') {74fullUri = Uri.joinPath(workspaceFolder.uri, uri).toString(false);75}76associations.push({ fullUri, workspaceFolder, label: uri });77}78}79}80const userSettingSchemas = settings?.globalValue;81if (Array.isArray(userSettingSchemas)) {82for (const setting of userSettingSchemas) {83const uri = setting.url;84if (typeof uri === 'string') {85let fullUri = uri;86if (workspaceFolder && uri[0] === '.' && uri[1] === '/') {87fullUri = Uri.joinPath(workspaceFolder.uri, uri).toString(false);88}89associations.push({ fullUri, workspaceFolder: undefined, label: uri });90}91}92}93return {94findSetting(uri: string): ShowSchemasItem | undefined {95for (const association of associations) {96if (association.fullUri === uri) {97return {98label: association.label,99detail: association.workspaceFolder ? l10n.t('Configured in workspace settings') : l10n.t('Configured in user settings'),100uri: Uri.parse(association.fullUri),101buttons: [{ iconPath: new ThemeIcon('gear'), tooltip: l10n.t('Open Settings') }],102buttonCommands: [() => commands.executeCommand(association.workspaceFolder ? 'workbench.action.openWorkspaceSettingsFile' : 'workbench.action.openSettingsJson', ['json.schemas'])]103};104}105}106return undefined;107}108};109}110111function showSchemaList(input: ShowSchemasInput) {112113const extensionSchemaAssocations = getExtensionSchemaAssociations();114const settingsSchemaAssocations = getSettingsSchemaAssociations(input.uri);115116const extensionEntries = [];117const settingsEntries = [];118const otherEntries = [];119120for (const schemaUri of input.schemas) {121const extensionEntry = extensionSchemaAssocations.findExtension(schemaUri);122if (extensionEntry) {123extensionEntries.push(extensionEntry);124continue;125}126const settingsEntry = settingsSchemaAssocations.findSetting(schemaUri);127if (settingsEntry) {128settingsEntries.push(settingsEntry);129continue;130}131otherEntries.push({ label: schemaUri, uri: Uri.parse(schemaUri) });132}133134const items: ShowSchemasItem[] = [...extensionEntries, ...settingsEntries, ...otherEntries];135if (items.length === 0) {136items.push({137label: l10n.t('No schema configured for this file'),138buttons: [{ iconPath: new ThemeIcon('gear'), tooltip: l10n.t('Open Settings') }],139buttonCommands: [() => commands.executeCommand('workbench.action.openSettingsJson', ['json.schemas'])]140});141}142143items.push({ label: '', kind: QuickPickItemKind.Separator });144items.push({ label: l10n.t('Learn more about JSON schema configuration...'), uri: Uri.parse('https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings') });145146const quickPick = window.createQuickPick<ShowSchemasItem>();147quickPick.placeholder = items.length ? l10n.t('Select the schema to use for {0}', input.uri) : undefined;148quickPick.items = items;149quickPick.show();150quickPick.onDidAccept(() => {151const uri = quickPick.selectedItems[0].uri;152if (uri) {153commands.executeCommand('vscode.open', uri);154quickPick.dispose();155}156});157quickPick.onDidTriggerItemButton(b => {158const index = b.item.buttons?.indexOf(b.button);159if (index !== undefined && index >= 0 && b.item.buttonCommands && b.item.buttonCommands[index]) {160b.item.buttonCommands[index]();161}162});163}164165export function createLanguageStatusItem(documentSelector: DocumentSelector, statusRequest: (uri: string) => Promise<JSONLanguageStatus>): Disposable {166const statusItem = languages.createLanguageStatusItem('json.projectStatus', documentSelector);167statusItem.name = l10n.t('JSON Validation Status');168statusItem.severity = LanguageStatusSeverity.Information;169170const showSchemasCommand = commands.registerCommand('_json.showAssociatedSchemaList', showSchemaList);171172const activeEditorListener = window.onDidChangeActiveTextEditor(() => {173updateLanguageStatus();174});175176async function updateLanguageStatus() {177const document = window.activeTextEditor?.document;178if (document) {179try {180statusItem.text = '$(loading~spin)';181statusItem.detail = l10n.t('Loading JSON info');182statusItem.command = undefined;183184const schemas = (await statusRequest(document.uri.toString())).schemas;185statusItem.detail = undefined;186if (schemas.length === 0) {187statusItem.text = l10n.t('No schema validation');188statusItem.detail = l10n.t('no JSON schema configured');189} else if (schemas.length === 1) {190statusItem.text = l10n.t('Schema validated');191statusItem.detail = l10n.t('JSON schema configured');192} else {193statusItem.text = l10n.t('Schema validated');194statusItem.detail = l10n.t('multiple JSON schemas configured');195}196statusItem.command = {197command: '_json.showAssociatedSchemaList',198title: l10n.t('Show Schemas'),199arguments: [{ schemas, uri: document.uri.toString() } satisfies ShowSchemasInput]200};201} catch (e) {202statusItem.text = l10n.t('Unable to compute used schemas: {0}', e.message);203statusItem.detail = undefined;204statusItem.command = undefined;205}206} else {207statusItem.text = l10n.t('Unable to compute used schemas: No document');208statusItem.detail = undefined;209statusItem.command = undefined;210}211}212213updateLanguageStatus();214215return Disposable.from(statusItem, activeEditorListener, showSchemasCommand);216}217218export function createLimitStatusItem(newItem: (limit: number) => Disposable) {219let statusItem: Disposable | undefined;220const activeLimits: Map<TextDocument, number> = new Map();221222const toDispose: Disposable[] = [];223toDispose.push(window.onDidChangeActiveTextEditor(textEditor => {224statusItem?.dispose();225statusItem = undefined;226const doc = textEditor?.document;227if (doc) {228const limit = activeLimits.get(doc);229if (limit !== undefined) {230statusItem = newItem(limit);231}232}233}));234toDispose.push(workspace.onDidCloseTextDocument(document => {235activeLimits.delete(document);236}));237238function update(document: TextDocument, limitApplied: number | false) {239if (limitApplied === false) {240activeLimits.delete(document);241if (statusItem && document === window.activeTextEditor?.document) {242statusItem.dispose();243statusItem = undefined;244}245} else {246activeLimits.set(document, limitApplied);247if (document === window.activeTextEditor?.document) {248if (!statusItem || limitApplied !== activeLimits.get(document)) {249statusItem?.dispose();250statusItem = newItem(limitApplied);251}252}253}254}255return {256update,257dispose() {258statusItem?.dispose();259toDispose.forEach(d => d.dispose());260toDispose.length = 0;261statusItem = undefined;262activeLimits.clear();263}264};265}266267const openSettingsCommand = 'workbench.action.openSettings';268const configureSettingsLabel = l10n.t('Configure');269270export function createDocumentSymbolsLimitItem(documentSelector: DocumentSelector, settingId: string, limit: number): Disposable {271const statusItem = languages.createLanguageStatusItem('json.documentSymbolsStatus', documentSelector);272statusItem.name = l10n.t('JSON Outline Status');273statusItem.severity = LanguageStatusSeverity.Warning;274statusItem.text = l10n.t('Outline');275statusItem.detail = l10n.t('only {0} document symbols shown for performance reasons', limit);276statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel };277return Disposable.from(statusItem);278}279280281282283