Skip to content

Commit c69436f

Browse files
committed
refactor(language_server): prepare for dynamicRegistration of fileWatchers
1 parent b1dae1c commit c69436f

4 files changed

Lines changed: 73 additions & 76 deletions

File tree

crates/oxc_language_server/src/main.rs

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use futures::future::join_all;
22
use log::{debug, info, warn};
33
use options::{Options, Run, WorkspaceOption};
44
use rustc_hash::FxBuildHasher;
5+
use serde_json::json;
56
use std::str::FromStr;
67
use tokio::sync::{Mutex, OnceCell, SetError};
78
use tower_lsp_server::{
@@ -10,9 +11,10 @@ use tower_lsp_server::{
1011
lsp_types::{
1112
CodeActionParams, CodeActionResponse, ConfigurationItem, Diagnostic,
1213
DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams,
13-
DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
14-
DidSaveTextDocumentParams, ExecuteCommandParams, InitializeParams, InitializeResult,
15-
InitializedParams, ServerInfo, Uri, WorkspaceEdit,
14+
DidChangeWatchedFilesRegistrationOptions, DidChangeWorkspaceFoldersParams,
15+
DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams,
16+
ExecuteCommandParams, InitializeParams, InitializeResult, InitializedParams, Registration,
17+
ServerInfo, Uri, WorkspaceEdit,
1618
},
1719
};
1820
// #
@@ -152,26 +154,43 @@ impl LanguageServer for Backend {
152154
}
153155
}
154156

155-
if needed_configurations.is_empty() {
156-
return;
157+
if !needed_configurations.is_empty() {
158+
let configurations = if capabilities.workspace_configuration {
159+
self.request_workspace_configuration(needed_configurations.keys().collect()).await
160+
} else {
161+
// every worker should be initialized already in `initialize` request
162+
vec![Some(Options::default()); needed_configurations.len()]
163+
};
164+
165+
for (index, worker) in needed_configurations.values().enumerate() {
166+
worker
167+
.init_linter(
168+
configurations
169+
.get(index)
170+
.unwrap_or(&None)
171+
.as_ref()
172+
.unwrap_or(&Options::default()),
173+
)
174+
.await;
175+
}
157176
}
158177

159-
let configurations = if capabilities.workspace_configuration {
160-
self.request_workspace_configuration(needed_configurations.keys().collect()).await
161-
} else {
162-
// every worker should be initialized already in `initialize` request
163-
vec![Some(Options::default()); needed_configurations.len()]
164-
};
178+
// init all file watchers
179+
if capabilities.dynamic_watchers {
180+
let mut watchers = vec![];
181+
for worker in workers {
182+
watchers.extend(worker.init_watchers().await);
183+
}
165184

166-
for (index, worker) in needed_configurations.values().enumerate() {
167-
worker
168-
.init_linter(
169-
configurations
170-
.get(index)
171-
.unwrap_or(&None)
172-
.as_ref()
173-
.unwrap_or(&Options::default()),
174-
)
185+
let _ = self
186+
.client
187+
.register_capability(vec![Registration {
188+
id: "global-watcher".to_string(), // ToDo: send it with an identifier so we can remove it when needed
189+
method: "workspace/didChangeWatchedFiles".to_string(),
190+
register_options: Some(json!(DidChangeWatchedFilesRegistrationOptions {
191+
watchers
192+
})),
193+
}])
175194
.await;
176195
}
177196
}

crates/oxc_language_server/src/worker.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use rustc_hash::FxBuildHasher;
55
use tokio::sync::{Mutex, RwLock};
66
use tower_lsp_server::{
77
UriExt,
8-
lsp_types::{CodeActionOrCommand, Diagnostic, FileEvent, Range, TextEdit, Uri},
8+
lsp_types::{
9+
CodeActionOrCommand, Diagnostic, FileEvent, FileSystemWatcher, GlobPattern, OneOf, Range,
10+
RelativePattern, TextEdit, Uri, WatchKind,
11+
},
912
};
1013

1114
use crate::{
@@ -50,6 +53,31 @@ impl WorkspaceWorker {
5053
*self.server_linter.write().await = Some(ServerLinter::new(&self.root_uri, options));
5154
}
5255

56+
pub async fn init_watchers(&self) -> Vec<FileSystemWatcher> {
57+
// ToDo: check with ServerLinter for `extends` files (oxc-project/oxc@10373)
58+
59+
let mut path_watchers = vec![];
60+
if let Some(config_path) = &self.options.lock().await.config_path {
61+
path_watchers.push(FileSystemWatcher {
62+
glob_pattern: GlobPattern::Relative(RelativePattern {
63+
base_uri: OneOf::Right(self.root_uri.clone()),
64+
pattern: config_path.to_string(),
65+
}),
66+
kind: Some(WatchKind::all()), // created, deleted, changed
67+
});
68+
} else {
69+
path_watchers.push(FileSystemWatcher {
70+
glob_pattern: GlobPattern::Relative(RelativePattern {
71+
base_uri: OneOf::Right(self.root_uri.clone()),
72+
pattern: "**/.oxlintrc.json".to_string(),
73+
}),
74+
kind: Some(WatchKind::all()), // created, deleted, changed
75+
});
76+
}
77+
78+
path_watchers
79+
}
80+
5381
pub async fn needs_init_linter(&self) -> bool {
5482
self.server_linter.read().await.is_none()
5583
}

editors/vscode/client/ConfigService.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ConfigurationChangeEvent, Uri, workspace, WorkspaceFolder } from 'vscode';
22
import { IDisposable } from './types';
33
import { VSCodeConfig } from './VSCodeConfig';
4-
import { oxlintConfigFileName, WorkspaceConfig, WorkspaceConfigInterface } from './WorkspaceConfig';
4+
import { WorkspaceConfig, WorkspaceConfigInterface } from './WorkspaceConfig';
55

66
export class ConfigService implements IDisposable {
77
public static readonly namespace = 'oxc';
@@ -70,16 +70,6 @@ export class ConfigService implements IDisposable {
7070
return false;
7171
}
7272

73-
public getOxlintCustomConfigs(): string[] {
74-
const customConfigs: string[] = [];
75-
for (const [path, config] of this.workspaceConfigs.entries()) {
76-
if (config.configPath && config.configPath !== oxlintConfigFileName) {
77-
customConfigs.push(`${path}/${config.configPath}`);
78-
}
79-
}
80-
return customConfigs;
81-
}
82-
8373
private async onVscodeConfigChange(event: ConfigurationChangeEvent): Promise<void> {
8474
let isConfigChanged = false;
8575

editors/vscode/client/extension.ts

Lines changed: 4 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { promises as fsPromises } from 'node:fs';
33
import {
44
commands,
55
ExtensionContext,
6-
FileSystemWatcher,
76
StatusBarAlignment,
87
StatusBarItem,
98
ThemeColor,
@@ -23,7 +22,6 @@ import { Executable, LanguageClient, LanguageClientOptions, ServerOptions } from
2322

2423
import { join } from 'node:path';
2524
import { ConfigService } from './ConfigService';
26-
import { oxlintConfigFileName } from './WorkspaceConfig';
2725

2826
const languageClientName = 'oxc';
2927
const outputChannelName = 'Oxc';
@@ -44,8 +42,6 @@ let client: LanguageClient | undefined;
4442

4543
let myStatusBarItem: StatusBarItem;
4644

47-
const globalWatchers: FileSystemWatcher[] = [];
48-
4945
export async function activate(context: ExtensionContext) {
5046
const configService = new ConfigService();
5147
const restartCommand = commands.registerCommand(
@@ -122,7 +118,6 @@ export async function activate(context: ExtensionContext) {
122118
);
123119

124120
const outputChannel = window.createOutputChannel(outputChannelName, { log: true });
125-
const fileWatchers = createFileEventWatchers(configService.getOxlintCustomConfigs());
126121

127122
context.subscriptions.push(
128123
applyAllFixesFile,
@@ -131,11 +126,6 @@ export async function activate(context: ExtensionContext) {
131126
toggleEnable,
132127
configService,
133128
outputChannel,
134-
{
135-
dispose: () => {
136-
globalWatchers.forEach((watcher) => watcher.dispose());
137-
},
138-
},
139129
);
140130

141131
async function findBinary(): Promise<string> {
@@ -209,10 +199,6 @@ export async function activate(context: ExtensionContext) {
209199
language: lang,
210200
scheme: 'file',
211201
})),
212-
synchronize: {
213-
// Notify the server about file config changes in the workspace
214-
fileEvents: fileWatchers,
215-
},
216202
initializationOptions: configService.languageServerConfig,
217203
outputChannel,
218204
traceOutputChannel: outputChannel,
@@ -294,13 +280,9 @@ export async function activate(context: ExtensionContext) {
294280
return;
295281
}
296282

297-
if (needRestart) {
298-
client.clientOptions.synchronize = client.clientOptions.synchronize ?? {};
299-
client.clientOptions.synchronize.fileEvents = createFileEventWatchers(configService.getOxlintCustomConfigs());
300-
301-
if (client.isRunning()) {
302-
await client.restart();
303-
}
283+
// Server does not support currently adding/removing watchers on the fly
284+
if (needRestart && client.isRunning()) {
285+
await client.restart();
304286
}
305287
});
306288

@@ -317,9 +299,7 @@ export async function activate(context: ExtensionContext) {
317299
client.clientOptions.initializationOptions = this.languageServerConfig;
318300

319301
if (configService.effectsWorkspaceConfigPathChange(event)) {
320-
client.clientOptions.synchronize = client.clientOptions.synchronize ?? {};
321-
client.clientOptions.synchronize.fileEvents = createFileEventWatchers(this.getOxlintCustomConfigs());
322-
302+
// Server does not support currently adding/removing watchers on the fly
323303
if (client.isRunning()) {
324304
await client.restart();
325305
}
@@ -366,23 +346,3 @@ export async function deactivate(): Promise<void> {
366346
await client.stop();
367347
client = undefined;
368348
}
369-
370-
// FileSystemWatcher are not ready on the start and can take some seconds on bigger repositories
371-
function createFileEventWatchers(configAbsolutePaths: string[]): FileSystemWatcher[] {
372-
// cleanup old watchers
373-
globalWatchers.forEach((watcher) => watcher.dispose());
374-
globalWatchers.length = 0;
375-
376-
// create new watchers
377-
let localWatchers: FileSystemWatcher[] = [];
378-
if (configAbsolutePaths.length) {
379-
localWatchers = configAbsolutePaths.map((path) => workspace.createFileSystemWatcher(path));
380-
}
381-
382-
localWatchers.push(workspace.createFileSystemWatcher(`**/${oxlintConfigFileName}`));
383-
384-
// assign watchers to global variable, so we can cleanup them on next call
385-
globalWatchers.push(...localWatchers);
386-
387-
return localWatchers;
388-
}

0 commit comments

Comments
 (0)