Path: blob/main/extensions/configuration-editing/src/configurationEditingMain.ts
3292 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 { getLocation, JSONPath, parse, visit, Location } from 'jsonc-parser';6import * as vscode from 'vscode';7import { SettingsDocument } from './settingsDocumentHelper';8import { provideInstalledExtensionProposals } from './extensionsProposals';9import './importExportProfiles';1011export function activate(context: vscode.ExtensionContext): void {12//settings.json suggestions13context.subscriptions.push(registerSettingsCompletions());1415//extensions suggestions16context.subscriptions.push(...registerExtensionsCompletions());1718// launch.json variable suggestions19context.subscriptions.push(registerVariableCompletions('**/launch.json'));2021// task.json variable suggestions22context.subscriptions.push(registerVariableCompletions('**/tasks.json'));2324// Workspace file launch/tasks variable completions25context.subscriptions.push(registerVariableCompletions('**/*.code-workspace'));2627// keybindings.json/package.json context key suggestions28context.subscriptions.push(registerContextKeyCompletions());29}3031function registerSettingsCompletions(): vscode.Disposable {32return vscode.languages.registerCompletionItemProvider({ language: 'jsonc', pattern: '**/settings.json' }, {33provideCompletionItems(document, position, token) {34return new SettingsDocument(document).provideCompletionItems(position, token);35}36});37}3839function registerVariableCompletions(pattern: string): vscode.Disposable {40return vscode.languages.registerCompletionItemProvider({ language: 'jsonc', pattern }, {41provideCompletionItems(document, position, _token) {42const location = getLocation(document.getText(), document.offsetAt(position));43if (isCompletingInsidePropertyStringValue(document, location, position)) {44if (document.fileName.endsWith('.code-workspace') && !isLocationInsideTopLevelProperty(location, ['launch', 'tasks'])) {45return [];46}4748let range = document.getWordRangeAtPosition(position, /\$\{[^"\}]*\}?/);49if (!range || range.start.isEqual(position) || range.end.isEqual(position) && document.getText(range).endsWith('}')) {50range = new vscode.Range(position, position);51}5253return [54{ label: 'workspaceFolder', detail: vscode.l10n.t("The path of the folder opened in VS Code") },55{ label: 'workspaceFolderBasename', detail: vscode.l10n.t("The name of the folder opened in VS Code without any slashes (/)") },56{ label: 'fileWorkspaceFolderBasename', detail: vscode.l10n.t("The current opened file workspace folder name without any slashes (/)") },57{ label: 'relativeFile', detail: vscode.l10n.t("The current opened file relative to ${workspaceFolder}") },58{ label: 'relativeFileDirname', detail: vscode.l10n.t("The current opened file's dirname relative to ${workspaceFolder}") },59{ label: 'file', detail: vscode.l10n.t("The current opened file") },60{ label: 'cwd', detail: vscode.l10n.t("The task runner's current working directory on startup") },61{ label: 'lineNumber', detail: vscode.l10n.t("The current selected line number in the active file") },62{ label: 'selectedText', detail: vscode.l10n.t("The current selected text in the active file") },63{ label: 'fileDirname', detail: vscode.l10n.t("The current opened file's dirname") },64{ label: 'fileDirnameBasename', detail: vscode.l10n.t("The current opened file's folder name") },65{ label: 'fileExtname', detail: vscode.l10n.t("The current opened file's extension") },66{ label: 'fileBasename', detail: vscode.l10n.t("The current opened file's basename") },67{ label: 'fileBasenameNoExtension', detail: vscode.l10n.t("The current opened file's basename with no file extension") },68{ 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.") },69{ label: 'pathSeparator', detail: vscode.l10n.t("The character used by the operating system to separate components in file paths. Is also aliased to '/'.") },70{ label: 'extensionInstallFolder', detail: vscode.l10n.t("The path where an extension is installed."), param: 'publisher.extension' },71].map(variable => ({72label: `\${${variable.label}}`,73range,74insertText: variable.param ? new vscode.SnippetString(`\${${variable.label}:`).appendPlaceholder(variable.param).appendText('}') : (`\${${variable.label}}`),75detail: variable.detail76}));77}7879return [];80}81});82}8384function isCompletingInsidePropertyStringValue(document: vscode.TextDocument, location: Location, pos: vscode.Position) {85if (location.isAtPropertyKey) {86return false;87}88const previousNode = location.previousNode;89if (previousNode && previousNode.type === 'string') {90const offset = document.offsetAt(pos);91return offset > previousNode.offset && offset < previousNode.offset + previousNode.length;92}93return false;94}9596function isLocationInsideTopLevelProperty(location: Location, values: string[]) {97return values.includes(location.path[0] as string);98}99100interface IExtensionsContent {101recommendations: string[];102}103104function registerExtensionsCompletions(): vscode.Disposable[] {105return [registerExtensionsCompletionsInExtensionsDocument(), registerExtensionsCompletionsInWorkspaceConfigurationDocument()];106}107108function registerExtensionsCompletionsInExtensionsDocument(): vscode.Disposable {109return vscode.languages.registerCompletionItemProvider({ pattern: '**/extensions.json' }, {110provideCompletionItems(document, position, _token) {111const location = getLocation(document.getText(), document.offsetAt(position));112if (location.path[0] === 'recommendations') {113const range = getReplaceRange(document, location, position);114const extensionsContent = <IExtensionsContent>parse(document.getText());115return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], '', range, false);116}117return [];118}119});120}121122function registerExtensionsCompletionsInWorkspaceConfigurationDocument(): vscode.Disposable {123return vscode.languages.registerCompletionItemProvider({ pattern: '**/*.code-workspace' }, {124provideCompletionItems(document, position, _token) {125const location = getLocation(document.getText(), document.offsetAt(position));126if (location.path[0] === 'extensions' && location.path[1] === 'recommendations') {127const range = getReplaceRange(document, location, position);128const extensionsContent = <IExtensionsContent>parse(document.getText())['extensions'];129return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], '', range, false);130}131return [];132}133});134}135136function getReplaceRange(document: vscode.TextDocument, location: Location, position: vscode.Position) {137const node = location.previousNode;138if (node) {139const nodeStart = document.positionAt(node.offset), nodeEnd = document.positionAt(node.offset + node.length);140if (nodeStart.isBeforeOrEqual(position) && nodeEnd.isAfterOrEqual(position)) {141return new vscode.Range(nodeStart, nodeEnd);142}143}144return new vscode.Range(position, position);145}146147vscode.languages.registerDocumentSymbolProvider({ pattern: '**/launch.json', language: 'jsonc' }, {148provideDocumentSymbols(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.SymbolInformation[]> {149const result: vscode.SymbolInformation[] = [];150let name: string = '';151let lastProperty = '';152let startOffset = 0;153let depthInObjects = 0;154155visit(document.getText(), {156onObjectProperty: (property, _offset, _length) => {157lastProperty = property;158},159onLiteralValue: (value: any, _offset: number, _length: number) => {160if (lastProperty === 'name') {161name = value;162}163},164onObjectBegin: (offset: number, _length: number) => {165depthInObjects++;166if (depthInObjects === 2) {167startOffset = offset;168}169},170onObjectEnd: (offset: number, _length: number) => {171if (name && depthInObjects === 2) {172result.push(new vscode.SymbolInformation(name, vscode.SymbolKind.Object, new vscode.Range(document.positionAt(startOffset), document.positionAt(offset))));173}174depthInObjects--;175},176});177178return result;179}180}, { label: 'Launch Targets' });181182function registerContextKeyCompletions(): vscode.Disposable {183type ContextKeyInfo = { key: string; type?: string; description?: string };184185const paths = new Map<vscode.DocumentFilter, JSONPath[]>([186[{ language: 'jsonc', pattern: '**/keybindings.json' }, [187['*', 'when']188]],189[{ language: 'json', pattern: '**/package.json' }, [190['contributes', 'menus', '*', '*', 'when'],191['contributes', 'views', '*', '*', 'when'],192['contributes', 'viewsWelcome', '*', 'when'],193['contributes', 'keybindings', '*', 'when'],194['contributes', 'keybindings', 'when'],195]]196]);197198return vscode.languages.registerCompletionItemProvider(199[...paths.keys()],200{201async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) {202203const location = getLocation(document.getText(), document.offsetAt(position));204205if (location.isAtPropertyKey) {206return;207}208209let isValidLocation = false;210for (const [key, value] of paths) {211if (vscode.languages.match(key, document)) {212if (value.some(location.matches.bind(location))) {213isValidLocation = true;214break;215}216}217}218219if (!isValidLocation || !isCompletingInsidePropertyStringValue(document, location, position)) {220return;221}222223const replacing = document.getWordRangeAtPosition(position, /[a-zA-Z.]+/) || new vscode.Range(position, position);224const inserting = replacing.with(undefined, position);225226const data = await vscode.commands.executeCommand<ContextKeyInfo[]>('getContextKeyInfo');227if (token.isCancellationRequested || !data) {228return;229}230231const result = new vscode.CompletionList();232for (const item of data) {233const completion = new vscode.CompletionItem(item.key, vscode.CompletionItemKind.Constant);234completion.detail = item.type;235completion.range = { replacing, inserting };236completion.documentation = item.description;237result.items.push(completion);238}239return result;240}241}242);243}244245246