mirror of
https://github.com/microsoft/vscode.git
synced 2026-10-02 14:04:01 +01:00
Add TSC Task Provider (#27093)
* extract standardLanguageDescriptions to constant * Remove client variable * Move VersionStatus into a class * Add TSC Task Provider Fixes #26079 Adds a task provider for building typescript projects. * Make ts loading lazy
This commit is contained in:
committed by
Dirk Baeumer
parent
5860e3dcad
commit
64d676ccf7
@@ -34,7 +34,10 @@
|
||||
"onCommand:typescript.selectTypeScriptVersion",
|
||||
"onCommand:javascript.goToProjectConfig",
|
||||
"onCommand:typescript.goToProjectConfig",
|
||||
"onCommand:typescript.openTsServerLog"
|
||||
"onCommand:typescript.openTsServerLog",
|
||||
"onCommand:workbench.action.tasks.runTask",
|
||||
"onCommand:workbench.action.tasks.build",
|
||||
"onCommand:workbench.action.tasks.test"
|
||||
],
|
||||
"main": "./out/typescriptMain",
|
||||
"contributes": {
|
||||
|
||||
@@ -88,7 +88,7 @@ export class TryCompleteJsDocCommand {
|
||||
static COMMAND_NAME = '_typeScript.tryCompleteJsDoc';
|
||||
|
||||
constructor(
|
||||
private client: ITypescriptServiceClient
|
||||
private lazyClient: () => ITypescriptServiceClient
|
||||
) { }
|
||||
|
||||
/**
|
||||
@@ -96,7 +96,7 @@ export class TryCompleteJsDocCommand {
|
||||
* if possible, otherwise falling back to a default comment format.
|
||||
*/
|
||||
public tryCompleteJsDoc(resource: Uri, start: Position, shouldGetJSDocFromTSServer: boolean): Thenable<boolean> {
|
||||
const file = this.client.normalizePath(resource);
|
||||
const file = this.lazyClient().normalizePath(resource);
|
||||
if (!file) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
@@ -126,7 +126,7 @@ export class TryCompleteJsDocCommand {
|
||||
offset: position.character + 1
|
||||
};
|
||||
return Promise.race([
|
||||
this.client.execute('docCommentTemplate', args),
|
||||
this.lazyClient().execute('docCommentTemplate', args),
|
||||
new Promise((_, reject) => setTimeout(reject, 250))
|
||||
]).then((res: DocCommandTemplateResponse) => {
|
||||
if (!res || !res.body) {
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import * as Proto from '../protocol';
|
||||
import TypeScriptServiceClient from '../typescriptServiceClient';
|
||||
|
||||
|
||||
const exists = (file: string): Promise<boolean> =>
|
||||
new Promise<boolean>((resolve, _reject) => {
|
||||
fs.exists(file, (value: boolean) => {
|
||||
resolve(value);
|
||||
});
|
||||
});
|
||||
|
||||
export default class TypeScriptTaskProvider implements vscode.TaskProvider {
|
||||
|
||||
public constructor(
|
||||
private readonly lazyClient: () => TypeScriptServiceClient
|
||||
) { }
|
||||
|
||||
async provideTasks(token: vscode.CancellationToken): Promise<vscode.Task[]> {
|
||||
const rootPath = vscode.workspace.rootPath;
|
||||
if (!rootPath) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const projects = (await this.getConfigForActiveFile(token)).concat(await this.getConfigsForWorkspace());
|
||||
const command = await this.getCommand();
|
||||
|
||||
return projects
|
||||
.filter((x, i) => projects.indexOf(x) === i)
|
||||
.map(configFile => {
|
||||
const configFileName = path.relative(rootPath, configFile);
|
||||
const buildTask = new vscode.ShellTask(`tsc: build ${configFileName}`, `${command} -p ${configFile}`, '$tsc');
|
||||
buildTask.group = vscode.TaskGroup.Build;
|
||||
return buildTask;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private async getConfigForActiveFile(token: vscode.CancellationToken): Promise<string[]> {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (editor) {
|
||||
if (path.basename(editor.document.fileName).match(/^tsconfig\.(.\.)?json$/)) {
|
||||
return [editor.document.fileName];
|
||||
}
|
||||
}
|
||||
|
||||
const file = this.getActiveTypeScriptFile();
|
||||
if (!file) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const res: Proto.ProjectInfoResponse = await this.lazyClient().execute(
|
||||
'projectInfo',
|
||||
{ file, needFileNameList: false } as protocol.ProjectInfoRequestArgs,
|
||||
token);
|
||||
|
||||
if (!res || !res.body) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { configFileName } = res.body;
|
||||
if (configFileName && configFileName.indexOf('/dev/null/') !== 0) {
|
||||
return [configFileName];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private async getConfigsForWorkspace(): Promise<string[]> {
|
||||
if (!vscode.workspace.rootPath) {
|
||||
return [];
|
||||
}
|
||||
const rootTsConfig = path.join(vscode.workspace.rootPath, 'tsconfig.json');
|
||||
if (!await exists(rootTsConfig)) {
|
||||
return [];
|
||||
}
|
||||
return [rootTsConfig];
|
||||
}
|
||||
|
||||
private async getCommand(): Promise<string> {
|
||||
const platform = process.platform;
|
||||
if (platform === 'win32' && await exists(path.join(vscode.workspace.rootPath!, 'node_modules', '.bin', 'tsc.cmd'))) {
|
||||
return path.join('.', 'node_modules', '.bin', 'tsc.cmd');
|
||||
} else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(vscode.workspace.rootPath!, 'node_modules', '.bin', 'tsc'))) {
|
||||
return path.join('.', 'node_modules', '.bin', 'tsc');
|
||||
} else {
|
||||
return 'tsc';
|
||||
}
|
||||
}
|
||||
|
||||
private getActiveTypeScriptFile(): string | null {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (editor) {
|
||||
const document = editor.document;
|
||||
if (document && (document.languageId === 'typescript' || document.languageId === 'typescriptreact')) {
|
||||
return this.lazyClient().normalizePath(document.uri);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -41,13 +41,14 @@ import CodeActionProvider from './features/codeActionProvider';
|
||||
import ReferenceCodeLensProvider from './features/referencesCodeLensProvider';
|
||||
import { JsDocCompletionProvider, TryCompleteJsDocCommand } from './features/jsDocCompletionProvider';
|
||||
import { DirectiveCommentCompletionProvider } from './features/directiveCommentCompletionProvider';
|
||||
import TypeScriptTaskProvider from './features/taskProvider';
|
||||
|
||||
import ImplementationCodeLensProvider from './features/implementationsCodeLensProvider';
|
||||
|
||||
import * as BuildStatus from './utils/buildStatus';
|
||||
import * as ProjectStatus from './utils/projectStatus';
|
||||
import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus';
|
||||
import * as VersionStatus from './utils/versionStatus';
|
||||
import VersionStatus from './utils/versionStatus';
|
||||
import { getContributedTypeScriptServerPlugins, TypeScriptServerPlugin } from "./utils/plugins";
|
||||
|
||||
interface LanguageDescription {
|
||||
@@ -67,72 +68,100 @@ interface ProjectConfigMessageItem extends MessageItem {
|
||||
id: ProjectConfigAction;
|
||||
}
|
||||
|
||||
const MODE_ID_TS = 'typescript';
|
||||
const MODE_ID_TSX = 'typescriptreact';
|
||||
const MODE_ID_JS = 'javascript';
|
||||
const MODE_ID_JSX = 'javascriptreact';
|
||||
|
||||
const standardLanguageDescriptions: LanguageDescription[] = [
|
||||
{
|
||||
id: 'typescript',
|
||||
diagnosticSource: 'ts',
|
||||
modeIds: [MODE_ID_TS, MODE_ID_TSX],
|
||||
configFile: 'tsconfig.json'
|
||||
}, {
|
||||
id: 'javascript',
|
||||
diagnosticSource: 'js',
|
||||
modeIds: [MODE_ID_JS, MODE_ID_JSX],
|
||||
configFile: 'jsconfig.json'
|
||||
}
|
||||
];
|
||||
|
||||
export function activate(context: ExtensionContext): void {
|
||||
const MODE_ID_TS = 'typescript';
|
||||
const MODE_ID_TSX = 'typescriptreact';
|
||||
const MODE_ID_JS = 'javascript';
|
||||
const MODE_ID_JSX = 'javascriptreact';
|
||||
|
||||
const plugins = getContributedTypeScriptServerPlugins();
|
||||
const clientHost = new TypeScriptServiceClientHost([
|
||||
{
|
||||
id: 'typescript',
|
||||
diagnosticSource: 'ts',
|
||||
modeIds: [MODE_ID_TS, MODE_ID_TSX],
|
||||
configFile: 'tsconfig.json'
|
||||
},
|
||||
{
|
||||
id: 'javascript',
|
||||
diagnosticSource: 'js',
|
||||
modeIds: [MODE_ID_JS, MODE_ID_JSX],
|
||||
configFile: 'jsconfig.json'
|
||||
}
|
||||
], context.storagePath, context.globalState, context.workspaceState, plugins);
|
||||
context.subscriptions.push(clientHost);
|
||||
|
||||
const client = clientHost.serviceClient;
|
||||
const lazyClientHost = (() => {
|
||||
let clientHost: TypeScriptServiceClientHost | undefined;
|
||||
return () => {
|
||||
if (!clientHost) {
|
||||
clientHost = new TypeScriptServiceClientHost(standardLanguageDescriptions, context.storagePath, context.globalState, context.workspaceState, plugins);
|
||||
context.subscriptions.push(clientHost);
|
||||
|
||||
const host = clientHost;
|
||||
clientHost.serviceClient.onReady().then(() => {
|
||||
context.subscriptions.push(ProjectStatus.create(host.serviceClient,
|
||||
path => new Promise<boolean>(resolve => setTimeout(() => resolve(host.handles(path)), 750)),
|
||||
context.workspaceState));
|
||||
}, () => {
|
||||
// Nothing to do here. The client did show a message;
|
||||
});
|
||||
}
|
||||
return clientHost;
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
context.subscriptions.push(commands.registerCommand('typescript.reloadProjects', () => {
|
||||
clientHost.reloadProjects();
|
||||
lazyClientHost().reloadProjects();
|
||||
}));
|
||||
|
||||
context.subscriptions.push(commands.registerCommand('javascript.reloadProjects', () => {
|
||||
clientHost.reloadProjects();
|
||||
lazyClientHost().reloadProjects();
|
||||
}));
|
||||
|
||||
context.subscriptions.push(commands.registerCommand('typescript.selectTypeScriptVersion', () => {
|
||||
client.onVersionStatusClicked();
|
||||
lazyClientHost().serviceClient.onVersionStatusClicked();
|
||||
}));
|
||||
|
||||
context.subscriptions.push(commands.registerCommand('typescript.openTsServerLog', () => {
|
||||
client.openTsServerLogFile();
|
||||
lazyClientHost().serviceClient.openTsServerLogFile();
|
||||
}));
|
||||
|
||||
context.subscriptions.push(commands.registerCommand('typescript.restartTsServer', () => {
|
||||
client.restartTsServer();
|
||||
lazyClientHost().serviceClient.restartTsServer();
|
||||
}));
|
||||
|
||||
context.subscriptions.push(workspace.registerTaskProvider(new TypeScriptTaskProvider(() => lazyClientHost().serviceClient)));
|
||||
|
||||
const goToProjectConfig = (isTypeScript: boolean) => {
|
||||
const editor = window.activeTextEditor;
|
||||
if (editor) {
|
||||
clientHost.goToProjectConfig(isTypeScript, editor.document.uri);
|
||||
lazyClientHost().goToProjectConfig(isTypeScript, editor.document.uri);
|
||||
}
|
||||
};
|
||||
context.subscriptions.push(commands.registerCommand('typescript.goToProjectConfig', goToProjectConfig.bind(null, true)));
|
||||
context.subscriptions.push(commands.registerCommand('javascript.goToProjectConfig', goToProjectConfig.bind(null, false)));
|
||||
|
||||
const jsDocCompletionCommand = new TryCompleteJsDocCommand(client);
|
||||
const jsDocCompletionCommand = new TryCompleteJsDocCommand(() => lazyClientHost().serviceClient);
|
||||
context.subscriptions.push(commands.registerCommand(TryCompleteJsDocCommand.COMMAND_NAME, jsDocCompletionCommand.tryCompleteJsDoc, jsDocCompletionCommand));
|
||||
|
||||
window.onDidChangeActiveTextEditor(VersionStatus.showHideStatus, null, context.subscriptions);
|
||||
client.onReady().then(() => {
|
||||
context.subscriptions.push(ProjectStatus.create(client,
|
||||
path => new Promise<boolean>(resolve => setTimeout(() => resolve(clientHost.handles(path)), 750)),
|
||||
context.workspaceState));
|
||||
}, () => {
|
||||
// Nothing to do here. The client did show a message;
|
||||
});
|
||||
const supportedLanguage = [].concat.apply([], standardLanguageDescriptions.map(x => x.modeIds).concat(plugins.map(x => x.languages)));
|
||||
function didOpenTextDocument(textDocument: TextDocument): boolean {
|
||||
if (supportedLanguage.indexOf(textDocument.languageId) >= 0) {
|
||||
openListener.dispose();
|
||||
// Force activation
|
||||
void lazyClientHost();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const openListener = workspace.onDidOpenTextDocument(didOpenTextDocument);
|
||||
for (let textDocument of workspace.textDocuments) {
|
||||
if (didOpenTextDocument(textDocument)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BuildStatus.update({ queueLength: 0 });
|
||||
}
|
||||
|
||||
@@ -423,6 +452,7 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
|
||||
private languages: LanguageProvider[] = [];
|
||||
private languagePerId: ObjectMap<LanguageProvider>;
|
||||
private readonly disposables: Disposable[] = [];
|
||||
private readonly versionStatus: VersionStatus;
|
||||
|
||||
constructor(
|
||||
descriptions: LanguageDescription[],
|
||||
@@ -446,7 +476,10 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
|
||||
configFileWatcher.onDidDelete(handleProjectCreateOrDelete, this, this.disposables);
|
||||
configFileWatcher.onDidChange(handleProjectChange, this, this.disposables);
|
||||
|
||||
this.client = new TypeScriptServiceClient(this, storagePath, globalState, workspaceState, plugins, this.disposables);
|
||||
this.versionStatus = new VersionStatus();
|
||||
this.disposables.push(this.versionStatus);
|
||||
|
||||
this.client = new TypeScriptServiceClient(this, storagePath, globalState, workspaceState, this.versionStatus, plugins, this.disposables);
|
||||
this.languagePerId = Object.create(null);
|
||||
for (const description of descriptions) {
|
||||
const manager = new LanguageProvider(this.client, description);
|
||||
|
||||
@@ -18,7 +18,7 @@ import { ITypescriptServiceClient, ITypescriptServiceClientHost, API } from './t
|
||||
import { TypeScriptServerPlugin } from './utils/plugins';
|
||||
import Logger from './utils/logger';
|
||||
|
||||
import * as VersionStatus from './utils/versionStatus';
|
||||
import VersionStatus from './utils/versionStatus';
|
||||
import * as is from './utils/is';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
@@ -141,7 +141,9 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
|
||||
host: ITypescriptServiceClientHost,
|
||||
storagePath: string | undefined,
|
||||
globalState: Memento,
|
||||
private workspaceState: Memento,
|
||||
private readonly workspaceState: Memento,
|
||||
private readonly versionStatus: VersionStatus,
|
||||
|
||||
private plugins: TypeScriptServerPlugin[],
|
||||
disposables: Disposable[]
|
||||
) {
|
||||
@@ -404,8 +406,8 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
|
||||
const label = version || localize('versionNumber.custom', 'custom');
|
||||
const tooltip = modulePath;
|
||||
this.modulePath = modulePath;
|
||||
VersionStatus.showHideStatus();
|
||||
VersionStatus.setInfo(label, tooltip);
|
||||
this.versionStatus.showHideStatus();
|
||||
this.versionStatus.setInfo(label, tooltip);
|
||||
|
||||
// This is backwards compatibility code to move the setting from the local
|
||||
// store into the workspace setting file.
|
||||
|
||||
@@ -3,42 +3,53 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import vscode = require('vscode');
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
const versionBarEntry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE);
|
||||
|
||||
export function showHideStatus() {
|
||||
if (!versionBarEntry) {
|
||||
return;
|
||||
}
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
versionBarEntry.hide();
|
||||
return;
|
||||
export default class VersionStatus extends vscode.Disposable {
|
||||
onChangeEditorSub: any;
|
||||
private versionBarEntry: vscode.StatusBarItem;
|
||||
|
||||
constructor() {
|
||||
super(() => this.dispose());
|
||||
|
||||
this.versionBarEntry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE);
|
||||
|
||||
this.onChangeEditorSub = vscode.window.onDidChangeActiveTextEditor(this.showHideStatus, this);
|
||||
}
|
||||
|
||||
let doc = vscode.window.activeTextEditor.document;
|
||||
if (vscode.languages.match('typescript', doc) || vscode.languages.match('typescriptreact', doc)) {
|
||||
versionBarEntry.show();
|
||||
return;
|
||||
dispose() {
|
||||
this.versionBarEntry.dispose();
|
||||
this.onChangeEditorSub.dispose();
|
||||
}
|
||||
|
||||
if (!vscode.window.activeTextEditor.viewColumn) {
|
||||
// viewColumn is undefined for the debug/output panel, but we still want
|
||||
// to show the version info
|
||||
return;
|
||||
showHideStatus() {
|
||||
if (!this.versionBarEntry) {
|
||||
return;
|
||||
}
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
this.versionBarEntry.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
let doc = vscode.window.activeTextEditor.document;
|
||||
if (vscode.languages.match('typescript', doc) || vscode.languages.match('typescriptreact', doc)) {
|
||||
this.versionBarEntry.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vscode.window.activeTextEditor.viewColumn) {
|
||||
// viewColumn is undefined for the debug/output panel, but we still want
|
||||
// to show the version info
|
||||
return;
|
||||
}
|
||||
|
||||
this.versionBarEntry.hide();
|
||||
}
|
||||
|
||||
versionBarEntry.hide();
|
||||
}
|
||||
|
||||
export function disposeStatus() {
|
||||
if (versionBarEntry) {
|
||||
versionBarEntry.dispose();
|
||||
public setInfo(message: string, tooltip: string) {
|
||||
this.versionBarEntry.text = message;
|
||||
this.versionBarEntry.tooltip = tooltip;
|
||||
this.versionBarEntry.command = 'typescript.selectTypeScriptVersion';
|
||||
}
|
||||
}
|
||||
|
||||
export function setInfo(message: string, tooltip: string) {
|
||||
versionBarEntry.text = message;
|
||||
versionBarEntry.tooltip = tooltip;
|
||||
versionBarEntry.command = 'typescript.selectTypeScriptVersion';
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ namespace ProblemMatcher {
|
||||
if (values === void 0 || values === null) {
|
||||
return undefined;
|
||||
}
|
||||
let result: (string | Problems.ProblemMatcher)[];
|
||||
let result: (string | Problems.ProblemMatcher)[] = [];
|
||||
for (let value of values) {
|
||||
let converted = typeof value === 'string' ? value : fromSingle(value);
|
||||
if (converted) {
|
||||
|
||||
Reference in New Issue
Block a user