diff --git a/.vscode/settings.json b/.vscode/settings.json index 489d87815c8..0e1419e7e55 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,6 @@ "extensions/**/out/**": true }, "tslint.enable": true, - "tslint.rulesDirectory": "build/lib/tslint" + "tslint.rulesDirectory": "build/lib/tslint", + "typescript.tsdk": "P:\\mseng\\TypeScript\\built\\local" } \ No newline at end of file diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index 08ac39137fd..e7aef0fcba1 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -71,6 +71,17 @@ "default": null, "description": "%typescript.tsdk.desc%" }, + "typescript.tsserver.trace": { + "type": "string", + "enum": ["off", "messages", "verbose"], + "default": "off", + "description": "%typescript.tsserver.trace%" + }, + "typescript.tsserver.experimentalAutoBuild": { + "type": "boolean", + "default": false, + "description": "%typescript.tsserver.experimentalAutoBuild%" + }, "typescript.useCodeSnippetsOnMethodSuggest": { "type": "boolean", "default": false, @@ -81,12 +92,6 @@ "default": true, "description": "%typescript.validate.enable%" }, - "typescript.tsserver.trace": { - "type": "string", - "enum": ["off", "messages", "verbose"], - "default": "off", - "description": "%typescript.tsserver.trace%" - }, "typescript.format.insertSpaceAfterCommaDelimiter": { "type": "boolean", "default": true, diff --git a/extensions/typescript/package.nls.json b/extensions/typescript/package.nls.json index e4ddd76d86f..56b9b09113c 100644 --- a/extensions/typescript/package.nls.json +++ b/extensions/typescript/package.nls.json @@ -5,6 +5,7 @@ "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.", "typescript.tsserver.trace": "Enables tracing of messages send to the TS server", + "typescript.tsserver.experimentalAutoBuild": "Enables experimental auto build. Requires 1.9 dev or 2.x tsserver version and a restart of VS Code after changing it.", "typescript.validate.enable": "Enable / disable TypeScript validation", "format.insertSpaceAfterCommaDelimiter": "Defines space handling after a comma delimiter", "format.insertSpaceAfterSemicolonInForStatements": " Defines space handling after a semicolon in a for statement", diff --git a/extensions/typescript/src/features/bufferSyncSupport.ts b/extensions/typescript/src/features/bufferSyncSupport.ts index 4225c0c5428..b51fae64f3a 100644 --- a/extensions/typescript/src/features/bufferSyncSupport.ts +++ b/extensions/typescript/src/features/bufferSyncSupport.ts @@ -85,6 +85,8 @@ export default class BufferSyncSupport { private syncedBuffers: Map; private closedFiles: Map; + private projectValidationRequested: boolean; + private pendingDiagnostics: { [key: string]: number; }; private diagnosticDelayer: Delayer; @@ -96,6 +98,8 @@ export default class BufferSyncSupport { this.extensions = extensions; this._validate = validate; + this.projectValidationRequested = false; + this.pendingDiagnostics = Object.create(null); this.diagnosticDelayer = new Delayer(100); @@ -210,9 +214,10 @@ export default class BufferSyncSupport { } public requestDiagnostic(file: string): void { - if (!this._validate) { + if (!this._validate || this.client.experimentalAutoBuild) { return; } + this.pendingDiagnostics[file] = Date.now(); this.diagnosticDelayer.trigger(() => { this.sendPendingDiagnostics(); diff --git a/extensions/typescript/src/protocol.d.ts b/extensions/typescript/src/protocol.d.ts index d3496e50373..17c6c327705 100644 --- a/extensions/typescript/src/protocol.d.ts +++ b/extensions/typescript/src/protocol.d.ts @@ -484,6 +484,19 @@ export interface ConfigureRequestArguments { */ hostInfo?: string; + /** + * Sets a folder that can be used by the tsserver to store + * meta data information. + */ + metaDataDirectory?: string; + + /** + * Turns the tsserver into auto diagnostice mode. When in + * auto diagnostic mode the server will automatically generate + * diagnostics and send them to the client without requests + */ + autoDiagnostics?: boolean; + /** * If present, tab settings apply only to this file. */ @@ -996,6 +1009,11 @@ export interface DiagnosticEventBody { * An array of diagnostic information items. */ diagnostics: Diagnostic[]; + + /** + * Information about the current length of the build queue. + */ + queueLength?: number; } /** diff --git a/extensions/typescript/src/typescriptMain.ts b/extensions/typescript/src/typescriptMain.ts index a336d72f4b0..9a7e581b38f 100644 --- a/extensions/typescript/src/typescriptMain.ts +++ b/extensions/typescript/src/typescriptMain.ts @@ -19,6 +19,9 @@ nls.config({locale: env.language}); import * as path from 'path'; import * as Proto from './protocol'; + +import * as Is from './utils/is'; + import TypeScriptServiceClient from './typescriptServiceClient'; import { ITypescriptServiceClientHost } from './typescriptService'; @@ -36,6 +39,7 @@ import WorkspaceSymbolProvider from './features/workspaceSymbolProvider'; import * as VersionStatus from './utils/versionStatus'; import * as ProjectStatus from './utils/projectStatus'; +import * as BuildStatus from './utils/buildStatus'; interface LanguageDescription { id: string; @@ -63,7 +67,7 @@ export function activate(context: ExtensionContext): void { modeIds: [MODE_ID_JS, MODE_ID_JSX], extensions: ['.js', '.jsx'] } - ]); + ], context.storagePath); let client = clientHost.serviceClient; @@ -83,6 +87,7 @@ export function activate(context: ExtensionContext): void { }, () => { // Nothing to do here. The client did show a message; }); + BuildStatus.update({ queueLength: 0 }); } const validateSetting = 'validate.enable'; @@ -285,7 +290,7 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { private languages: LanguageProvider[]; private languagePerId: Map; - constructor(descriptions: LanguageDescription[]) { + constructor(descriptions: LanguageDescription[], storagePath: string) { let handleProjectCreateOrDelete = () => { this.client.execute('reloadProjects', null, false); this.triggerAllDiagnostics(); @@ -300,7 +305,7 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { watcher.onDidDelete(handleProjectCreateOrDelete); watcher.onDidChange(handleProjectChange); - this.client = new TypeScriptServiceClient(this); + this.client = new TypeScriptServiceClient(this, storagePath); this.languages = []; this.languagePerId = Object.create(null); descriptions.forEach(description => { @@ -362,6 +367,9 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { language.semanticDiagnosticsReceived(body.file, this.createMarkerDatas(body.diagnostics, language.diagnosticSource)); } } + if (Is.defined(body.queueLength)) { + BuildStatus.update( { queueLength: body.queueLength }); + } } private createMarkerDatas(diagnostics: Proto.Diagnostic[], source: string): Diagnostic[] { diff --git a/extensions/typescript/src/typescriptService.ts b/extensions/typescript/src/typescriptService.ts index 9749c1990bb..d2d66153304 100644 --- a/extensions/typescript/src/typescriptService.ts +++ b/extensions/typescript/src/typescriptService.ts @@ -20,6 +20,8 @@ export interface ITypescriptServiceClient { logTelemetry(eventName: string, properties?: { [prop: string]: string }); + experimentalAutoBuild: boolean; + execute(command:'configure', args: Proto.ConfigureRequestArguments, token?: CancellationToken):Promise; execute(command:'open', args: Proto.OpenRequestArgs, expectedResult:boolean, token?: CancellationToken):Promise; execute(command:'close', args: Proto.FileRequestArgs, expectedResult:boolean, token?: CancellationToken):Promise; diff --git a/extensions/typescript/src/typescriptServiceClient.ts b/extensions/typescript/src/typescriptServiceClient.ts index b2ad1006d47..77aa0798221 100644 --- a/extensions/typescript/src/typescriptServiceClient.ts +++ b/extensions/typescript/src/typescriptServiceClient.ts @@ -68,10 +68,12 @@ namespace Trace { export default class TypeScriptServiceClient implements ITypescriptServiceClient { private host: ITypescriptServiceClientHost; + private storagePath: string; private pathSeparator: string; private _onReady: { promise: Promise; resolve: () => void; reject: () => void; }; private tsdk: string; + private _experimentalAutoBuild: boolean; private trace: Trace; private output: OutputChannel; private servicePromise: Promise; @@ -90,8 +92,9 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient private _packageInfo: IPackageInfo; private telemetryReporter: TelemetryReporter; - constructor(host: ITypescriptServiceClientHost) { + constructor(host: ITypescriptServiceClientHost, storagePath: string) { this.host = host; + this.storagePath = storagePath; this.pathSeparator = path.sep; let p = new Promise((resolve, reject) => { @@ -109,7 +112,9 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient this.requestQueue = []; this.pendingResponses = 0; this.callbacks = Object.create(null); - this.tsdk = workspace.getConfiguration().get('typescript.tsdk', null); + const configuration = workspace.getConfiguration(); + this.tsdk = configuration.get('typescript.tsdk', null); + this._experimentalAutoBuild = configuration.get('typescript.tsserver.experimentalAutoBuild', false); this.trace = this.readTrace(); workspace.onDidChangeConfiguration(() => { this.trace = this.readTrace(); @@ -136,6 +141,10 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient return result; } + public get experimentalAutoBuild(): boolean { + return this._experimentalAutoBuild; + } + public onReady(): Promise { return this._onReady.promise; } @@ -201,7 +210,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient this.servicePromise = new Promise((resolve, reject) => { try { let options: electron.IForkOptions = { - execArgv: [] //[`--debug-brk=5859`] + execArgv: [`--debug-brk=5859`] }; let value = process.env.TSS_DEBUG; if (value) { @@ -239,6 +248,16 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient } private serviceStarted(resendModels: boolean): void { + if (this._experimentalAutoBuild && this.storagePath) { + try { + fs.mkdirSync(this.storagePath); + } catch(error) { + } + this.execute('configure', { + autoBuild: true, + metaDataDirectory: this.storagePath + }); + } if (resendModels) { this.host.populateService(); } diff --git a/extensions/typescript/src/utils/buildStatus.ts b/extensions/typescript/src/utils/buildStatus.ts new file mode 100644 index 00000000000..cf640508af3 --- /dev/null +++ b/extensions/typescript/src/utils/buildStatus.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * 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 vscode = require('vscode'); + +const statusItem: vscode.StatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, Number.MIN_VALUE); + +export interface BuildInfo { + queueLength: number; +} + +export function update(info: BuildInfo): void { + if (info.queueLength === 0) { + statusItem.hide(); + return; + } + statusItem.text = info.queueLength.toString(); + statusItem.show(); +} \ No newline at end of file diff --git a/src/tsconfig.json b/src/tsconfig.json index 7b7bdf30a99..20251441003 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,4 +1,5 @@ { + "autoDiagnostics": true, "compilerOptions": { "module": "amd", "moduleResolution": "classic", @@ -7,6 +8,7 @@ "preserveConstEnums": true, "target": "es5", "sourceMap": false, - "experimentalDecorators": true + "experimentalDecorators": true, + "declaration": true } } diff --git a/src/vs/platform/extensions/common/nativeExtensionService.ts b/src/vs/platform/extensions/common/nativeExtensionService.ts index 40adb7c2abc..2ed39569bdc 100644 --- a/src/vs/platform/extensions/common/nativeExtensionService.ts +++ b/src/vs/platform/extensions/common/nativeExtensionService.ts @@ -234,6 +234,7 @@ export interface IExtensionContext { workspaceState: IExtensionMemento; globalState: IExtensionMemento; extensionPath: string; + storagePath: string; asAbsolutePath(relativePath: string): string; } @@ -244,17 +245,19 @@ export class ExtHostExtensionService extends AbstractExtensionService { return Object.freeze({ @@ -330,6 +334,7 @@ export class ExtHostExtensionService extends AbstractExtensionService { return paths.normalize(paths.join(extensionDescription.extensionFolderPath, relativePath), true); } }); }); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 93cf6db550b..1f5ce84e876 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2955,6 +2955,14 @@ declare namespace vscode { * @return The absolute path of the resource. */ asAbsolutePath(relativePath: string): string; + + /** + * The absolute file path of the workspace specific directory where the extension + * can store proviate state on disk. The directory might not exist yet. + * + * Use `workspaceState` or `globalState` to store simple data. + */ + storagePath: string; } /** diff --git a/src/vs/workbench/node/extensionHostMain.ts b/src/vs/workbench/node/extensionHostMain.ts index 773f3af968a..e788c9b45f6 100644 --- a/src/vs/workbench/node/extensionHostMain.ts +++ b/src/vs/workbench/node/extensionHostMain.ts @@ -5,6 +5,9 @@ 'use strict'; +import * as fs from 'fs'; +import * as crypto from 'crypto'; + import nls = require('vs/nls'); import pfs = require('vs/base/node/pfs'); import URI from 'vs/base/common/uri'; @@ -58,11 +61,52 @@ export function createServices(remoteCom: IMainProcessExtHostIPC, initData: IIni threadService.setInstantiationService(new InstantiationService(new ServiceCollection([IThreadService, threadService]))); let telemetryService = new RemoteTelemetryService('pluginHostTelemetry', threadService); + let workspaceStoragePath: string; + const workspace = contextService.getWorkspace(); + const env = contextService.getConfiguration().env; + function rmkDir(directory: string): boolean { + console.log('Creating Directory ' + directory); + try { + fs.mkdirSync(directory); + return true; + } catch (err) { + if (err.code === 'ENOENT') { + if (rmkDir(paths.dirname(directory))) { + fs.mkdirSync(directory); + return true; + } + } else { + return fs.statSync(directory).isDirectory(); + } + } + } + if (workspace) { + const hash = crypto.createHash('md5'); + hash.update(workspace.resource.fsPath); + if (workspace.uid) { + hash.update(workspace.uid.toString()); + } + workspaceStoragePath = paths.join(env.appSettingsHome, 'workspaceStorage', hash.digest('hex')); + if (!fs.existsSync(workspaceStoragePath)) { + try { + if (rmkDir(workspaceStoragePath)) { + fs.writeFileSync(paths.join(workspaceStoragePath, 'meta.json'), JSON.stringify({ + workspacePath: workspace.resource.fsPath, + uid: workspace.uid ? workspace.uid : null + }, null, 4)); + } else { + workspaceStoragePath = undefined; + } + } catch (err) { + workspaceStoragePath = undefined; + } + } + } let services = new ServiceCollection(); services.set(IWorkspaceContextService, contextService); services.set(ITelemetryService, telemetryService); services.set(IThreadService, threadService); - services.set(IExtensionService, new ExtHostExtensionService(threadService, telemetryService)); + services.set(IExtensionService, new ExtHostExtensionService(threadService, telemetryService, {serviceId: 'optionalArgs', workspaceStoragePath })); // Connect to shared process services const channel = sharedProcessClient.getChannel('extensions');