From dc7891db87d22412861a58feb19996a2e0a446d9 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 15 Jun 2023 15:22:20 -0700 Subject: [PATCH 01/15] Use vscode watches for tsserver --- .../src/tsServer/protocol/protocol.const.ts | 3 ++ .../src/tsServer/spawner.ts | 1 + .../src/typescriptService.ts | 1 + .../src/typescriptServiceClient.ts | 41 +++++++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/extensions/typescript-language-features/src/tsServer/protocol/protocol.const.ts b/extensions/typescript-language-features/src/tsServer/protocol/protocol.const.ts index deb3357e6ae30..4f02ed29427e0 100644 --- a/extensions/typescript-language-features/src/tsServer/protocol/protocol.const.ts +++ b/extensions/typescript-language-features/src/tsServer/protocol/protocol.const.ts @@ -88,6 +88,9 @@ export enum EventName { surveyReady = 'surveyReady', projectLoadingStart = 'projectLoadingStart', projectLoadingFinish = 'projectLoadingFinish', + createFileWatcher = 'createFileWatcher', + createDirectoryWatcher = 'createDirectoryWatcher', + closeFileWatcher = 'closeFileWatcher', } export enum OrganizeImportsMode { diff --git a/extensions/typescript-language-features/src/tsServer/spawner.ts b/extensions/typescript-language-features/src/tsServer/spawner.ts index 52dcf5baa1939..fed9c1ec0f916 100644 --- a/extensions/typescript-language-features/src/tsServer/spawner.ts +++ b/extensions/typescript-language-features/src/tsServer/spawner.ts @@ -270,6 +270,7 @@ export class TypeScriptServerSpawner { args.push('--locale', TypeScriptServerSpawner.getTsLocale(configuration)); args.push('--noGetErrOnBackgroundUpdate'); + args.push('--canUseWatchEvents'); // TODO check ts version args.push('--validateDefaultNpmLocation'); diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 6eb30e2098633..a35df36570f2c 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -85,6 +85,7 @@ interface NoResponseTsServerRequests { 'compilerOptionsForInferredProjects': [Proto.SetCompilerOptionsForInferredProjectsArgs, null]; 'reloadProjects': [null, null]; 'configurePlugin': [Proto.ConfigurePluginRequest, Proto.ConfigurePluginResponse]; + 'watchChange': [Proto.Request, null]; } interface AsyncTsServerRequests { diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 7553be7ed4990..9fa0c08144bf3 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -128,6 +128,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType private readonly versionProvider: ITypeScriptVersionProvider; private readonly processFactory: TsServerProcessFactory; + private readonly watches = new Map(); + constructor( private readonly context: vscode.ExtensionContext, onCaseInsenitiveFileSystem: boolean, @@ -973,6 +975,45 @@ export default class TypeScriptServiceClient extends Disposable implements IType case EventName.projectLoadingFinish: this.loadingIndicator.finishedLoadingProject((event as Proto.ProjectLoadingFinishEvent).body.projectName); break; + + case EventName.createDirectoryWatcher: + this.createFileSystemWatcher(event.body.id, new vscode.RelativePattern(vscode.Uri.file(event.body.path), event.body.recursive ? '**' : '*')); + break; + + case EventName.createFileWatcher: + this.createFileSystemWatcher(event.body.id, event.body.path); + break; + + case EventName.closeFileWatcher: + this.closeFileSystemWatcher(event.body.id); + break; + } + } + + private createFileSystemWatcher( + id: number, + pattern: vscode.GlobPattern, + ) { + const watcher = vscode.workspace.createFileSystemWatcher(pattern); + watcher.onDidChange(changeFile => + this.executeWithoutWaitingForResponse('watchChange', { id, path: changeFile.fsPath, eventType: 'update' }) + ); + watcher.onDidCreate(createFile => + this.executeWithoutWaitingForResponse('watchChange', { id, path: createFile.fsPath, eventType: 'create' }) + ); + watcher.onDidDelete(deletedFile => + this.executeWithoutWaitingForResponse('watchChange', { id, path: deletedFile.fsPath, eventType: 'delete' }) + ); + this.watches.set(id, watcher); + } + + private closeFileSystemWatcher( + id: number, + ) { + const existing = this.watches.get(id); + if (existing) { + existing.dispose(); + this.watches.delete(id); } } From a936df41b768663d19f39f1f6a09544e1c598f96 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 12 Oct 2023 06:13:12 +0200 Subject: [PATCH 02/15] towards using new proposed watch API --- extensions/typescript-language-features/package.json | 3 ++- .../src/typescriptServiceClient.ts | 7 ++++--- extensions/typescript-language-features/tsconfig.json | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 607bb3aabe0d8..55f685869ac6f 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -8,7 +8,8 @@ "license": "MIT", "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "enabledApiProposals": [ - "workspaceTrust" + "workspaceTrust", + "createFileSystemWatcher" ], "capabilities": { "virtualWorkspaces": { diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 1557f4ac0a81d..dc791e81cdbb0 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -977,11 +977,11 @@ export default class TypeScriptServiceClient extends Disposable implements IType break; case EventName.createDirectoryWatcher: - this.createFileSystemWatcher(event.body.id, new vscode.RelativePattern(vscode.Uri.file(event.body.path), event.body.recursive ? '**' : '*')); + this.createFileSystemWatcher(event.body.id, new vscode.RelativePattern(vscode.Uri.file(event.body.path), event.body.recursive ? '**' : '*'), [ /* TODO need to fill in excludes list */]); break; case EventName.createFileWatcher: - this.createFileSystemWatcher(event.body.id, event.body.path); + this.createFileSystemWatcher(event.body.id, event.body.path, []); break; case EventName.closeFileWatcher: @@ -993,8 +993,9 @@ export default class TypeScriptServiceClient extends Disposable implements IType private createFileSystemWatcher( id: number, pattern: vscode.GlobPattern, + excludes: string[] ) { - const watcher = vscode.workspace.createFileSystemWatcher(pattern); + const watcher = typeof pattern === 'string' ? vscode.workspace.createFileSystemWatcher(pattern) : vscode.workspace.createFileSystemWatcher(pattern, { excludes }); watcher.onDidChange(changeFile => this.executeWithoutWaitingForResponse('watchChange', { id, path: changeFile.fsPath, eventType: 'update' }) ); diff --git a/extensions/typescript-language-features/tsconfig.json b/extensions/typescript-language-features/tsconfig.json index 73957dde9321e..5ac0797334b2c 100644 --- a/extensions/typescript-language-features/tsconfig.json +++ b/extensions/typescript-language-features/tsconfig.json @@ -11,6 +11,7 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.workspaceTrust.d.ts" + "../../src/vscode-dts/vscode.proposed.workspaceTrust.d.ts", + "../../src/vscode-dts/vscode.proposed.createFileSystemWatcher.d.ts" ] } From bf56b37fd6f6a379dd18b33249ccdd9851802c89 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 18 Oct 2023 10:13:07 -0700 Subject: [PATCH 03/15] Add setting and make sure to clear watchers --- extensions/typescript-language-features/package.json | 5 +++++ .../typescript-language-features/package.nls.json | 1 + .../src/configuration/configuration.ts | 8 +++++++- .../src/tsServer/spawner.ts | 5 ++++- .../src/typescriptServiceClient.ts | 12 +++++++++++- .../src/utils/dispose.ts | 7 +++---- 6 files changed, 31 insertions(+), 7 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 55f685869ac6f..19a7ab3fc0bce 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1193,6 +1193,11 @@ "experimental" ] }, + "typescript.tsserver.experimental.useVsCodeWatcher": { + "type": "boolean", + "description": "%configuration.tsserver.useVsCodeWatcher%", + "default": true + }, "typescript.tsserver.watchOptions": { "type": "object", "description": "%configuration.tsserver.watchOptions%", diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index f31c5fa3be4fe..c1e66855c78f2 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -171,6 +171,7 @@ "typescript.suggest.enabled": "Enabled/disable autocomplete suggestions.", "configuration.surveys.enabled": "Enabled/disable occasional surveys that help us improve VS Code's JavaScript and TypeScript support.", "configuration.suggest.completeJSDocs": "Enable/disable suggestion to complete JSDoc comments.", + "configuration.tsserver.useVsCodeWatcher": "Use VS Code's file watchers instead of TypeScript's. Requires using TypeScript 5.3+ in the workspace.", "configuration.tsserver.watchOptions": "Configure which watching strategies should be used to keep track of files and directories.", "configuration.tsserver.watchOptions.watchFile": "Strategy for how individual files are watched.", "configuration.tsserver.watchOptions.watchFile.fixedChunkSizePolling": "Polls files in chunks at regular interval.", diff --git a/extensions/typescript-language-features/src/configuration/configuration.ts b/extensions/typescript-language-features/src/configuration/configuration.ts index 5fce20d1d1f29..484c180609baa 100644 --- a/extensions/typescript-language-features/src/configuration/configuration.ts +++ b/extensions/typescript-language-features/src/configuration/configuration.ts @@ -117,6 +117,7 @@ export interface TypeScriptServiceConfiguration { readonly enableProjectDiagnostics: boolean; readonly maxTsServerMemory: number; readonly enablePromptUseWorkspaceTsdk: boolean; + readonly useVsCodeWatcher: boolean; readonly watchOptions: Proto.WatchOptions | undefined; readonly includePackageJsonAutoImports: 'auto' | 'on' | 'off' | undefined; readonly enableTsServerTracing: boolean; @@ -154,6 +155,7 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu enableProjectDiagnostics: this.readEnableProjectDiagnostics(configuration), maxTsServerMemory: this.readMaxTsServerMemory(configuration), enablePromptUseWorkspaceTsdk: this.readEnablePromptUseWorkspaceTsdk(configuration), + useVsCodeWatcher: this.readUseVsCodeWatcher(configuration), watchOptions: this.readWatchOptions(configuration), includePackageJsonAutoImports: this.readIncludePackageJsonAutoImports(configuration), enableTsServerTracing: this.readEnableTsServerTracing(configuration), @@ -222,7 +224,11 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu return configuration.get('typescript.tsserver.experimental.enableProjectDiagnostics', false); } - protected readWatchOptions(configuration: vscode.WorkspaceConfiguration): Proto.WatchOptions | undefined { + private readUseVsCodeWatcher(configuration: vscode.WorkspaceConfiguration): boolean { + return configuration.get('typescript.tsserver.experimental.useVsCodeWatcher', true); + } + + private readWatchOptions(configuration: vscode.WorkspaceConfiguration): Proto.WatchOptions | undefined { const watchOptions = configuration.get('typescript.tsserver.watchOptions'); // Returned value may be a proxy. Clone it into a normal object return { ...(watchOptions ?? {}) }; diff --git a/extensions/typescript-language-features/src/tsServer/spawner.ts b/extensions/typescript-language-features/src/tsServer/spawner.ts index fed9c1ec0f916..bbe5b1d1d12ee 100644 --- a/extensions/typescript-language-features/src/tsServer/spawner.ts +++ b/extensions/typescript-language-features/src/tsServer/spawner.ts @@ -270,7 +270,10 @@ export class TypeScriptServerSpawner { args.push('--locale', TypeScriptServerSpawner.getTsLocale(configuration)); args.push('--noGetErrOnBackgroundUpdate'); - args.push('--canUseWatchEvents'); // TODO check ts version + + if (apiVersion.gte(API.v350) && configuration.useVsCodeWatcher) { + args.push('--canUseWatchEvents'); + } args.push('--validateDefaultNpmLocation'); diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index dc791e81cdbb0..4ed897074b1ce 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -21,7 +21,7 @@ import { TypeScriptVersionManager } from './tsServer/versionManager'; import { ITypeScriptVersionProvider, TypeScriptVersion } from './tsServer/versionProvider'; import { ClientCapabilities, ClientCapability, ExecConfig, ITypeScriptServiceClient, ServerResponse, TypeScriptRequests } from './typescriptService'; import { ServiceConfigurationProvider, SyntaxServerConfiguration, TsServerLogLevel, TypeScriptServiceConfiguration, areServiceConfigurationsEqual } from './configuration/configuration'; -import { Disposable } from './utils/dispose'; +import { Disposable, disposeAll } from './utils/dispose'; import * as fileSchemes from './configuration/fileSchemes'; import { Logger } from './logging/logger'; import { isWeb, isWebAndHasSharedArrayBuffers } from './utils/platform'; @@ -300,6 +300,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType } this.loadingIndicator.reset(); + + this.resetWatchers(); } public restartTsServer(fromUserAction = false): void { @@ -403,6 +405,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.info(`Using Node installation from ${nodePath} to run TS Server`); } + this.resetWatchers(); + const apiVersion = version.apiVersion || API.defaultVersion; const mytoken = ++this.token; const handle = this.typescriptServerSpawner.spawn(version, this.capabilities, this.configuration, this.pluginManager, this.cancellerFactory, { @@ -495,6 +499,11 @@ export default class TypeScriptServiceClient extends Disposable implements IType return this.serverState; } + private resetWatchers() { + disposeAll(this.watches.values()); + this.watches.clear(); + } + public async showVersionPicker(): Promise { this._versionManager.promptUserForVersion(); } @@ -596,6 +605,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType } private serviceExited(restart: boolean): void { + this.resetWatchers(); this.loadingIndicator.reset(); const previousState = this.serverState; diff --git a/extensions/typescript-language-features/src/utils/dispose.ts b/extensions/typescript-language-features/src/utils/dispose.ts index 7b6627204d5ed..a1eb92b9dd7f0 100644 --- a/extensions/typescript-language-features/src/utils/dispose.ts +++ b/extensions/typescript-language-features/src/utils/dispose.ts @@ -5,10 +5,9 @@ import * as vscode from 'vscode'; -export function disposeAll(disposables: vscode.Disposable[]) { - while (disposables.length) { - const item = disposables.pop(); - item?.dispose(); +export function disposeAll(disposables: Iterable) { + for (const disposable of disposables) { + disposable.dispose(); } } From 77d693d93e059a5451c29242726514bfd3ef1597 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 10:47:45 +0100 Subject: [PATCH 04/15] fix bad merge --- extensions/typescript-language-features/package.json | 2 +- extensions/typescript-language-features/tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index ce38b6d7307c2..f4a08a83ba4f9 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -9,7 +9,7 @@ "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "enabledApiProposals": [ "workspaceTrust", - "createFileSystemWatcher" + "createFileSystemWatcher", "multiDocumentHighlightProvider", "mappedEditsProvider", "codeActionAI", diff --git a/extensions/typescript-language-features/tsconfig.json b/extensions/typescript-language-features/tsconfig.json index ec24654f1479a..adf7b9d402612 100644 --- a/extensions/typescript-language-features/tsconfig.json +++ b/extensions/typescript-language-features/tsconfig.json @@ -12,7 +12,7 @@ "src/**/*", "../../src/vscode-dts/vscode.d.ts", "../../src/vscode-dts/vscode.proposed.workspaceTrust.d.ts", - "../../src/vscode-dts/vscode.proposed.createFileSystemWatcher.d.ts" + "../../src/vscode-dts/vscode.proposed.createFileSystemWatcher.d.ts", "../../src/vscode-dts/vscode.proposed.codeActionAI.d.ts", "../../src/vscode-dts/vscode.proposed.codeActionRanges.d.ts", "../../src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts", From 6ffc5341cab445a19c7c85df4ee1048162064780 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 10:48:27 +0100 Subject: [PATCH 05/15] fix bad merge --- extensions/typescript-language-features/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/typescript-language-features/tsconfig.json b/extensions/typescript-language-features/tsconfig.json index adf7b9d402612..1da85cd17cd4b 100644 --- a/extensions/typescript-language-features/tsconfig.json +++ b/extensions/typescript-language-features/tsconfig.json @@ -11,7 +11,6 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.workspaceTrust.d.ts", "../../src/vscode-dts/vscode.proposed.createFileSystemWatcher.d.ts", "../../src/vscode-dts/vscode.proposed.codeActionAI.d.ts", "../../src/vscode-dts/vscode.proposed.codeActionRanges.d.ts", From 5b070e443b91e5b0b286fd675f42e72ba8090463 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 10:58:55 +0100 Subject: [PATCH 06/15] more disposables work --- .../src/typescriptServiceClient.ts | 23 +++++++++++-------- .../src/utils/dispose.ts | 9 ++++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 611d97679c107..4949b34d224ec 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -21,7 +21,7 @@ import { TypeScriptVersionManager } from './tsServer/versionManager'; import { ITypeScriptVersionProvider, TypeScriptVersion } from './tsServer/versionProvider'; import { ClientCapabilities, ClientCapability, ExecConfig, ITypeScriptServiceClient, ServerResponse, TypeScriptRequests } from './typescriptService'; import { ServiceConfigurationProvider, SyntaxServerConfiguration, TsServerLogLevel, TypeScriptServiceConfiguration, areServiceConfigurationsEqual } from './configuration/configuration'; -import { Disposable, disposeAll } from './utils/dispose'; +import { Disposable, DisposableStore, disposeAll } from './utils/dispose'; import * as fileSchemes from './configuration/fileSchemes'; import { Logger } from './logging/logger'; import { isWeb, isWebAndHasSharedArrayBuffers } from './utils/platform'; @@ -128,7 +128,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType private readonly versionProvider: ITypeScriptVersionProvider; private readonly processFactory: TsServerProcessFactory; - private readonly watches = new Map(); + private readonly watches = new Map(); constructor( private readonly context: vscode.ExtensionContext, @@ -1005,17 +1005,20 @@ export default class TypeScriptServiceClient extends Disposable implements IType pattern: vscode.GlobPattern, excludes: string[] ) { - const watcher = typeof pattern === 'string' ? vscode.workspace.createFileSystemWatcher(pattern) : vscode.workspace.createFileSystemWatcher(pattern, { excludes }); - watcher.onDidChange(changeFile => + const disposable = new DisposableStore(); + const watcher = typeof pattern === 'string' ? + disposable.add(vscode.workspace.createFileSystemWatcher(pattern)) : + disposable.add(vscode.workspace.createFileSystemWatcher(pattern, { excludes })); + disposable.add(watcher.onDidChange(changeFile => this.executeWithoutWaitingForResponse('watchChange', { id, path: changeFile.fsPath, eventType: 'update' }) - ); - watcher.onDidCreate(createFile => + )); + disposable.add(watcher.onDidCreate(createFile => this.executeWithoutWaitingForResponse('watchChange', { id, path: createFile.fsPath, eventType: 'create' }) - ); - watcher.onDidDelete(deletedFile => + )); + disposable.add(watcher.onDidDelete(deletedFile => this.executeWithoutWaitingForResponse('watchChange', { id, path: deletedFile.fsPath, eventType: 'delete' }) - ); - this.watches.set(id, watcher); + )); + this.watches.set(id, disposable); } private closeFileSystemWatcher( diff --git a/extensions/typescript-language-features/src/utils/dispose.ts b/extensions/typescript-language-features/src/utils/dispose.ts index a1eb92b9dd7f0..effe8b726fc3b 100644 --- a/extensions/typescript-language-features/src/utils/dispose.ts +++ b/extensions/typescript-language-features/src/utils/dispose.ts @@ -41,3 +41,12 @@ export abstract class Disposable { return this._isDisposed; } } + +export class DisposableStore extends Disposable { + + public add(disposable: T): T { + this._register(disposable); + + return disposable; + } +} From c2d1bbd57afc6d5f788ef4f54d9d70388e3d6bba Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 12:03:31 +0100 Subject: [PATCH 07/15] use relative pattern everywhere --- .../src/typescriptServiceClient.ts | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 4949b34d224ec..56b9be15d16ad 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -987,11 +987,23 @@ export default class TypeScriptServiceClient extends Disposable implements IType break; case EventName.createDirectoryWatcher: - this.createFileSystemWatcher(event.body.id, new vscode.RelativePattern(vscode.Uri.file(event.body.path), event.body.recursive ? '**' : '*'), [ /* TODO need to fill in excludes list */]); + this.createFileSystemWatcher( + (event.body as Proto.CreateDirectoryWatcherEventBody).id, + new vscode.RelativePattern( + vscode.Uri.file((event.body as Proto.CreateDirectoryWatcherEventBody).path), + (event.body as Proto.CreateDirectoryWatcherEventBody).recursive ? '**' : '*' + ) + ); break; case EventName.createFileWatcher: - this.createFileSystemWatcher(event.body.id, event.body.path, []); + this.createFileSystemWatcher( + (event.body as Proto.CreateFileWatcherEventBody).id, + new vscode.RelativePattern( + vscode.Uri.file((event.body as Proto.CreateFileWatcherEventBody).path), + '*' + ) + ); break; case EventName.closeFileWatcher: @@ -1002,22 +1014,25 @@ export default class TypeScriptServiceClient extends Disposable implements IType private createFileSystemWatcher( id: number, - pattern: vscode.GlobPattern, - excludes: string[] + pattern: vscode.RelativePattern ) { const disposable = new DisposableStore(); - const watcher = typeof pattern === 'string' ? - disposable.add(vscode.workspace.createFileSystemWatcher(pattern)) : - disposable.add(vscode.workspace.createFileSystemWatcher(pattern, { excludes })); + + const watcher = disposable.add(vscode.workspace.createFileSystemWatcher(pattern, { excludes: [] /* TODO need to fill in excludes list */ })); + disposable.add(watcher.onDidChange(changeFile => - this.executeWithoutWaitingForResponse('watchChange', { id, path: changeFile.fsPath, eventType: 'update' }) + this.executeWithoutWaitingForResponse('watchChange', { id, path: changeFile.fsPath, eventType: 'update' } satisfies Proto.WatchChangeRequestArgs) )); disposable.add(watcher.onDidCreate(createFile => - this.executeWithoutWaitingForResponse('watchChange', { id, path: createFile.fsPath, eventType: 'create' }) + this.executeWithoutWaitingForResponse('watchChange', { id, path: createFile.fsPath, eventType: 'create' } satisfies Proto.WatchChangeRequestArgs) )); disposable.add(watcher.onDidDelete(deletedFile => - this.executeWithoutWaitingForResponse('watchChange', { id, path: deletedFile.fsPath, eventType: 'delete' }) + this.executeWithoutWaitingForResponse('watchChange', { id, path: deletedFile.fsPath, eventType: 'delete' } satisfies Proto.WatchChangeRequestArgs) )); + + if (this.watches.has(id)) { + this.closeFileSystemWatcher(id); + } this.watches.set(id, disposable); } From a37cfa427f721d8cd6965b8eb85f4156f17509cb Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 26 Mar 2024 16:24:04 -0700 Subject: [PATCH 08/15] Add ignoreUpdate --- .../src/typescriptServiceClient.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 4ab9ed0a23d8c..1e9dacc25ba3c 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -992,7 +992,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType new vscode.RelativePattern( vscode.Uri.file((event.body as Proto.CreateDirectoryWatcherEventBody).path), (event.body as Proto.CreateDirectoryWatcherEventBody).recursive ? '**' : '*' - ) + ), + event.body.ignoreUpdate // TODO when typescript.d.ts gets updated update the type info ); break; @@ -1014,11 +1015,12 @@ export default class TypeScriptServiceClient extends Disposable implements IType private createFileSystemWatcher( id: number, - pattern: vscode.RelativePattern + pattern: vscode.RelativePattern, + ignoreChangeEvents?: boolean, ) { const disposable = new DisposableStore(); - const watcher = disposable.add(vscode.workspace.createFileSystemWatcher(pattern, { excludes: [] /* TODO need to fill in excludes list */ })); + const watcher = disposable.add(vscode.workspace.createFileSystemWatcher(pattern, { excludes: [] /* TODO need to fill in excludes list */, ignoreChangeEvents })); disposable.add(watcher.onDidChange(changeFile => this.executeWithoutWaitingForResponse('watchChange', { id, path: changeFile.fsPath, eventType: 'update' } satisfies Proto.WatchChangeRequestArgs) From 24679a7291d63f88e61364e210208289a451e21a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 27 Mar 2024 11:51:36 -0700 Subject: [PATCH 09/15] Protocol changes for tsserver to batch the notifications --- .../src/typescriptServiceClient.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 1e9dacc25ba3c..ce0b163f424e2 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -1020,16 +1020,19 @@ export default class TypeScriptServiceClient extends Disposable implements IType ) { const disposable = new DisposableStore(); + // TODO:: not sure whwere we check typescript version but this has to be 5.4+ or 5.5 depeneding on which version of typescript the protocol changes go into + const watcher = disposable.add(vscode.workspace.createFileSystemWatcher(pattern, { excludes: [] /* TODO need to fill in excludes list */, ignoreChangeEvents })); + // TODO:: Batch these together disposable.add(watcher.onDidChange(changeFile => - this.executeWithoutWaitingForResponse('watchChange', { id, path: changeFile.fsPath, eventType: 'update' } satisfies Proto.WatchChangeRequestArgs) + this.executeWithoutWaitingForResponse('watchChange', { id, updated: [changeFile.fsPath] }) )); disposable.add(watcher.onDidCreate(createFile => - this.executeWithoutWaitingForResponse('watchChange', { id, path: createFile.fsPath, eventType: 'create' } satisfies Proto.WatchChangeRequestArgs) + this.executeWithoutWaitingForResponse('watchChange', { id, created: [createFile.fsPath] }) )); disposable.add(watcher.onDidDelete(deletedFile => - this.executeWithoutWaitingForResponse('watchChange', { id, path: deletedFile.fsPath, eventType: 'delete' } satisfies Proto.WatchChangeRequestArgs) + this.executeWithoutWaitingForResponse('watchChange', { id, deleted: [deletedFile.fsPath] }) // TODO:: add satifies for all of these when we publish )); if (this.watches.has(id)) { From 04bb6f6e864087c01a8d4575b9e67462d1b43190 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 28 Mar 2024 12:41:57 +0100 Subject: [PATCH 10/15] add event aggregation --- .../src/typescriptServiceClient.ts | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index ce0b163f424e2..0bc5400066e9d 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -1020,20 +1020,48 @@ export default class TypeScriptServiceClient extends Disposable implements IType ) { const disposable = new DisposableStore(); - // TODO:: not sure whwere we check typescript version but this has to be 5.4+ or 5.5 depeneding on which version of typescript the protocol changes go into - - const watcher = disposable.add(vscode.workspace.createFileSystemWatcher(pattern, { excludes: [] /* TODO need to fill in excludes list */, ignoreChangeEvents })); - - // TODO:: Batch these together - disposable.add(watcher.onDidChange(changeFile => - this.executeWithoutWaitingForResponse('watchChange', { id, updated: [changeFile.fsPath] }) - )); - disposable.add(watcher.onDidCreate(createFile => - this.executeWithoutWaitingForResponse('watchChange', { id, created: [createFile.fsPath] }) - )); - disposable.add(watcher.onDidDelete(deletedFile => - this.executeWithoutWaitingForResponse('watchChange', { id, deleted: [deletedFile.fsPath] }) // TODO:: add satifies for all of these when we publish - )); + const events = { updated: new Set(), created: new Set(), deleted: new Set() }; + + let timeout: NodeJS.Timeout | undefined; + disposable.add({ dispose: () => clearTimeout(timeout) }); + + const executeWatchChangeRequest = () => { + try { + // TODO:: not sure whwere we check typescript version but this has to be 5.4+ or 5.5 depeneding on which version of typescript the protocol changes go into + this.executeWithoutWaitingForResponse('watchChange', { + id, + updated: events.updated.size > 0 ? Array.from(events.updated) : undefined, + created: events.created.size > 0 ? Array.from(events.created) : undefined, + deleted: events.deleted.size > 0 ? Array.from(events.deleted) : undefined + }); + } finally { + events.updated.clear(); + events.created.clear(); + events.deleted.clear(); + + timeout = undefined; + } + }; + + const scheduleExecuteWatchChangeRequest = () => { + if (!timeout) { + timeout = setTimeout(() => executeWatchChangeRequest(), 100 /* aggregate events over 100ms to reduce client<->server IPC overhead */); + } + }; + + const watcher = disposable.add(vscode.workspace.createFileSystemWatcher(pattern, { excludes: [] /* TODO:: need to fill in excludes list */, ignoreChangeEvents })); + disposable.add(watcher.onDidChange(changeFile => { + events.updated.add(changeFile.fsPath); + scheduleExecuteWatchChangeRequest(); + })); + disposable.add(watcher.onDidCreate(createFile => { + events.created.add(createFile.fsPath); + scheduleExecuteWatchChangeRequest(); + })); + disposable.add(watcher.onDidDelete(deletedFile => { + events.deleted.add(deletedFile.fsPath); + scheduleExecuteWatchChangeRequest(); + })); if (this.watches.has(id)) { this.closeFileSystemWatcher(id); From 5ffe26407be9284accff0e0c4c1932b97130aa2f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 28 Mar 2024 12:06:48 -0700 Subject: [PATCH 11/15] Update version numbers --- extensions/typescript-language-features/package.json | 5 ++++- extensions/typescript-language-features/package.nls.json | 2 +- extensions/typescript-language-features/src/tsServer/api.ts | 1 + .../typescript-language-features/src/tsServer/spawner.ts | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index f4a08a83ba4f9..ba80a44d4a0b0 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1172,7 +1172,10 @@ "typescript.tsserver.experimental.useVsCodeWatcher": { "type": "boolean", "description": "%configuration.tsserver.useVsCodeWatcher%", - "default": true + "default": true, + "tags": [ + "experimental" + ] }, "typescript.tsserver.watchOptions": { "type": "object", diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index f1962cd4f7ee7..e60451eaeb4a9 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -164,7 +164,7 @@ "typescript.suggest.enabled": "Enabled/disable autocomplete suggestions.", "configuration.surveys.enabled": "Enabled/disable occasional surveys that help us improve VS Code's JavaScript and TypeScript support.", "configuration.suggest.completeJSDocs": "Enable/disable suggestion to complete JSDoc comments.", - "configuration.tsserver.useVsCodeWatcher": "Use VS Code's file watchers instead of TypeScript's. Requires using TypeScript 5.3+ in the workspace.", + "configuration.tsserver.useVsCodeWatcher": "Use VS Code's file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", "configuration.tsserver.watchOptions": "Configure which watching strategies should be used to keep track of files and directories.", "configuration.tsserver.watchOptions.watchFile": "Strategy for how individual files are watched.", "configuration.tsserver.watchOptions.watchFile.fixedChunkSizePolling": "Polls files in chunks at regular interval.", diff --git a/extensions/typescript-language-features/src/tsServer/api.ts b/extensions/typescript-language-features/src/tsServer/api.ts index 92e4503ec85a4..4a35ada0f24b4 100644 --- a/extensions/typescript-language-features/src/tsServer/api.ts +++ b/extensions/typescript-language-features/src/tsServer/api.ts @@ -35,6 +35,7 @@ export class API { public static readonly v500 = API.fromSimpleString('5.0.0'); public static readonly v510 = API.fromSimpleString('5.1.0'); public static readonly v520 = API.fromSimpleString('5.2.0'); + public static readonly v544 = API.fromSimpleString('5.4.4'); public static readonly v540 = API.fromSimpleString('5.4.0'); public static fromVersionString(versionString: string): API { diff --git a/extensions/typescript-language-features/src/tsServer/spawner.ts b/extensions/typescript-language-features/src/tsServer/spawner.ts index bbe5b1d1d12ee..b88251a70336e 100644 --- a/extensions/typescript-language-features/src/tsServer/spawner.ts +++ b/extensions/typescript-language-features/src/tsServer/spawner.ts @@ -271,7 +271,7 @@ export class TypeScriptServerSpawner { args.push('--noGetErrOnBackgroundUpdate'); - if (apiVersion.gte(API.v350) && configuration.useVsCodeWatcher) { + if (apiVersion.gte(API.v544) && configuration.useVsCodeWatcher) { args.push('--canUseWatchEvents'); } From 56b4e3517a256f5ddca282ae7dccef730687ce5e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 2 Apr 2024 15:50:03 -0700 Subject: [PATCH 12/15] Aggregate events over multiple watchers --- .../src/typescriptServiceClient.ts | 117 +++++++++++------- 1 file changed, 74 insertions(+), 43 deletions(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 0bc5400066e9d..762714793767d 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -97,6 +97,12 @@ export const emptyAuthority = 'ts-nul-authority'; export const inMemoryResourcePrefix = '^'; +interface WatchEvent { + updated?: Set; + created?: Set; + deleted?: Set; +} + export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient { @@ -129,6 +135,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType private readonly processFactory: TsServerProcessFactory; private readonly watches = new Map(); + private readonly watchEvents = new Map(); + private watchChangeTimeout: NodeJS.Timeout | undefined; constructor( private readonly context: vscode.ExtensionContext, @@ -500,8 +508,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType } private resetWatchers() { + clearTimeout(this.watchChangeTimeout); disposeAll(this.watches.values()); - this.watches.clear(); } public async showVersionPicker(): Promise { @@ -1013,55 +1021,79 @@ export default class TypeScriptServiceClient extends Disposable implements IType } } - private createFileSystemWatcher( - id: number, - pattern: vscode.RelativePattern, - ignoreChangeEvents?: boolean, - ) { - const disposable = new DisposableStore(); - - const events = { updated: new Set(), created: new Set(), deleted: new Set() }; - - let timeout: NodeJS.Timeout | undefined; - disposable.add({ dispose: () => clearTimeout(timeout) }); - - const executeWatchChangeRequest = () => { - try { - // TODO:: not sure whwere we check typescript version but this has to be 5.4+ or 5.5 depeneding on which version of typescript the protocol changes go into - this.executeWithoutWaitingForResponse('watchChange', { + private scheduleExecuteWatchChangeRequest() { + if (!this.watchChangeTimeout) { + this.watchChangeTimeout = setTimeout(() => { + this.watchChangeTimeout = undefined; + const allEvents = Array.from(this.watchEvents, ([id, event]) => ({ id, - updated: events.updated.size > 0 ? Array.from(events.updated) : undefined, - created: events.created.size > 0 ? Array.from(events.created) : undefined, - deleted: events.deleted.size > 0 ? Array.from(events.deleted) : undefined - }); - } finally { - events.updated.clear(); - events.created.clear(); - events.deleted.clear(); + updated: event.updated && Array.from(event.updated), + created: event.created && Array.from(event.created), + deleted: event.deleted && Array.from(event.deleted) + })); + this.watchEvents.clear(); + this.executeWithoutWaitingForResponse('watchChange', allEvents); + }, 100); /* aggregate events over 100ms to reduce client<->server IPC overhead */ + } + } - timeout = undefined; + private addWatchEvent(id: number, eventType: keyof WatchEvent, path: string) { + let event = this.watchEvents.get(id); + const removeEvent = (typeOfEventToRemove: keyof WatchEvent) => { + if (event?.[typeOfEventToRemove]?.delete(path) && event[typeOfEventToRemove].size === 0) { + event[typeOfEventToRemove] = undefined; } }; - - const scheduleExecuteWatchChangeRequest = () => { - if (!timeout) { - timeout = setTimeout(() => executeWatchChangeRequest(), 100 /* aggregate events over 100ms to reduce client<->server IPC overhead */); + const aggregateEvent = () => { + if (!event) { + this.watchEvents.set(id, event = {}); } + (event[eventType] ??= new Set()).add(path); }; + switch (eventType) { + case 'created': + removeEvent('deleted'); + removeEvent('updated'); + aggregateEvent(); + break; + case 'deleted': + removeEvent('created'); + removeEvent('updated'); + aggregateEvent(); + break; + case 'updated': + if (event?.created?.has(path)) { + return; + } + removeEvent('deleted'); + aggregateEvent(); + break; + } + this.scheduleExecuteWatchChangeRequest(); + } + private createFileSystemWatcher( + id: number, + pattern: vscode.RelativePattern, + ignoreChangeEvents?: boolean, + ) { + const disposable = new DisposableStore(); const watcher = disposable.add(vscode.workspace.createFileSystemWatcher(pattern, { excludes: [] /* TODO:: need to fill in excludes list */, ignoreChangeEvents })); - disposable.add(watcher.onDidChange(changeFile => { - events.updated.add(changeFile.fsPath); - scheduleExecuteWatchChangeRequest(); - })); - disposable.add(watcher.onDidCreate(createFile => { - events.created.add(createFile.fsPath); - scheduleExecuteWatchChangeRequest(); - })); - disposable.add(watcher.onDidDelete(deletedFile => { - events.deleted.add(deletedFile.fsPath); - scheduleExecuteWatchChangeRequest(); - })); + disposable.add(watcher.onDidChange(changeFile => + this.addWatchEvent(id, 'updated', changeFile.fsPath) + )); + disposable.add(watcher.onDidCreate(createFile => + this.addWatchEvent(id, 'created', createFile.fsPath) + )); + disposable.add(watcher.onDidDelete(deletedFile => + this.addWatchEvent(id, 'deleted', deletedFile.fsPath) + )); + disposable.add({ + dispose: () => { + this.watchEvents.delete(id); + this.watches.delete(id); + } + }); if (this.watches.has(id)) { this.closeFileSystemWatcher(id); @@ -1075,7 +1107,6 @@ export default class TypeScriptServiceClient extends Disposable implements IType const existing = this.watches.get(id); if (existing) { existing.dispose(); - this.watches.delete(id); } } From 1cd941e7d08d00be84825c5cb64c83649e3f4d2a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 9 Apr 2024 12:54:18 +0200 Subject: [PATCH 13/15] :lipstick: --- extensions/typescript-language-features/package.json | 2 +- .../src/configuration/configuration.ts | 2 +- .../src/typescriptServiceClient.ts | 2 +- extensions/typescript-language-features/src/utils/dispose.ts | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index ba80a44d4a0b0..fb5383a2978b7 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1172,7 +1172,7 @@ "typescript.tsserver.experimental.useVsCodeWatcher": { "type": "boolean", "description": "%configuration.tsserver.useVsCodeWatcher%", - "default": true, + "default": false, "tags": [ "experimental" ] diff --git a/extensions/typescript-language-features/src/configuration/configuration.ts b/extensions/typescript-language-features/src/configuration/configuration.ts index 484c180609baa..47620bd0743b3 100644 --- a/extensions/typescript-language-features/src/configuration/configuration.ts +++ b/extensions/typescript-language-features/src/configuration/configuration.ts @@ -225,7 +225,7 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu } private readUseVsCodeWatcher(configuration: vscode.WorkspaceConfiguration): boolean { - return configuration.get('typescript.tsserver.experimental.useVsCodeWatcher', true); + return configuration.get('typescript.tsserver.experimental.useVsCodeWatcher', false); } private readWatchOptions(configuration: vscode.WorkspaceConfiguration): Proto.WatchOptions | undefined { diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 762714793767d..f0608dc5d8ce2 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -509,7 +509,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType private resetWatchers() { clearTimeout(this.watchChangeTimeout); - disposeAll(this.watches.values()); + disposeAll(Array.from(this.watches.values())); } public async showVersionPicker(): Promise { diff --git a/extensions/typescript-language-features/src/utils/dispose.ts b/extensions/typescript-language-features/src/utils/dispose.ts index effe8b726fc3b..a3730ee4540a6 100644 --- a/extensions/typescript-language-features/src/utils/dispose.ts +++ b/extensions/typescript-language-features/src/utils/dispose.ts @@ -5,10 +5,11 @@ import * as vscode from 'vscode'; -export function disposeAll(disposables: Iterable) { +export function disposeAll(disposables: vscode.Disposable[]) { for (const disposable of disposables) { disposable.dispose(); } + disposables.length = 0; } export interface IDisposable { From 0a0d36e2c8d6007e3bbe6a9af9013c2a779b46d9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 9 Apr 2024 12:57:24 +0200 Subject: [PATCH 14/15] fix todo --- .../typescript-language-features/src/typescriptServiceClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index f0608dc5d8ce2..78a77f23c9f0c 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -1001,7 +1001,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType vscode.Uri.file((event.body as Proto.CreateDirectoryWatcherEventBody).path), (event.body as Proto.CreateDirectoryWatcherEventBody).recursive ? '**' : '*' ), - event.body.ignoreUpdate // TODO when typescript.d.ts gets updated update the type info + (event.body as Proto.CreateDirectoryWatcherEventBody).ignoreUpdate ); break; From 0dbdd589df89515cba734cafae5c63764f0b14c1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 9 Apr 2024 13:14:31 +0200 Subject: [PATCH 15/15] enable in workspace --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 251f8c0617a0b..cb577d02703ea 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -172,4 +172,5 @@ "css.format.spaceAroundSelectorSeparator": true, "inlineChat.mode": "live", "typescript.enablePromptUseWorkspaceTsdk": true, + "typescript.tsserver.experimental.useVsCodeWatcher": true }