diff --git a/extensions/typescript-language-features/src/commands/useTsgo.ts b/extensions/typescript-language-features/src/commands/useTsgo.ts index aedf28e54b0..3f6e5f8c2c9 100644 --- a/extensions/typescript-language-features/src/commands/useTsgo.ts +++ b/extensions/typescript-language-features/src/commands/useTsgo.ts @@ -27,7 +27,7 @@ export class DisableTsgoCommand implements Command { * @param enable Whether to enable or disable TypeScript Go */ async function updateTsgoSetting(enable: boolean): Promise { - const tsgoExtension = vscode.extensions.getExtension('typescript.typescript-lsp'); + const tsgoExtension = vscode.extensions.getExtension('typescriptteam.native-preview'); // Error if the TypeScript Go extension is not installed with a button to open the GitHub repo if (!tsgoExtension) { const selection = await vscode.window.showErrorMessage( diff --git a/extensions/typescript-language-features/src/extension.ts b/extensions/typescript-language-features/src/extension.ts index 29f809bd653..b528a1e90dd 100644 --- a/extensions/typescript-language-features/src/extension.ts +++ b/extensions/typescript-language-features/src/extension.ts @@ -25,27 +25,12 @@ import { onCaseInsensitiveFileSystem } from './utils/fs.electron'; import { Lazy } from './utils/lazy'; import { getPackageInfo } from './utils/packageInfo'; import * as temp from './utils/temp.electron'; +import { conditionalRegistration, requireGlobalConfiguration } from './languageFeatures/util/dependentRegistration'; +import { DisposableStore } from './utils/dispose'; export function activate( context: vscode.ExtensionContext ): Api { - const commandManager = new CommandManager(); - context.subscriptions.push(commandManager); - - // Disable extension if using the experimental TypeScript Go extension - const config = vscode.workspace.getConfiguration('typescript'); - const useTsgo = config.get('experimental.useTsgo', false); - - if (useTsgo) { - commandManager.register(new DisableTsgoCommand()); - // Return a no-op API when disabled - return { - getAPI() { - return undefined; - } - }; - } - const pluginManager = new PluginManager(); context.subscriptions.push(pluginManager); @@ -55,9 +40,6 @@ export function activate( const logDirectoryProvider = new NodeLogDirectoryProvider(context); const versionProvider = new DiskTypeScriptVersionProvider(); - const activeJsTsEditorTracker = new ActiveJsTsEditorTracker(); - context.subscriptions.push(activeJsTsEditorTracker); - let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined; const packageInfo = getPackageInfo(context); if (packageInfo) { @@ -71,34 +53,55 @@ export function activate( new ExperimentationService(experimentTelemetryReporter, id, version, context.globalState); } - const logger = new Logger(); + context.subscriptions.push(conditionalRegistration([ + requireGlobalConfiguration('typescript', 'experimental.useTsgo'), + ], () => { + // TSGO. Only register a small set of features that don't use TS Server + const disposables = new DisposableStore(); - const lazyClientHost = createLazyClientHost(context, onCaseInsensitiveFileSystem(), { - pluginManager, - commandManager, - logDirectoryProvider, - cancellerFactory: nodeRequestCancellerFactory, - versionProvider, - processFactory: new ElectronServiceProcessFactory(), - activeJsTsEditorTracker, - serviceConfigurationProvider: new ElectronServiceConfigurationProvider(), - experimentTelemetryReporter, - logger, - }, item => { - onCompletionAccepted.fire(item); - }); + const commandManager = disposables.add(new CommandManager()); + commandManager.register(new DisableTsgoCommand()); - registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker); + return disposables; + }, () => { + // Normal registration path + const disposables = new DisposableStore(); - import('./task/taskProvider').then(module => { - context.subscriptions.push(module.register(new Lazy(() => lazyClientHost.value.serviceClient))); - }); + const commandManager = disposables.add(new CommandManager()); + const activeJsTsEditorTracker = disposables.add(new ActiveJsTsEditorTracker()); - import('./languageFeatures/tsconfig').then(module => { - context.subscriptions.push(module.register()); - }); + const lazyClientHost = createLazyClientHost(context, onCaseInsensitiveFileSystem(), { + pluginManager, + commandManager, + logDirectoryProvider, + cancellerFactory: nodeRequestCancellerFactory, + versionProvider, + processFactory: new ElectronServiceProcessFactory(), + activeJsTsEditorTracker, + serviceConfigurationProvider: new ElectronServiceConfigurationProvider(), + experimentTelemetryReporter, + logger: new Logger(), + }, item => { + onCompletionAccepted.fire(item); + }).map(clientHost => { + return disposables.add(clientHost); + }); - context.subscriptions.push(lazilyActivateClient(lazyClientHost, pluginManager, activeJsTsEditorTracker)); + // Register features + registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker); + + import('./task/taskProvider').then(module => { + disposables.add(module.register(new Lazy(() => lazyClientHost.value.serviceClient))); + }); + + import('./languageFeatures/tsconfig').then(module => { + disposables.add(module.register()); + }); + + disposables.add(lazilyActivateClient(lazyClientHost, pluginManager, activeJsTsEditorTracker)); + + return disposables; + },)); return getExtensionApi(onCompletionAccepted.event, pluginManager); } diff --git a/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts b/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts index e234acd1ab4..c445cc20e12 100644 --- a/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts +++ b/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts @@ -34,11 +34,15 @@ export class Condition extends Disposable { } class ConditionalRegistration { - private registration: vscode.Disposable | undefined = undefined; + private state?: { + readonly enabled: boolean; + readonly registration: vscode.Disposable | undefined; + }; public constructor( private readonly conditions: readonly Condition[], - private readonly doRegister: () => vscode.Disposable + private readonly doRegister: () => vscode.Disposable, + private readonly elseDoRegister?: () => vscode.Disposable ) { for (const condition of conditions) { condition.onDidChange(() => this.update()); @@ -47,17 +51,22 @@ class ConditionalRegistration { } public dispose() { - this.registration?.dispose(); - this.registration = undefined; + this.state?.registration?.dispose(); + this.state = undefined; } private update() { const enabled = this.conditions.every(condition => condition.value); if (enabled) { - this.registration ??= this.doRegister(); + if (!this.state?.enabled) { + this.state?.registration?.dispose(); + this.state = { enabled: true, registration: this.doRegister() }; + } } else { - this.registration?.dispose(); - this.registration = undefined; + if (this.state?.enabled || !this.state) { + this.state?.registration?.dispose(); + this.state = { enabled: false, registration: this.elseDoRegister?.() }; + } } } } @@ -65,8 +74,9 @@ class ConditionalRegistration { export function conditionalRegistration( conditions: readonly Condition[], doRegister: () => vscode.Disposable, + elseDoRegister?: () => vscode.Disposable ): vscode.Disposable { - return new ConditionalRegistration(conditions, doRegister); + return new ConditionalRegistration(conditions, doRegister, elseDoRegister); } export function requireMinVersion( diff --git a/extensions/typescript-language-features/src/lazyClientHost.ts b/extensions/typescript-language-features/src/lazyClientHost.ts index 3fc8a37cd17..ff03830e012 100644 --- a/extensions/typescript-language-features/src/lazyClientHost.ts +++ b/extensions/typescript-language-features/src/lazyClientHost.ts @@ -38,16 +38,12 @@ export function createLazyClientHost( onCompletionAccepted: (item: vscode.CompletionItem) => void, ): Lazy { return new Lazy(() => { - const clientHost = new TypeScriptServiceClientHost( + return new TypeScriptServiceClientHost( standardLanguageDescriptions, context, onCaseInsensitiveFileSystem, services, onCompletionAccepted); - - context.subscriptions.push(clientHost); - - return clientHost; }); } diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 4201d6da29b..57395ce3999 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -108,7 +108,6 @@ interface WatchEvent { export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient { - private readonly _onReady?: { promise: Promise; resolve: () => void; reject: () => void }; private _configuration: TypeScriptServiceConfiguration; private readonly pluginPathsProvider: TypeScriptPluginPathsProvider; @@ -632,6 +631,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.serverState = ServerState.None; + if (this.isDisposed) { + return; + } + if (restart) { const diff = Date.now() - this.lastStart; this.numberRestarts++; diff --git a/extensions/typescript-language-features/src/utils/lazy.ts b/extensions/typescript-language-features/src/utils/lazy.ts index 7114ece99b1..be1d0530b88 100644 --- a/extensions/typescript-language-features/src/utils/lazy.ts +++ b/extensions/typescript-language-features/src/utils/lazy.ts @@ -44,4 +44,8 @@ export class Lazy { * Get the wrapped value without forcing evaluation. */ get rawValue(): T | undefined { return this._value; } + + map(fn: (value: T) => R): Lazy { + return new Lazy(() => fn(this.value)); + } }