mirror of
https://github.com/Microsoft/vscode
synced 2024-10-02 17:32:41 +00:00
Use vscode watches for tsserver (#193848)
This commit is contained in:
parent
e36423a09c
commit
b2c4302323
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -172,4 +172,5 @@
|
|||
"css.format.spaceAroundSelectorSeparator": true,
|
||||
"inlineChat.mode": "live",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"typescript.tsserver.experimental.useVsCodeWatcher": true
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255",
|
||||
"enabledApiProposals": [
|
||||
"workspaceTrust",
|
||||
"createFileSystemWatcher",
|
||||
"multiDocumentHighlightProvider",
|
||||
"mappedEditsProvider",
|
||||
"codeActionAI",
|
||||
|
@ -1168,6 +1169,14 @@
|
|||
"experimental"
|
||||
]
|
||||
},
|
||||
"typescript.tsserver.experimental.useVsCodeWatcher": {
|
||||
"type": "boolean",
|
||||
"description": "%configuration.tsserver.useVsCodeWatcher%",
|
||||
"default": false,
|
||||
"tags": [
|
||||
"experimental"
|
||||
]
|
||||
},
|
||||
"typescript.tsserver.watchOptions": {
|
||||
"type": "object",
|
||||
"description": "%configuration.tsserver.watchOptions%",
|
||||
|
|
|
@ -164,6 +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.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.",
|
||||
|
|
|
@ -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<boolean>('typescript.tsserver.experimental.enableProjectDiagnostics', false);
|
||||
}
|
||||
|
||||
protected readWatchOptions(configuration: vscode.WorkspaceConfiguration): Proto.WatchOptions | undefined {
|
||||
private readUseVsCodeWatcher(configuration: vscode.WorkspaceConfiguration): boolean {
|
||||
return configuration.get<boolean>('typescript.tsserver.experimental.useVsCodeWatcher', false);
|
||||
}
|
||||
|
||||
private readWatchOptions(configuration: vscode.WorkspaceConfiguration): Proto.WatchOptions | undefined {
|
||||
const watchOptions = configuration.get<Proto.WatchOptions>('typescript.tsserver.watchOptions');
|
||||
// Returned value may be a proxy. Clone it into a normal object
|
||||
return { ...(watchOptions ?? {}) };
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -88,6 +88,9 @@ export enum EventName {
|
|||
surveyReady = 'surveyReady',
|
||||
projectLoadingStart = 'projectLoadingStart',
|
||||
projectLoadingFinish = 'projectLoadingFinish',
|
||||
createFileWatcher = 'createFileWatcher',
|
||||
createDirectoryWatcher = 'createDirectoryWatcher',
|
||||
closeFileWatcher = 'closeFileWatcher',
|
||||
}
|
||||
|
||||
export enum OrganizeImportsMode {
|
||||
|
|
|
@ -271,6 +271,10 @@ export class TypeScriptServerSpawner {
|
|||
|
||||
args.push('--noGetErrOnBackgroundUpdate');
|
||||
|
||||
if (apiVersion.gte(API.v544) && configuration.useVsCodeWatcher) {
|
||||
args.push('--canUseWatchEvents');
|
||||
}
|
||||
|
||||
args.push('--validateDefaultNpmLocation');
|
||||
|
||||
if (isWebAndHasSharedArrayBuffers()) {
|
||||
|
|
|
@ -86,6 +86,7 @@ interface NoResponseTsServerRequests {
|
|||
'compilerOptionsForInferredProjects': [Proto.SetCompilerOptionsForInferredProjectsArgs, null];
|
||||
'reloadProjects': [null, null];
|
||||
'configurePlugin': [Proto.ConfigurePluginRequest, Proto.ConfigurePluginResponse];
|
||||
'watchChange': [Proto.Request, null];
|
||||
}
|
||||
|
||||
interface AsyncTsServerRequests {
|
||||
|
|
|
@ -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, DisposableStore, disposeAll } from './utils/dispose';
|
||||
import * as fileSchemes from './configuration/fileSchemes';
|
||||
import { Logger } from './logging/logger';
|
||||
import { isWeb, isWebAndHasSharedArrayBuffers } from './utils/platform';
|
||||
|
@ -97,6 +97,12 @@ export const emptyAuthority = 'ts-nul-authority';
|
|||
|
||||
export const inMemoryResourcePrefix = '^';
|
||||
|
||||
interface WatchEvent {
|
||||
updated?: Set<string>;
|
||||
created?: Set<string>;
|
||||
deleted?: Set<string>;
|
||||
}
|
||||
|
||||
export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient {
|
||||
|
||||
|
||||
|
@ -128,6 +134,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType
|
|||
private readonly versionProvider: ITypeScriptVersionProvider;
|
||||
private readonly processFactory: TsServerProcessFactory;
|
||||
|
||||
private readonly watches = new Map<number, Disposable>();
|
||||
private readonly watchEvents = new Map<number, WatchEvent>();
|
||||
private watchChangeTimeout: NodeJS.Timeout | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly context: vscode.ExtensionContext,
|
||||
onCaseInsenitiveFileSystem: boolean,
|
||||
|
@ -298,6 +308,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType
|
|||
}
|
||||
|
||||
this.loadingIndicator.reset();
|
||||
|
||||
this.resetWatchers();
|
||||
}
|
||||
|
||||
public restartTsServer(fromUserAction = false): void {
|
||||
|
@ -401,6 +413,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, {
|
||||
|
@ -493,6 +507,11 @@ export default class TypeScriptServiceClient extends Disposable implements IType
|
|||
return this.serverState;
|
||||
}
|
||||
|
||||
private resetWatchers() {
|
||||
clearTimeout(this.watchChangeTimeout);
|
||||
disposeAll(Array.from(this.watches.values()));
|
||||
}
|
||||
|
||||
public async showVersionPicker(): Promise<void> {
|
||||
this._versionManager.promptUserForVersion();
|
||||
}
|
||||
|
@ -594,6 +613,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
|
|||
}
|
||||
|
||||
private serviceExited(restart: boolean): void {
|
||||
this.resetWatchers();
|
||||
this.loadingIndicator.reset();
|
||||
|
||||
const previousState = this.serverState;
|
||||
|
@ -973,6 +993,120 @@ 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 as Proto.CreateDirectoryWatcherEventBody).id,
|
||||
new vscode.RelativePattern(
|
||||
vscode.Uri.file((event.body as Proto.CreateDirectoryWatcherEventBody).path),
|
||||
(event.body as Proto.CreateDirectoryWatcherEventBody).recursive ? '**' : '*'
|
||||
),
|
||||
(event.body as Proto.CreateDirectoryWatcherEventBody).ignoreUpdate
|
||||
);
|
||||
break;
|
||||
|
||||
case EventName.createFileWatcher:
|
||||
this.createFileSystemWatcher(
|
||||
(event.body as Proto.CreateFileWatcherEventBody).id,
|
||||
new vscode.RelativePattern(
|
||||
vscode.Uri.file((event.body as Proto.CreateFileWatcherEventBody).path),
|
||||
'*'
|
||||
)
|
||||
);
|
||||
break;
|
||||
|
||||
case EventName.closeFileWatcher:
|
||||
this.closeFileSystemWatcher(event.body.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleExecuteWatchChangeRequest() {
|
||||
if (!this.watchChangeTimeout) {
|
||||
this.watchChangeTimeout = setTimeout(() => {
|
||||
this.watchChangeTimeout = undefined;
|
||||
const allEvents = Array.from(this.watchEvents, ([id, event]) => ({
|
||||
id,
|
||||
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 */
|
||||
}
|
||||
}
|
||||
|
||||
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 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 =>
|
||||
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);
|
||||
}
|
||||
this.watches.set(id, disposable);
|
||||
}
|
||||
|
||||
private closeFileSystemWatcher(
|
||||
id: number,
|
||||
) {
|
||||
const existing = this.watches.get(id);
|
||||
if (existing) {
|
||||
existing.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
import * as vscode from 'vscode';
|
||||
|
||||
export function disposeAll(disposables: vscode.Disposable[]) {
|
||||
while (disposables.length) {
|
||||
const item = disposables.pop();
|
||||
item?.dispose();
|
||||
for (const disposable of disposables) {
|
||||
disposable.dispose();
|
||||
}
|
||||
disposables.length = 0;
|
||||
}
|
||||
|
||||
export interface IDisposable {
|
||||
|
@ -42,3 +42,12 @@ export abstract class Disposable {
|
|||
return this._isDisposed;
|
||||
}
|
||||
}
|
||||
|
||||
export class DisposableStore extends Disposable {
|
||||
|
||||
public add<T extends IDisposable>(disposable: T): T {
|
||||
this._register(disposable);
|
||||
|
||||
return disposable;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"include": [
|
||||
"src/**/*",
|
||||
"../../src/vscode-dts/vscode.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",
|
||||
|
|
Loading…
Reference in a new issue