Skip to content

Commit 2b7ab22

Browse files
committed
feat(language_server): watch for fmt.configPath file content change
1 parent 545e9db commit 2b7ab22

File tree

2 files changed

+125
-28
lines changed

2 files changed

+125
-28
lines changed

crates/oxc_language_server/src/backend.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ impl LanguageServer for Backend {
327327
continue;
328328
};
329329

330-
let (diagnostics, watcher, formatter_activated) =
330+
let (diagnostics, watchers, formatter_activated) =
331331
worker.did_change_configuration(&option.options).await;
332332

333333
if formatter_activated && self.capabilities.get().is_some_and(|c| c.dynamic_formatting)
@@ -344,7 +344,7 @@ impl LanguageServer for Backend {
344344
}
345345
}
346346

347-
if let Some(watcher) = watcher
347+
if let Some(watchers) = watchers
348348
&& self.capabilities.get().is_some_and(|capabilities| capabilities.dynamic_watchers)
349349
{
350350
// remove the old watcher
@@ -357,7 +357,7 @@ impl LanguageServer for Backend {
357357
id: format!("watcher-{}", worker.get_root_uri().as_str()),
358358
method: "workspace/didChangeWatchedFiles".to_string(),
359359
register_options: Some(json!(DidChangeWatchedFilesRegistrationOptions {
360-
watchers: vec![watcher]
360+
watchers
361361
})),
362362
});
363363
}

crates/oxc_language_server/src/worker.rs

Lines changed: 122 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use tower_lsp_server::{
1111
};
1212

1313
use crate::{
14-
ConcurrentHashMap,
14+
ConcurrentHashMap, FORMAT_CONFIG_FILE,
1515
code_actions::{apply_all_fix_code_action, apply_fix_code_actions, fix_all_text_edit},
1616
formatter::server_formatter::ServerFormatter,
1717
linter::{
@@ -104,6 +104,19 @@ impl WorkspaceWorker {
104104
kind: Some(WatchKind::all()), // created, deleted, changed
105105
});
106106

107+
watchers.push(FileSystemWatcher {
108+
glob_pattern: GlobPattern::Relative(RelativePattern {
109+
base_uri: OneOf::Right(self.root_uri.clone()),
110+
pattern: options
111+
.lint
112+
.config_path
113+
.as_ref()
114+
.map_or(FORMAT_CONFIG_FILE, |v| v)
115+
.to_owned(),
116+
}),
117+
kind: Some(WatchKind::all()), // created, deleted, changed
118+
});
119+
107120
let Some(root_path) = &self.root_uri.to_file_path() else {
108121
return watchers;
109122
};
@@ -296,6 +309,7 @@ impl WorkspaceWorker {
296309
&self,
297310
_file_event: &FileEvent,
298311
) -> Option<ConcurrentHashMap<String, Vec<DiagnosticReport>>> {
312+
// TODO: the tools should implement a helper function to detect if the changed file is relevant
299313
let files = {
300314
let server_linter_guard = self.server_linter.read().await;
301315
let server_linter = server_linter_guard.as_ref()?;
@@ -320,8 +334,10 @@ impl WorkspaceWorker {
320334
) -> (
321335
// Diagnostic reports that need to be revalidated
322336
Option<ConcurrentHashMap<String, Vec<DiagnosticReport>>>,
323-
// File system watcher for lint config changes
324-
Option<FileSystemWatcher>,
337+
// File system watcher for lint/fmt config changes
338+
// - `None` if no watcher changes are needed
339+
// - empty vector if all watchers should be removed
340+
Option<Vec<FileSystemWatcher>>,
325341
// Is true, when the formatter was added to the workspace worker
326342
bool,
327343
) {
@@ -346,13 +362,35 @@ impl WorkspaceWorker {
346362
}
347363

348364
let mut formatting = false;
365+
366+
// create all watchers again, because maybe one tool configuration is changed
367+
// and we unregister the workspace watcher and register a new one.
368+
// Without adding the old watchers back, the client would not watch them anymore.
369+
//
370+
// TODO: create own watcher for each tool with its own id,
371+
// so we can unregister only the watcher that changed.
372+
let mut watchers = Vec::new();
373+
349374
if current_option.format != changed_options.format {
350375
if changed_options.format.experimental {
351376
debug!("experimental formatter enabled/restarted");
352377
// restart the formatter
353378
*self.server_formatter.write().await =
354379
Some(ServerFormatter::new(&self.root_uri, &changed_options.format));
355380
formatting = true;
381+
382+
watchers.push(FileSystemWatcher {
383+
glob_pattern: GlobPattern::Relative(RelativePattern {
384+
base_uri: OneOf::Right(self.root_uri.clone()),
385+
pattern: changed_options
386+
.format
387+
.config_path
388+
.as_ref()
389+
.map_or(FORMAT_CONFIG_FILE, |v| v)
390+
.to_owned(),
391+
}),
392+
kind: Some(WatchKind::all()), // created, deleted, changed
393+
});
356394
} else {
357395
debug!("experimental formatter disabled");
358396
*self.server_formatter.write().await = None;
@@ -371,29 +409,25 @@ impl WorkspaceWorker {
371409
};
372410
self.refresh_server_linter(&changed_options.lint).await;
373411

374-
if current_option.lint.config_path != changed_options.lint.config_path {
375-
return (
376-
Some(self.revalidate_diagnostics(files).await),
377-
Some(FileSystemWatcher {
378-
glob_pattern: GlobPattern::Relative(RelativePattern {
379-
base_uri: OneOf::Right(self.root_uri.clone()),
380-
pattern: changed_options
381-
.lint
382-
.config_path
383-
.as_ref()
384-
.unwrap_or(&"**/.oxlintrc.json".to_string())
385-
.to_owned(),
386-
}),
387-
kind: Some(WatchKind::all()), // created, deleted, changed
388-
}),
389-
formatting,
390-
);
391-
}
412+
watchers.push(FileSystemWatcher {
413+
glob_pattern: GlobPattern::Relative(RelativePattern {
414+
base_uri: OneOf::Right(self.root_uri.clone()),
415+
pattern: changed_options
416+
.lint
417+
.config_path
418+
.as_ref()
419+
.unwrap_or(&"**/.oxlintrc.json".to_string())
420+
.to_owned(),
421+
}),
422+
kind: Some(WatchKind::all()), // created, deleted, changed
423+
});
392424

393-
return (Some(self.revalidate_diagnostics(files).await), None, formatting);
425+
return (Some(self.revalidate_diagnostics(files).await), Some(watchers), formatting);
394426
}
395427

396-
(None, None, formatting)
428+
let watchers = if watchers.is_empty() { None } else { Some(watchers) };
429+
430+
(None, watchers, formatting)
397431
}
398432
}
399433

@@ -471,7 +505,7 @@ mod test_watchers {
471505
.block_on(async { self.worker.init_watchers().await })
472506
}
473507

474-
fn did_change_configuration(&self, options: &Options) -> Option<FileSystemWatcher> {
508+
fn did_change_configuration(&self, options: &Options) -> Option<Vec<FileSystemWatcher>> {
475509
let (_, watchers, _) = tokio::runtime::Runtime::new()
476510
.unwrap()
477511
.block_on(async { self.worker.did_change_configuration(options).await });
@@ -582,6 +616,68 @@ mod test_watchers {
582616
})
583617
);
584618
}
619+
620+
#[test]
621+
fn test_formatter_experimental_enabled() {
622+
let tester = Tester::new(
623+
"fixtures/watcher/default",
624+
&Options {
625+
format: crate::formatter::options::FormatOptions {
626+
experimental: true,
627+
..Default::default()
628+
},
629+
..Default::default()
630+
},
631+
);
632+
let watchers = tester.init_watchers();
633+
634+
assert_eq!(watchers.len(), 2);
635+
assert_eq!(
636+
watchers[0].glob_pattern,
637+
GlobPattern::Relative(RelativePattern {
638+
base_uri: OneOf::Right(tester.worker.get_root_uri().clone()),
639+
pattern: "**/.oxlintrc.json".to_string(),
640+
})
641+
);
642+
assert_eq!(
643+
watchers[1].glob_pattern,
644+
GlobPattern::Relative(RelativePattern {
645+
base_uri: OneOf::Right(tester.worker.get_root_uri().clone()),
646+
pattern: "oxfmtrc.json".to_string(),
647+
})
648+
);
649+
}
650+
651+
#[test]
652+
fn test_formatter_custom_config_path() {
653+
let tester = Tester::new(
654+
"fixtures/watcher/default",
655+
&Options {
656+
format: crate::formatter::options::FormatOptions {
657+
experimental: true,
658+
config_path: Some("configs/formatter.json".to_string()),
659+
},
660+
..Default::default()
661+
},
662+
);
663+
let watchers = tester.init_watchers();
664+
665+
assert_eq!(watchers.len(), 2);
666+
assert_eq!(
667+
watchers[0].glob_pattern,
668+
GlobPattern::Relative(RelativePattern {
669+
base_uri: OneOf::Right(tester.worker.get_root_uri().clone()),
670+
pattern: "**/.oxlintrc.json".to_string(),
671+
})
672+
);
673+
assert_eq!(
674+
watchers[1].glob_pattern,
675+
GlobPattern::Relative(RelativePattern {
676+
base_uri: OneOf::Right(tester.worker.get_root_uri().clone()),
677+
pattern: "configs/formatter.json".to_string(),
678+
})
679+
);
680+
}
585681
}
586682

587683
mod did_change_configuration {
@@ -613,8 +709,9 @@ mod test_watchers {
613709
})
614710
.unwrap();
615711

712+
assert_eq!(watchers.len(), 1);
616713
assert_eq!(
617-
watchers.glob_pattern,
714+
watchers[0].glob_pattern,
618715
GlobPattern::Relative(RelativePattern {
619716
base_uri: OneOf::Right(tester.worker.get_root_uri().clone()),
620717
pattern: "configs/lint.json".to_string(),

0 commit comments

Comments
 (0)