🌐 AI搜索 & 代理 主页
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The language server accepts various settings through the `initializationOptions`
| plugins | object[] | An array of `{ name: string, location: string, languages?: string[] }` objects for registering a Typescript plugins. **Default**: [] |
| preferences | object | Preferences passed to the Typescript (`tsserver`) process. See [`preferences` options](#preferences-options) for more details. |
| supportsMoveToFileCodeAction | boolean | Whether the client supports the "Move to file" interactive code action. See [`supportsMoveToFileCodeAction` option](#supportsmovetofilecodeaction-option) for more details. |
| tsserver | object | Options related to the `tsserver` process. See below for more |
| tsserver | object | Options related to the `tsserver` process. See [tsserver options](#tsserver-options). |

### `plugins` option

Expand Down Expand Up @@ -74,6 +74,8 @@ Specifies additional options related to the internal `tsserver` process, like tr

**trace** [string] The verbosity of logging of the tsserver communication. Delivered through the LSP messages and not related to file logging. Allowed values are: `'off'`, `'messages'`, `'verbose'`. **Default**: `'off'`

**useClientFileWatcher** [boolean] Use client's file watcher instead of TypeScript's built-in one. Requires TypeScript 5.4.4+ in the workspace. **Default**: `false`

**useSyntaxServer** [string] Whether a dedicated server is launched to more quickly handle syntax related operations, such as computing diagnostics or code folding. **Default**: `'auto'`. Allowed values:
- `'auto'`: Spawn both a full server and a lighter weight server dedicated to syntax operations. The syntax server is used to speed up syntax operations and provide IntelliSense while projects are loading.
- `'never'`: Don't use a dedicated syntax server. Use a single server to handle all IntelliSense operations.
Expand Down
5 changes: 5 additions & 0 deletions src/lsp-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface LspClient {
rename(args: lsp.TextDocumentPositionParams): Promise<any>;
sendNotification<P>(type: lsp.NotificationType<P>, params: P): Promise<void>;
getWorkspaceConfiguration<R = unknown>(scopeUri: string, section: string): Promise<R>;
registerDidChangeWatchedFilesCapability(watchers: lsp.FileSystemWatcher[]): Promise<lsp.Disposable>;
}

// Hack around the LSP library that makes it otherwise impossible to differentiate between Null and Client-initiated reporter.
Expand Down Expand Up @@ -82,4 +83,8 @@ export class LspClientImpl implements LspClient {
async sendNotification<P>(type: lsp.NotificationType<P>, params: P): Promise<void> {
await this.connection.sendNotification(type, params);
}

async registerDidChangeWatchedFilesCapability(watchers: lsp.FileSystemWatcher[]): Promise<lsp.Disposable> {
return await this.connection.client.register(lsp.DidChangeWatchedFilesNotification.type, { watchers });
}
}
1 change: 1 addition & 0 deletions src/lsp-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function createLspConnection(options: LspConnectionOptions): lsp.Connecti
connection.onDidSaveTextDocument(server.didSaveTextDocument.bind(server));
connection.onDidCloseTextDocument(server.didCloseTextDocument.bind(server));
connection.onDidChangeTextDocument(server.didChangeTextDocument.bind(server));
connection.onDidChangeWatchedFiles(server.didChangeWatchedFiles.bind(server));

connection.onCodeAction(server.codeAction.bind(server));
connection.onCodeActionResolve(server.codeActionResolve.bind(server));
Expand Down
49 changes: 48 additions & 1 deletion src/lsp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { Position, Range } from './utils/typeConverters.js';
import { CodeActionKind } from './utils/types.js';
import { CommandManager } from './commands/commandManager.js';
import { CodeActionManager } from './features/codeActions/codeActionManager.js';
import { WatchEventManager } from './watchEventManager.js';

export class LspServer {
private tsClient: TsClient;
Expand All @@ -61,6 +62,7 @@ export class LspServer {
private cachedNavTreeResponse = new CachedResponse<ts.server.protocol.NavTreeResponse>();
private implementationsCodeLensProvider: TypeScriptImplementationsCodeLensProvider | null = null;
private referencesCodeLensProvider: TypeScriptReferencesCodeLensProvider | null = null;
private watchEventManager: WatchEventManager | null = null;

constructor(private options: LspServerConfiguration) {
this.logger = new PrefixingLogger(options.logger, '[lspserver]');
Expand Down Expand Up @@ -91,6 +93,8 @@ export class LspServer {
}

shutdown(): void {
this.watchEventManager?.dispose();
this.watchEventManager = null;
this.tsClient.shutdown();
}

Expand Down Expand Up @@ -148,6 +152,20 @@ export class LspServer {
useLabelDetailsInCompletionEntries: this.features.completionLabelDetails,
});

const supportsFileWatcherRegistration = Boolean(workspace?.didChangeWatchedFiles?.dynamicRegistration);
const supportsRelativePatterns = workspace?.didChangeWatchedFiles?.relativePatternSupport !== false;
const requestedWatchEvents = tsserver?.useClientFileWatcher ?? false;
const typescriptSupportsWatchEvents = typescriptVersion.version?.gte(API.v544);
const canUseWatchEvents = Boolean(requestedWatchEvents && supportsFileWatcherRegistration && supportsRelativePatterns && typescriptSupportsWatchEvents);

if (requestedWatchEvents && !supportsFileWatcherRegistration) {
this.logger.logIgnoringVerbosity(LogLevel.Warning, 'Client does not support dynamic file watcher registration; tsserver watch events will stay disabled.');
} else if (requestedWatchEvents && !supportsRelativePatterns) {
this.logger.logIgnoringVerbosity(LogLevel.Warning, 'Client does not support relative file watcher patterns; tsserver watch events will stay disabled.');
} else if (requestedWatchEvents && !typescriptSupportsWatchEvents) {
this.logger.logIgnoringVerbosity(LogLevel.Warning, 'tsserver watch events require TypeScript 5.4.4 or newer; disabling useClientFileWatcher.');
}

const tsserverLogVerbosity = tsserver?.logVerbosity && TsServerLogLevel.fromString(tsserver.logVerbosity);
const started = this.tsClient.start(
this.workspaceRoot,
Expand All @@ -169,7 +187,8 @@ export class LspServer {
throw new Error(`tsserver process has exited (exit code: ${exitCode}, signal: ${signal}). Stopping the server.`);
}
},
useSyntaxServer: toSyntaxServerConfiguration(userInitializationOptions.tsserver?.useSyntaxServer),
useClientFileWatcher: tsserver?.useClientFileWatcher ?? false,
useSyntaxServer: toSyntaxServerConfiguration(tsserver?.useSyntaxServer),
});
if (!started) {
throw new Error('tsserver process has failed to start.');
Expand All @@ -181,6 +200,16 @@ export class LspServer {
process.exit();
});

if (canUseWatchEvents) {
this.watchEventManager = new WatchEventManager({
lspClient: this.options.lspClient,
logger: this.logger,
workspaceFolders: this.getWorkspaceFolders(),
sendWatchChanges: changes => this.tsClient.sendWatchChanges(changes),
caseInsensitive: onCaseInsensitiveFileSystem(),
});
}

this.fileConfigurationManager.setGlobalConfiguration(this.workspaceRoot, hostInfo);
this.registerHandlers();

Expand Down Expand Up @@ -319,6 +348,7 @@ export class LspServer {
version: apiVersion.displayName,
source: typescriptVersionSource,
});
this.watchEventManager?.onInitialized();
}

private findTypescriptVersion(userTsserverPath: string | undefined, fallbackTsserverPath: string | undefined): TypeScriptVersion | null {
Expand Down Expand Up @@ -366,6 +396,16 @@ export class LspServer {
return undefined;
}

private getWorkspaceFolders(): URI[] {
if (this.initializeParams?.workspaceFolders?.length) {
return this.initializeParams.workspaceFolders.map(folder => URI.parse(folder.uri));
}
if (this.workspaceRoot) {
return [URI.file(this.workspaceRoot)];
}
return [];
}

didChangeConfiguration(params: lsp.DidChangeConfigurationParams): void {
this.fileConfigurationManager.setWorkspaceConfiguration((params.settings || {}) as WorkspaceConfiguration);
const ignoredDiagnosticCodes = this.fileConfigurationManager.workspaceConfiguration.diagnostics?.ignoredCodes || [];
Expand Down Expand Up @@ -412,6 +452,10 @@ export class LspServer {
// do nothing
}

didChangeWatchedFiles(params: lsp.DidChangeWatchedFilesParams): void {
this.watchEventManager?.handleFileChanges(params);
}

async definition(params: lsp.DefinitionParams, token?: lsp.CancellationToken): Promise<lsp.Definition | lsp.DefinitionLink[] | undefined> {
return this.getDefinition({
type: this.features.definitionLinkSupport ? CommandTypes.DefinitionAndBoundSpan : CommandTypes.Definition,
Expand Down Expand Up @@ -1175,6 +1219,9 @@ export class LspServer {
}

protected onTsEvent(event: ts.server.protocol.Event): void {
if (this.watchEventManager?.handleTsserverEvent(event)) {
return;
}
const eventName = event.event as EventName;
if (eventName === EventName.semanticDiag || eventName === EventName.syntaxDiag || eventName === EventName.suggestionDiag) {
const diagnosticEvent = event as ts.server.protocol.DiagnosticEvent;
Expand Down
8 changes: 6 additions & 2 deletions src/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export class TestLspClient implements LspClient {
}

publishDiagnostics(args: lsp.PublishDiagnosticsParams): void {
return this.options.publishDiagnostics(args);
this.options.publishDiagnostics(args);
}

showErrorMessage(message: string): void {
Expand All @@ -184,7 +184,7 @@ export class TestLspClient implements LspClient {
return { applied: true };
}

rename(): Promise<void> {
rename(_args: lsp.TextDocumentPositionParams): Promise<any> {
throw new Error('unsupported');
}

Expand All @@ -195,6 +195,10 @@ export class TestLspClient implements LspClient {
async getWorkspaceConfiguration(_scopeUri: string, _section: string): Promise<any> {
return Promise.resolve(undefined);
}

registerDidChangeWatchedFilesCapability(_watchers: lsp.FileSystemWatcher[]): Promise<lsp.Disposable> {
throw new Error('unsupported');
}
}

export class TestLspServer extends LspServer {
Expand Down
1 change: 1 addition & 0 deletions src/ts-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('ts server client', () => {
plugins: [],
trace: Trace.Off,
typescriptVersion: bundled!,
useClientFileWatcher: false,
useSyntaxServer: SyntaxServerConfiguration.Never,
},
);
Expand Down
5 changes: 5 additions & 0 deletions src/ts-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export interface TsClientOptions {
plugins: TypeScriptPlugin[];
onEvent?: (event: ts.server.protocol.Event) => void;
onExit?: (exitCode: number | null, signal: NodeJS.Signals | null) => void;
useClientFileWatcher: boolean;
useSyntaxServer: SyntaxServerConfiguration;
}

Expand Down Expand Up @@ -346,6 +347,10 @@ export class TsClient implements ITypeScriptServiceClient {
}
}

public sendWatchChanges(args: ts.server.protocol.WatchChangeRequestArgs | readonly ts.server.protocol.WatchChangeRequestArgs[]): void {
this.executeWithoutWaitingForResponse(CommandTypes.WatchChange, args);
}

start(
workspaceRoot: string | undefined,
options: TsClientOptions,
Expand Down
11 changes: 10 additions & 1 deletion src/ts-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ export enum CommandTypes {
PrepareCallHierarchy = 'prepareCallHierarchy',
ProvideCallHierarchyIncomingCalls = 'provideCallHierarchyIncomingCalls',
ProvideCallHierarchyOutgoingCalls = 'provideCallHierarchyOutgoingCalls',
ProvideInlayHints = 'provideInlayHints'
ProvideInlayHints = 'provideInlayHints',
WatchChange = 'watchChange',
}

export enum HighlightSpanKind {
Expand Down Expand Up @@ -288,6 +289,9 @@ export const enum EventName {
surveyReady = 'surveyReady',
projectLoadingStart = 'projectLoadingStart',
projectLoadingFinish = 'projectLoadingFinish',
createFileWatcher = 'createFileWatcher',
createDirectoryWatcher = 'createDirectoryWatcher',
closeFileWatcher = 'closeFileWatcher',
}

export class KindModifiers {
Expand Down Expand Up @@ -411,6 +415,11 @@ interface TsserverOptions {
* @default 'off'
*/
trace?: TraceValue;
/**
* Use client's file watcher instead of TypeScript's built-in one. Requires TypeScript 5.4.4+ in the workspace.
* @default false
*/
useClientFileWatcher?: boolean;
/**
* Whether a dedicated server is launched to more quickly handle syntax related operations, such as computing diagnostics or code folding.
*
Expand Down
4 changes: 4 additions & 0 deletions src/tsServer/spawner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ export class TypeScriptServerSpawner {
args.push('--npmLocation', `"${npmLocation}"`);
}

if (configuration.useClientFileWatcher && apiVersion.gte(API.v544)) {
args.push('--canUseWatchEvents');
}

args.push('--locale', locale || 'en');
// args.push('--noGetErrOnBackgroundUpdate');
args.push('--validateDefaultNpmLocation');
Expand Down
2 changes: 1 addition & 1 deletion src/typescriptService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export interface NoResponseTsServerRequests {
[CommandTypes.Configure]: [ts.server.protocol.ConfigureRequestArguments, ts.server.protocol.ConfigureResponse];
[CommandTypes.ConfigurePlugin]: [ts.server.protocol.ConfigurePluginRequestArguments, ts.server.protocol.ConfigurePluginResponse];
[CommandTypes.Open]: [ts.server.protocol.OpenRequestArgs, null];
}
[CommandTypes.WatchChange]: [ts.server.protocol.WatchChangeRequest['arguments'], null];}

export interface AsyncTsServerRequests {
[CommandTypes.Geterr]: [ts.server.protocol.GeterrRequestArgs, ts.server.protocol.Response];
Expand Down
1 change: 1 addition & 0 deletions src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export default class API {
public static readonly v510 = API.fromSimpleString('5.1.0');
public static readonly v520 = API.fromSimpleString('5.2.0');
public static readonly v540 = API.fromSimpleString('5.4.0');
public static readonly v544 = API.fromSimpleString('5.4.4');

public static fromVersionString(versionString: string): API {
let version = semver.valid(versionString);
Expand Down
Loading