From 18fc98a03a3c97742cd9e32bad7ed0f1e3a934e7 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 9 Feb 2017 16:18:44 -0800 Subject: [PATCH 1/3] Add Command to Go To / Create project configuration for an active js or ts file Part of #20356 Adds a new command that opens the jsconfig or tsconfig project configuration file for the currently active file. If one does not exist, displays a quick pick that allows users to learn more and create a config file at the root of their project --- extensions/typescript/package.json | 7 +- extensions/typescript/package.nls.json | 3 +- extensions/typescript/src/typescriptMain.ts | 84 ++++++++++++++++++++- 3 files changed, 91 insertions(+), 3 deletions(-) diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index 415686fe9c980..8068d255a2df8 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -33,7 +33,8 @@ "onLanguage:jsx-tags", "onCommand:typescript.reloadProjects", "onCommand:javascript.reloadProjects", - "onCommand:typescript.selectTypeScriptVersion" + "onCommand:typescript.selectTypeScriptVersion", + "onCommand:typescript.goToProjectConfig" ], "main": "./out/typescriptMain", "enableProposedApi": true, @@ -266,6 +267,10 @@ { "command": "typescript.selectTypeScriptVersion", "title": "%typescript.selectTypeScriptVersion.title%" + }, + { + "command": "typescript.goToProjectConfig", + "title": "%typescript.goToProjectConfig.title%" } ], "breakpoints": [ diff --git a/extensions/typescript/package.nls.json b/extensions/typescript/package.nls.json index 2d1589b98cf4d..716231fca9da2 100644 --- a/extensions/typescript/package.nls.json +++ b/extensions/typescript/package.nls.json @@ -24,6 +24,7 @@ "format.placeOpenBraceOnNewLineForFunctions": "Defines whether an open brace is put onto a new line for functions or not.", "format.placeOpenBraceOnNewLineForControlBlocks": "Defines whether an open brace is put onto a new line for control blocks or not.", "javascript.validate.enable": "Enable/disable JavaScript validation.", + "typescript.goToProjectConfig.title": "Go to project configuration", "typescript.referencesCodeLens.enabled": "Enable/disable the references code lens.", - "typescript.selectTypeScriptVersion.title": "Select TypeScript version." + "typescript.selectTypeScriptVersion.title": "Select TypeScript version" } \ No newline at end of file diff --git a/extensions/typescript/src/typescriptMain.ts b/extensions/typescript/src/typescriptMain.ts index 2fe25d90d57f6..0f3bc36a92a3c 100644 --- a/extensions/typescript/src/typescriptMain.ts +++ b/extensions/typescript/src/typescriptMain.ts @@ -9,12 +9,13 @@ * ------------------------------------------------------------------------------------------ */ 'use strict'; -import { env, languages, commands, workspace, window, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, DocumentFilter, Disposable, Uri } from 'vscode'; +import { env, languages, commands, workspace, window, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, DocumentFilter, Disposable, Uri, QuickPickItem, TextEditor } from 'vscode'; // This must be the first statement otherwise modules might got loaded with // the wrong locale. import * as nls from 'vscode-nls'; nls.config({ locale: env.language }); +const localize = nls.loadMessageBundle(); import * as path from 'path'; @@ -52,6 +53,16 @@ interface LanguageDescription { configFile: string; } +enum ProjectConfigAction { + None, + CreateConfig, + LearnMore +} + +interface ProjectConfigQuickPick extends QuickPickItem { + id: ProjectConfigAction; +} + export function activate(context: ExtensionContext): void { const MODE_ID_TS = 'typescript'; const MODE_ID_TSX = 'typescriptreact'; @@ -89,6 +100,15 @@ export function activate(context: ExtensionContext): void { client.onVersionStatusClicked(); })); + context.subscriptions.push(commands.registerCommand('typescript.goToProjectConfig', () => { + const editor = window.activeTextEditor; + if (!editor) { + return; + } + + clientHost.goToProjectConfig(editor.document.uri, editor.document.languageId); + })); + window.onDidChangeActiveTextEditor(VersionStatus.showHideStatus, null, context.subscriptions); client.onReady().then(() => { context.subscriptions.push(ProjectStatus.create(client, @@ -378,6 +398,68 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { return !!this.findLanguage(file); } + public goToProjectConfig(resource: Uri, languageId: string): Thenable | undefined { + const rootPath = workspace.rootPath; + if (!this.languagePerId[languageId] || !rootPath) { + return undefined; + } + + const file = this.client.normalizePath(resource); + if (!file) { + return undefined; + } + const args: protocol.ProjectInfoRequestArgs = { + file: file, + needFileNameList: false + }; + return this.client.execute('projectInfo', args).then(res => { + if (!res || !res.body) { + return undefined; + } + + const {configFileName} = res.body; + if (configFileName && configFileName.indexOf('/dev/null/') !== 0) { + return workspace.openTextDocument(configFileName) + .then(window.showTextDocument); + } + + const isJsProject = languageId === 'javascript' || languageId === 'javascriptreact'; + return window.showQuickPick([ + { + label: isJsProject + ? localize('typescript.configureJsconfigQuickPick', 'Configure jsconfig.json') + : localize('typescript.configureTsconfigQuickPick', 'Configure tsconfig.json'), + description: '', + id: ProjectConfigAction.CreateConfig, + }, { + label: localize('typescript.projectConfigLearnMore', 'Learn More'), + description: '', + id: ProjectConfigAction.LearnMore + }], { + placeHolder: localize('typescript.noProjectConfigPlaceholder', 'File is not part of a project') + }).then(selected => { + switch (selected && selected.id) { + case ProjectConfigAction.CreateConfig: + const configFile = Uri.file(path.join(rootPath, isJsProject ? 'jsconfig.json' : 'tsconfig.json')); + return workspace.openTextDocument(configFile) + .then(undefined, _ => workspace.openTextDocument(configFile.with({ scheme: 'untitled' }))) + .then(window.showTextDocument); + + case ProjectConfigAction.LearnMore: + if (isJsProject) { + commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=759670')); + } else { + commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=841896')); + } + return; + + default: + return Promise.resolve(undefined); + } + }); + }); + } + private findLanguage(file: string): LanguageProvider | null { for (let i = 0; i < this.languages.length; i++) { let language = this.languages[i]; From 782fa00187948bce1c4b85cef8fa85d6c7ca7b23 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 10 Feb 2017 14:22:03 -0800 Subject: [PATCH 2/3] Add messages for error cases --- extensions/typescript/package.json | 20 ++++-- extensions/typescript/package.nls.json | 9 +-- extensions/typescript/src/typescriptMain.ts | 73 +++++++++++---------- 3 files changed, 58 insertions(+), 44 deletions(-) diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index 8068d255a2df8..652728d8a17ca 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -34,6 +34,7 @@ "onCommand:typescript.reloadProjects", "onCommand:javascript.reloadProjects", "onCommand:typescript.selectTypeScriptVersion", + "onCommand:javascript.goToProjectConfig", "onCommand:typescript.goToProjectConfig" ], "main": "./out/typescriptMain", @@ -258,19 +259,28 @@ "commands": [ { "command": "typescript.reloadProjects", - "title": "%typescript.reloadProjects.title%" + "title": "%typescript.reloadProjects.title%", + "category": "TypeScript" }, { "command": "javascript.reloadProjects", - "title": "%javascript.reloadProjects.title%" + "title": "%javascript.reloadProjects.title%", + "category": "JavaScript" }, { "command": "typescript.selectTypeScriptVersion", - "title": "%typescript.selectTypeScriptVersion.title%" + "title": "%typescript.selectTypeScriptVersion.title%", + "category": "TypeScript" }, { "command": "typescript.goToProjectConfig", - "title": "%typescript.goToProjectConfig.title%" + "title": "%typescript.goToProjectConfig.title%", + "category": "TypeScript" + }, + { + "command": "javascript.goToProjectConfig", + "title": "%javascript.goToProjectConfig.title%", + "category": "JavaScript" } ], "breakpoints": [ @@ -306,4 +316,4 @@ } ] } -} +} \ No newline at end of file diff --git a/extensions/typescript/package.nls.json b/extensions/typescript/package.nls.json index 716231fca9da2..d925d83f77964 100644 --- a/extensions/typescript/package.nls.json +++ b/extensions/typescript/package.nls.json @@ -1,6 +1,6 @@ { - "typescript.reloadProjects.title": "Reload TypeScript Project", - "javascript.reloadProjects.title": "Reload JavaScript Project", + "typescript.reloadProjects.title": "Reload Project", + "javascript.reloadProjects.title": "Reload Project", "configuration.typescript": "TypeScript", "typescript.useCodeSnippetsOnMethodSuggest.dec": "Complete functions with their parameter signature.", "typescript.tsdk.desc": "Specifies the folder path containing the tsserver and lib*.d.ts files to use.", @@ -24,7 +24,8 @@ "format.placeOpenBraceOnNewLineForFunctions": "Defines whether an open brace is put onto a new line for functions or not.", "format.placeOpenBraceOnNewLineForControlBlocks": "Defines whether an open brace is put onto a new line for control blocks or not.", "javascript.validate.enable": "Enable/disable JavaScript validation.", - "typescript.goToProjectConfig.title": "Go to project configuration", + "typescript.goToProjectConfig.title": "Go to Project Configuration", + "javascript.goToProjectConfig.title": "Go to Project Configuration", "typescript.referencesCodeLens.enabled": "Enable/disable the references code lens.", - "typescript.selectTypeScriptVersion.title": "Select TypeScript version" + "typescript.selectTypeScriptVersion.title": "Select TypeScript Version" } \ No newline at end of file diff --git a/extensions/typescript/src/typescriptMain.ts b/extensions/typescript/src/typescriptMain.ts index 0f3bc36a92a3c..5154b73ca3fe4 100644 --- a/extensions/typescript/src/typescriptMain.ts +++ b/extensions/typescript/src/typescriptMain.ts @@ -9,7 +9,7 @@ * ------------------------------------------------------------------------------------------ */ 'use strict'; -import { env, languages, commands, workspace, window, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, DocumentFilter, Disposable, Uri, QuickPickItem, TextEditor } from 'vscode'; +import { env, languages, commands, workspace, window, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, DocumentFilter, Disposable, Uri, MessageItem, TextEditor } from 'vscode'; // This must be the first statement otherwise modules might got loaded with // the wrong locale. @@ -59,7 +59,7 @@ enum ProjectConfigAction { LearnMore } -interface ProjectConfigQuickPick extends QuickPickItem { +interface ProjectConfigMessageItem extends MessageItem { id: ProjectConfigAction; } @@ -100,14 +100,14 @@ export function activate(context: ExtensionContext): void { client.onVersionStatusClicked(); })); - context.subscriptions.push(commands.registerCommand('typescript.goToProjectConfig', () => { + const goToProjectConfig = (isTypeScript: boolean) => { const editor = window.activeTextEditor; - if (!editor) { - return; + if (editor) { + clientHost.goToProjectConfig(isTypeScript, editor.document.uri); } - - clientHost.goToProjectConfig(editor.document.uri, editor.document.languageId); - })); + }; + context.subscriptions.push(commands.registerCommand('typescript.goToProjectConfig', goToProjectConfig.bind(null, true))); + context.subscriptions.push(commands.registerCommand('javascript.goToProjectConfig', goToProjectConfig.bind(null, false))); window.onDidChangeActiveTextEditor(VersionStatus.showHideStatus, null, context.subscriptions); client.onReady().then(() => { @@ -398,58 +398,61 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { return !!this.findLanguage(file); } - public goToProjectConfig(resource: Uri, languageId: string): Thenable | undefined { + public goToProjectConfig(isTypeScriptProject: boolean, resource: Uri): Thenable | undefined { const rootPath = workspace.rootPath; - if (!this.languagePerId[languageId] || !rootPath) { - return undefined; + if (!rootPath) { + window.showInformationMessage( + localize( + 'typescript.projectConfigNoWorkspace', + 'Please open a folder in VS Code to use a TypeScript or JavaScript project')); + return; } const file = this.client.normalizePath(resource); if (!file) { - return undefined; + window.showWarningMessage( + localize( + 'typescript.projectConfigUnsupportedFile', + 'Could not determine TypeScript or JavaScript project. Unsupported file type')); + return; } - const args: protocol.ProjectInfoRequestArgs = { - file: file, - needFileNameList: false - }; - return this.client.execute('projectInfo', args).then(res => { + + return this.client.execute('projectInfo', { file, needFileNameList: false }).then(res => { if (!res || !res.body) { - return undefined; + return window.showWarningMessage(localize('typescript.projectConfigCouldNotGetInfo', 'Could not determine TypeScript or JavaScript project')); } - const {configFileName} = res.body; + const { configFileName } = res.body; if (configFileName && configFileName.indexOf('/dev/null/') !== 0) { return workspace.openTextDocument(configFileName) .then(window.showTextDocument); } - const isJsProject = languageId === 'javascript' || languageId === 'javascriptreact'; - return window.showQuickPick([ - { - label: isJsProject - ? localize('typescript.configureJsconfigQuickPick', 'Configure jsconfig.json') - : localize('typescript.configureTsconfigQuickPick', 'Configure tsconfig.json'), - description: '', - id: ProjectConfigAction.CreateConfig, + return window.showInformationMessage( + (isTypeScriptProject + ? localize('typescript.noTypeScriptProjectConfig', 'File is not part of a TypeScript project') + : localize('typescript.noJavaScriptProjectConfig', 'File is not part of a JavaScript project') + ), { + title: isTypeScriptProject + ? localize('typescript.configureTsconfigQuickPick', 'Configure tsconfig.json') + : localize('typescript.configureJsconfigQuickPick', 'Configure jsconfig.json'), + id: ProjectConfigAction.CreateConfig }, { - label: localize('typescript.projectConfigLearnMore', 'Learn More'), - description: '', + title: localize('typescript.projectConfigLearnMore', 'Learn More'), id: ProjectConfigAction.LearnMore - }], { - placeHolder: localize('typescript.noProjectConfigPlaceholder', 'File is not part of a project') }).then(selected => { switch (selected && selected.id) { case ProjectConfigAction.CreateConfig: - const configFile = Uri.file(path.join(rootPath, isJsProject ? 'jsconfig.json' : 'tsconfig.json')); + const configFile = Uri.file(path.join(rootPath, isTypeScriptProject ? 'tsconfig.json' : 'jsconfig.json')); return workspace.openTextDocument(configFile) .then(undefined, _ => workspace.openTextDocument(configFile.with({ scheme: 'untitled' }))) .then(window.showTextDocument); case ProjectConfigAction.LearnMore: - if (isJsProject) { - commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=759670')); - } else { + if (isTypeScriptProject) { commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=841896')); + } else { + commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=759670')); } return; From 49a50c08fc2aae7c6d2425b29b200558cf2119e6 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 10 Feb 2017 15:17:31 -0800 Subject: [PATCH 3/3] Work around ts error --- extensions/typescript/src/typescriptMain.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/extensions/typescript/src/typescriptMain.ts b/extensions/typescript/src/typescriptMain.ts index 5154b73ca3fe4..dad29a5ea3f7a 100644 --- a/extensions/typescript/src/typescriptMain.ts +++ b/extensions/typescript/src/typescriptMain.ts @@ -103,7 +103,7 @@ export function activate(context: ExtensionContext): void { const goToProjectConfig = (isTypeScript: boolean) => { const editor = window.activeTextEditor; if (editor) { - clientHost.goToProjectConfig(isTypeScript, editor.document.uri); + clientHost.goToProjectConfig(isTypeScript, editor.document.uri, editor.document.languageId); } }; context.subscriptions.push(commands.registerCommand('typescript.goToProjectConfig', goToProjectConfig.bind(null, true))); @@ -398,7 +398,11 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { return !!this.findLanguage(file); } - public goToProjectConfig(isTypeScriptProject: boolean, resource: Uri): Thenable | undefined { + public goToProjectConfig( + isTypeScriptProject: boolean, + resource: Uri, + languageId: string + ): Thenable | undefined { const rootPath = workspace.rootPath; if (!rootPath) { window.showInformationMessage( @@ -409,7 +413,8 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { } const file = this.client.normalizePath(resource); - if (!file) { + // TODO: TSServer errors when 'projectInfo' is invoked on a non js/ts file + if (!file || !this.languagePerId[languageId]) { window.showWarningMessage( localize( 'typescript.projectConfigUnsupportedFile',