Merge pull request #266088 from mjbvz/permanent-wolf

Don't require restart when switching to TS native
This commit is contained in:
Matt Bierner
2025-09-10 15:59:54 -07:00
committed by GitHub
6 changed files with 74 additions and 58 deletions

View File

@@ -27,7 +27,7 @@ export class DisableTsgoCommand implements Command {
* @param enable Whether to enable or disable TypeScript Go
*/
async function updateTsgoSetting(enable: boolean): Promise<void> {
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(

View File

@@ -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<boolean>('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);
}

View File

@@ -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(

View File

@@ -38,16 +38,12 @@ export function createLazyClientHost(
onCompletionAccepted: (item: vscode.CompletionItem) => void,
): Lazy<TypeScriptServiceClientHost> {
return new Lazy(() => {
const clientHost = new TypeScriptServiceClientHost(
return new TypeScriptServiceClientHost(
standardLanguageDescriptions,
context,
onCaseInsensitiveFileSystem,
services,
onCompletionAccepted);
context.subscriptions.push(clientHost);
return clientHost;
});
}

View File

@@ -108,7 +108,6 @@ interface WatchEvent {
export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient {
private readonly _onReady?: { promise: Promise<void>; 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++;

View File

@@ -44,4 +44,8 @@ export class Lazy<T> {
* Get the wrapped value without forcing evaluation.
*/
get rawValue(): T | undefined { return this._value; }
map<R>(fn: (value: T) => R): Lazy<R> {
return new Lazy(() => fn(this.value));
}
}