diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index bbe7fb41a40..5b76995706a 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -10,6 +10,7 @@ "Other" ], "activationEvents": [ + "onLanguage:json", "onLanguage:typescript" ], "main": "./out/extension", @@ -17,6 +18,10 @@ "compile": "gulp compile-extension:extension-editing", "watch": "gulp watch-extension:extension-editing" }, + "dependencies": { + "jsonc-parser": "^0.3.1", + "vscode-nls": "^2.0.1" + }, "contributes": { "jsonValidation": [ { diff --git a/extensions/extension-editing/src/extension.ts b/extensions/extension-editing/src/extension.ts index b7a41895542..d915e656b6b 100644 --- a/extensions/extension-editing/src/extension.ts +++ b/extensions/extension-editing/src/extension.ts @@ -7,10 +7,14 @@ import * as vscode from 'vscode'; import * as ts from 'typescript'; +import { PackageDocument } from './packageDocumentHelper'; export function activate(context: vscode.ExtensionContext) { const registration = vscode.languages.registerDocumentLinkProvider({ language: 'typescript', pattern: '**/vscode.d.ts' }, _linkProvider); context.subscriptions.push(registration); + + //package.json suggestions + context.subscriptions.push(registerPackageDocumentCompletions()); } const _linkProvider = new class implements vscode.DocumentLinkProvider { @@ -101,3 +105,11 @@ namespace ast { }; } } + +function registerPackageDocumentCompletions(): vscode.Disposable { + return vscode.languages.registerCompletionItemProvider({ language: 'json', pattern: '**/**/package.json' }, { + provideCompletionItems(document, position, token) { + return new PackageDocument(document).provideCompletionItems(position, token); + } + }); +} \ No newline at end of file diff --git a/extensions/extension-editing/src/packageDocumentHelper.ts b/extensions/extension-editing/src/packageDocumentHelper.ts new file mode 100644 index 00000000000..95edcda721d --- /dev/null +++ b/extensions/extension-editing/src/packageDocumentHelper.ts @@ -0,0 +1,84 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { getLocation, Location } from 'jsonc-parser'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); + +export class PackageDocument { + + constructor(private document: vscode.TextDocument) { } + + public provideCompletionItems(position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult { + const location = getLocation(this.document.getText(), this.document.offsetAt(position)); + + if (location.path.length >= 2 && location.path[1] === 'configurationDefaults') { + return this.provideLanguageOverridesCompletionItems(location, position); + } + + } + + private provideLanguageOverridesCompletionItems(location: Location, position: vscode.Position): vscode.ProviderResult { + let range = this.document.getWordRangeAtPosition(position) || new vscode.Range(position, position); + const text = this.document.getText(range); + + if (location.path.length === 2) { + + let snippet = '"[${1:language}]": {\n\t"$0"\n}'; + + // Suggestion model word matching includes quotes, + // hence exclude the starting quote from the snippet and the range + // ending quote gets replaced + if (text.startsWith('"')) { + range = new vscode.Range(new vscode.Position(range.start.line, range.start.character + 1), range.end); + snippet = snippet.substring(1); + } + + return Promise.resolve([this.newSnippetCompletionItem({ + label: localize('languageSpecificEditorSettings', "Language specific editor settings"), + documentation: localize('languageSpecificEditorSettingsDescription', "Override editor settings for language"), + snippet, + range + })]); + } + + if (location.path.length === 3 && location.previousNode && location.previousNode.value.startsWith('[')) { + + // Suggestion model word matching includes starting quote and open sqaure bracket + // Hence exclude them from the proposal range + range = new vscode.Range(new vscode.Position(range.start.line, range.start.character + 2), range.end); + + return vscode.languages.getLanguages().then(languages => { + return languages.map(l => { + + // Suggestion model word matching includes closed sqaure bracket and ending quote + // Hence include them in the proposal to replace + return this.newSimpleCompletionItem(l, range, '', l + ']"'); + }); + }); + } + return Promise.resolve([]); + } + + private newSimpleCompletionItem(text: string, range: vscode.Range, description?: string, insertText?: string): vscode.CompletionItem { + const item = new vscode.CompletionItem(text); + item.kind = vscode.CompletionItemKind.Value; + item.detail = description; + item.insertText = insertText ? insertText : text; + item.range = range; + return item; + } + + private newSnippetCompletionItem(o: { label: string; documentation?: string; snippet: string; range: vscode.Range; }): vscode.CompletionItem { + const item = new vscode.CompletionItem(o.label); + item.kind = vscode.CompletionItemKind.Value; + item.documentation = o.documentation; + item.insertText = new vscode.SnippetString(o.snippet); + item.range = o.range; + return item; + } +}