Skip to content

Commit

Permalink
New tsdk flow for 19 (#19602)
Browse files Browse the repository at this point in the history
* Allow using workspace typescript.tsdk setting

Allows users to opt into using their workspace typescript.tsdk setting to specify the path to their typescript install.

Also, fixes a bug when the global tsdk setting points to the workspace version of typescript, our ts selector interface can get confused on which version is currently active. The fix adds a check using the local storage value to show the correct active version.

* Flip vscode and workspace pick order

* Use shared logic when workspace tsdk setting is not used
  • Loading branch information
mjbvz committed Jan 31, 2017
1 parent 6e03d49 commit fcac9b6
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 123 deletions.
3 changes: 1 addition & 2 deletions extensions/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@
"null"
],
"default": null,
"description": "%typescript.tsdk.desc%",
"isExecutable": true
"description": "%typescript.tsdk.desc%"
},
"typescript.disableAutomaticTypeAcquisition": {
"type": "boolean",
Expand Down
223 changes: 102 additions & 121 deletions extensions/typescript/src/typescriptServiceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import * as fs from 'fs';
import * as electron from './utils/electron';
import { Reader } from './utils/wireProtocol';

import { workspace, window, Uri, CancellationToken, OutputChannel, Memento, MessageItem, QuickPickItem, EventEmitter, Event, commands } from 'vscode';
import { workspace, window, Uri, CancellationToken, OutputChannel, Memento, MessageItem, QuickPickItem, EventEmitter, Event, commands, WorkspaceConfiguration } from 'vscode';
import * as Proto from './protocol';
import { ITypescriptServiceClient, ITypescriptServiceClientHost, API } from './typescriptService';

Expand Down Expand Up @@ -98,7 +98,8 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
private modulePath: string | undefined;

private _onReady: { promise: Promise<void>; resolve: () => void; reject: () => void; };
private tsdk: string | null;
private globalTsdk: string | null;
private localTsdk: string | null;
private _checkGlobalTSCVersion: boolean;
private _experimentalAutoBuild: boolean;
private trace: Trace;
Expand Down Expand Up @@ -145,16 +146,23 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
this.pendingResponses = 0;
this.callbacks = Object.create(null);
const configuration = workspace.getConfiguration();
this.tsdk = configuration.get<string | null>('typescript.tsdk', null);
this.globalTsdk = this.extractGlobalTsdk(configuration);
this.localTsdk = this.extractLocalTsdk(configuration);

this._experimentalAutoBuild = false; // configuration.get<boolean>('typescript.tsserver.experimentalAutoBuild', false);
this._apiVersion = new API('1.0.0');
this._checkGlobalTSCVersion = true;
this.trace = this.readTrace();
workspace.onDidChangeConfiguration(() => {
this.trace = this.readTrace();
let oldTsdk = this.tsdk;
this.tsdk = workspace.getConfiguration().get<string | null>('typescript.tsdk', null);
if (this.servicePromise === null && oldTsdk !== this.tsdk) {
let oldglobalTsdk = this.globalTsdk;
let oldLocalTsdk = this.localTsdk;

const configuration = workspace.getConfiguration();
this.globalTsdk = this.extractGlobalTsdk(configuration);
this.localTsdk = this.extractLocalTsdk(configuration);

if (this.servicePromise === null && (oldglobalTsdk !== this.globalTsdk || oldLocalTsdk !== this.localTsdk)) {
this.startService();
}
});
Expand All @@ -164,6 +172,25 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
this.startService();
}

private extractGlobalTsdk(configuration: WorkspaceConfiguration): string | null {
let inspect = configuration.inspect('typescript.tsdk');
if (inspect && inspect.globalValue && 'string' === typeof inspect.globalValue) {
return inspect.globalValue;
}
if (inspect && inspect.defaultValue && 'string' === typeof inspect.defaultValue) {
return inspect.defaultValue;
}
return null;
}

private extractLocalTsdk(configuration: WorkspaceConfiguration): string | null {
let inspect = configuration.inspect('typescript.tsdk');
if (inspect && inspect.workspaceValue && 'string' === typeof inspect.workspaceValue) {
return inspect.workspaceValue;
}
return null;
}

get onProjectLanguageServiceStateChanged(): Event<Proto.ProjectLanguageServiceStateEventBody> {
return this._onProjectLanguageServiceStateChanged.event;
}
Expand Down Expand Up @@ -306,6 +333,14 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
return null;
}

if (this.localTsdk) {
this._checkGlobalTSCVersion = false;
if ((<any>path).isAbsolute(this.localTsdk)) {
return path.join(this.localTsdk, 'tsserver.js');
}
return path.join(workspace.rootPath, this.localTsdk, 'tsserver.js');
}

const localModulePath = path.join(workspace.rootPath, 'node_modules', 'typescript', 'lib', 'tsserver.js');
if (fs.existsSync(localModulePath) && this.getTypeScriptVersion(localModulePath)) {
return localModulePath;
Expand All @@ -314,68 +349,20 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
}

private get globalTypescriptPath(): string {
if (this.tsdk) {
if (this.globalTsdk) {
this._checkGlobalTSCVersion = false;
if ((<any>path).isAbsolute(this.tsdk)) {
return path.join(this.tsdk, 'tsserver.js');
if ((<any>path).isAbsolute(this.globalTsdk)) {
return path.join(this.globalTsdk, 'tsserver.js');
} else if (workspace.rootPath) {
return path.join(workspace.rootPath, this.tsdk, 'tsserver.js');
return path.join(workspace.rootPath, this.globalTsdk, 'tsserver.js');
}
}

return this.bundledTypeScriptPath;
}

private hasWorkspaceTsdkSetting(): boolean {
function stripComments(content: string): string {
/**
* First capturing group matches double quoted string
* Second matches single quotes string
* Third matches block comments
* Fourth matches line comments
*/
var regexp: RegExp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
let result = content.replace(regexp, (match, m1, m2, m3, m4) => {
// Only one of m1, m2, m3, m4 matches
if (m3) {
// A block comment. Replace with nothing
return '';
} else if (m4) {
// A line comment. If it ends in \r?\n then keep it.
let length = m4.length;
if (length > 2 && m4[length - 1] === '\n') {
return m4[length - 2] === '\r' ? '\r\n' : '\n';
} else {
return '';
}
} else {
// We match a string
return match;
}
});
return result;
};

try {
let rootPath = workspace.rootPath;
if (!rootPath) {
return false;
}
let settingsFile = path.join(rootPath, '.vscode', 'settings.json');
if (!fs.existsSync(settingsFile)) {
return false;
}
let content = fs.readFileSync(settingsFile, 'utf8');
if (!content || content.length === 0) {
return false;
}
content = stripComments(content);
let json = JSON.parse(content);
let value = json['typescript.tsdk'];
return is.string(value);
} catch (error) {
}
return false;
return !!this.localTsdk;
}

private startService(resendModels: boolean = false): void {
Expand All @@ -391,8 +378,8 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
modulePath.then(modulePath => {
if (this.workspaceState.get<boolean>(TypeScriptServiceClient.useWorkspaceTsdkStorageKey, false)) {
if (workspace.rootPath) {
const pathValue = './node_modules/typescript/lib';
return path.join(workspace.rootPath, pathValue, 'tsserver.js');
// TODO: check if we need better error handling
return this.localTypeScriptPath || modulePath;
}
}
return modulePath;
Expand Down Expand Up @@ -501,44 +488,32 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
const useWorkspaceVersionSetting = this.workspaceState.get<boolean>(TypeScriptServiceClient.useWorkspaceTsdkStorageKey, false);
const shippedVersion = this.getTypeScriptVersion(this.globalTypescriptPath);
const localModulePath = this.localTypeScriptPath;
let messageShown: Thenable<MyQuickPickItem | undefined>;

const pickOptions: MyQuickPickItem[] = [];

pickOptions.push({
label: localize('useVSCodeVersionOption', 'Use VSCode\'s Version'),
description: shippedVersion || this.globalTypescriptPath,
detail: modulePath === this.globalTypescriptPath && (modulePath !== localModulePath || !useWorkspaceVersionSetting) ? localize('activeVersion', 'Currently active') : '',
id: MessageAction.useBundled,
});

if (localModulePath) {
const localVersion = this.getTypeScriptVersion(localModulePath);
messageShown = window.showQuickPick<MyQuickPickItem>([
{
label: localize('useWorkspaceVersionOption', 'Use Workspace Version'),
description: localVersion || '',
detail: modulePath === localModulePath && (modulePath !== this.globalTypescriptPath || useWorkspaceVersionSetting) ? localize('activeVersion', 'Currently active') : '',
id: MessageAction.useLocal
}, {
label: localize('useVSCodeVersionOption', 'Use VSCode\'s Version'),
description: shippedVersion || '',
detail: modulePath === this.globalTypescriptPath && (modulePath !== localModulePath || !useWorkspaceVersionSetting) ? localize('activeVersion', 'Currently active') : '',
id: MessageAction.useBundled,
}, {
label: localize('learnMore', 'Learn More'),
description: '',
id: MessageAction.learnMore
}], {
placeHolder: localize(
'selectTsVersion',
'Select the TypeScript version used for JavaScript and TypeScript language features'),
ignoreFocusOut: firstRun
});
} else {
messageShown = window.showQuickPick<MyQuickPickItem>([
{
label: localize('learnMore', 'Learn More'),
description: '',
id: MessageAction.learnMore
}], {
placeHolder: localize(
'versionCheckUsingBundledTS',
'Using VSCode\'s TypeScript version {0} for JavaScript and TypeScript language features',
shippedVersion),
});
pickOptions.push({
label: localize('useWorkspaceVersionOption', 'Use Workspace Version'),
description: localVersion || localModulePath,
detail: modulePath === localModulePath && (modulePath !== this.globalTypescriptPath || useWorkspaceVersionSetting) ? localize('activeVersion', 'Currently active') : '',
id: MessageAction.useLocal
});
}

pickOptions.push({
label: localize('learnMore', 'Learn More'),
description: '',
id: MessageAction.learnMore
});

const tryShowRestart = (newModulePath: string) => {
if (firstRun || newModulePath === this.modulePath) {
return;
Expand All @@ -559,32 +534,38 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
});
};

return messageShown.then(selected => {
if (!selected) {
return modulePath;
}
switch (selected.id) {
case MessageAction.useLocal:
return this.workspaceState.update(TypeScriptServiceClient.useWorkspaceTsdkStorageKey, true)
.then(_ => {
if (localModulePath) {
tryShowRestart(localModulePath);
}
return localModulePath;
});
case MessageAction.useBundled:
return this.workspaceState.update(TypeScriptServiceClient.useWorkspaceTsdkStorageKey, false)
.then(_ => {
tryShowRestart(this.globalTypescriptPath);
return this.globalTypescriptPath;
});
case MessageAction.learnMore:
commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919'));
return modulePath;
default:
return window.showQuickPick<MyQuickPickItem>(pickOptions, {
placeHolder: localize(
'selectTsVersion',
'Select the TypeScript version used for JavaScript and TypeScript language features'),
ignoreFocusOut: firstRun
})
.then(selected => {
if (!selected) {
return modulePath;
}
});
}
switch (selected.id) {
case MessageAction.useLocal:
return this.workspaceState.update(TypeScriptServiceClient.useWorkspaceTsdkStorageKey, true)
.then(_ => {
if (localModulePath) {
tryShowRestart(localModulePath);
}
return localModulePath;
});
case MessageAction.useBundled:
return this.workspaceState.update(TypeScriptServiceClient.useWorkspaceTsdkStorageKey, false)
.then(_ => {
tryShowRestart(this.globalTypescriptPath);
return this.globalTypescriptPath;
});
case MessageAction.learnMore:
commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919'));
return modulePath;
default:
return modulePath;
}
});
}

private serviceStarted(resendModels: boolean): void {
Expand Down Expand Up @@ -912,4 +893,4 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
}
this.logTrace(`Event received: ${event.event} (${event.seq}).`, data);
}
}
}

0 comments on commit fcac9b6

Please sign in to comment.