Skip to content

Commit a797a9e

Browse files
committed
feat(language_server): watch for fmt.configPath file content change
1 parent 33b6cde commit a797a9e

File tree

2 files changed

+129
-29
lines changed

2 files changed

+129
-29
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: 126 additions & 26 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,21 @@ impl WorkspaceWorker {
104104
kind: Some(WatchKind::all()), // created, deleted, changed
105105
});
106106

107+
if options.format.experimental {
108+
watchers.push(FileSystemWatcher {
109+
glob_pattern: GlobPattern::Relative(RelativePattern {
110+
base_uri: OneOf::Right(self.root_uri.clone()),
111+
pattern: options
112+
.format
113+
.config_path
114+
.as_ref()
115+
.map_or(FORMAT_CONFIG_FILE, |v| v)
116+
.to_owned(),
117+
}),
118+
kind: Some(WatchKind::all()), // created, deleted, changed
119+
});
120+
}
121+
107122
let Some(root_path) = &self.root_uri.to_file_path() else {
108123
return watchers;
109124
};
@@ -296,6 +311,7 @@ impl WorkspaceWorker {
296311
&self,
297312
_file_event: &FileEvent,
298313
) -> Option<ConcurrentHashMap<String, Vec<DiagnosticReport>>> {
314+
// TODO: the tools should implement a helper function to detect if the changed file is relevant
299315
let files = {
300316
let server_linter_guard = self.server_linter.read().await;
301317
let server_linter = server_linter_guard.as_ref()?;
@@ -320,8 +336,10 @@ impl WorkspaceWorker {
320336
) -> (
321337
// Diagnostic reports that need to be revalidated
322338
Option<ConcurrentHashMap<String, Vec<DiagnosticReport>>>,
323-
// File system watcher for lint config changes
324-
Option<FileSystemWatcher>,
339+
// File system watcher for lint/fmt config changes
340+
// - `None` if no watcher changes are needed
341+
// - empty vector if all watchers should be removed
342+
Option<Vec<FileSystemWatcher>>,
325343
// Is true, when the formatter was added to the workspace worker
326344
bool,
327345
) {
@@ -346,13 +364,35 @@ impl WorkspaceWorker {
346364
}
347365

348366
let mut formatting = false;
367+
368+
// create all watchers again, because maybe one tool configuration is changed
369+
// and we unregister the workspace watcher and register a new one.
370+
// Without adding the old watchers back, the client would not watch them anymore.
371+
//
372+
// TODO: create own watcher for each tool with its own id,
373+
// so we can unregister only the watcher that changed.
374+
let mut watchers = Vec::new();
375+
349376
if current_option.format != changed_options.format {
350377
if changed_options.format.experimental {
351378
debug!("experimental formatter enabled/restarted");
352379
// restart the formatter
353380
*self.server_formatter.write().await =
354381
Some(ServerFormatter::new(&self.root_uri, &changed_options.format));
355382
formatting = true;
383+
384+
watchers.push(FileSystemWatcher {
385+
glob_pattern: GlobPattern::Relative(RelativePattern {
386+
base_uri: OneOf::Right(self.root_uri.clone()),
387+
pattern: changed_options
388+
.format
389+
.config_path
390+
.as_ref()
391+
.map_or(FORMAT_CONFIG_FILE, |v| v)
392+
.to_owned(),
393+
}),
394+
kind: Some(WatchKind::all()), // created, deleted, changed
395+
});
356396
} else {
357397
debug!("experimental formatter disabled");
358398
*self.server_formatter.write().await = None;
@@ -371,29 +411,25 @@ impl WorkspaceWorker {
371411
};
372412
self.refresh_server_linter(&changed_options.lint).await;
373413

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-
}
414+
watchers.push(FileSystemWatcher {
415+
glob_pattern: GlobPattern::Relative(RelativePattern {
416+
base_uri: OneOf::Right(self.root_uri.clone()),
417+
pattern: changed_options
418+
.lint
419+
.config_path
420+
.as_ref()
421+
.unwrap_or(&"**/.oxlintrc.json".to_string())
422+
.to_owned(),
423+
}),
424+
kind: Some(WatchKind::all()), // created, deleted, changed
425+
});
392426

393-
return (Some(self.revalidate_diagnostics(files).await), None, formatting);
427+
return (Some(self.revalidate_diagnostics(files).await), Some(watchers), formatting);
394428
}
395429

396-
(None, None, formatting)
430+
let watchers = if watchers.is_empty() { None } else { Some(watchers) };
431+
432+
(None, watchers, formatting)
397433
}
398434
}
399435

@@ -471,7 +507,7 @@ mod test_watchers {
471507
.block_on(async { self.worker.init_watchers().await })
472508
}
473509

474-
fn did_change_configuration(&self, options: &Options) -> Option<FileSystemWatcher> {
510+
fn did_change_configuration(&self, options: &Options) -> Option<Vec<FileSystemWatcher>> {
475511
let (_, watchers, _) = tokio::runtime::Runtime::new()
476512
.unwrap()
477513
.block_on(async { self.worker.did_change_configuration(options).await });
@@ -483,7 +519,8 @@ mod test_watchers {
483519
use tower_lsp_server::lsp_types::{GlobPattern, OneOf, RelativePattern};
484520

485521
use crate::{
486-
linter::options::LintOptions, options::Options, worker::test_watchers::Tester,
522+
formatter::options::FormatOptions, linter::options::LintOptions, options::Options,
523+
worker::test_watchers::Tester,
487524
};
488525

489526
#[test]
@@ -582,6 +619,68 @@ mod test_watchers {
582619
})
583620
);
584621
}
622+
623+
#[test]
624+
fn test_formatter_experimental_enabled() {
625+
let tester = Tester::new(
626+
"fixtures/watcher/default",
627+
&Options {
628+
format: crate::formatter::options::FormatOptions {
629+
experimental: true,
630+
..Default::default()
631+
},
632+
..Default::default()
633+
},
634+
);
635+
let watchers = tester.init_watchers();
636+
637+
assert_eq!(watchers.len(), 2);
638+
assert_eq!(
639+
watchers[0].glob_pattern,
640+
GlobPattern::Relative(RelativePattern {
641+
base_uri: OneOf::Right(tester.worker.get_root_uri().clone()),
642+
pattern: "**/.oxlintrc.json".to_string(),
643+
})
644+
);
645+
assert_eq!(
646+
watchers[1].glob_pattern,
647+
GlobPattern::Relative(RelativePattern {
648+
base_uri: OneOf::Right(tester.worker.get_root_uri().clone()),
649+
pattern: ".oxfmtrc.json".to_string(),
650+
})
651+
);
652+
}
653+
654+
#[test]
655+
fn test_formatter_custom_config_path() {
656+
let tester = Tester::new(
657+
"fixtures/watcher/default",
658+
&Options {
659+
format: FormatOptions {
660+
experimental: true,
661+
config_path: Some("configs/formatter.json".to_string()),
662+
},
663+
..Default::default()
664+
},
665+
);
666+
let watchers = tester.init_watchers();
667+
668+
assert_eq!(watchers.len(), 2);
669+
assert_eq!(
670+
watchers[0].glob_pattern,
671+
GlobPattern::Relative(RelativePattern {
672+
base_uri: OneOf::Right(tester.worker.get_root_uri().clone()),
673+
pattern: "**/.oxlintrc.json".to_string(),
674+
})
675+
);
676+
assert_eq!(
677+
watchers[1].glob_pattern,
678+
GlobPattern::Relative(RelativePattern {
679+
base_uri: OneOf::Right(tester.worker.get_root_uri().clone()),
680+
pattern: "configs/formatter.json".to_string(),
681+
})
682+
);
683+
}
585684
}
586685

587686
mod did_change_configuration {
@@ -613,8 +712,9 @@ mod test_watchers {
613712
})
614713
.unwrap();
615714

715+
assert_eq!(watchers.len(), 1);
616716
assert_eq!(
617-
watchers.glob_pattern,
717+
watchers[0].glob_pattern,
618718
GlobPattern::Relative(RelativePattern {
619719
base_uri: OneOf::Right(tester.worker.get_root_uri().clone()),
620720
pattern: "configs/lint.json".to_string(),

0 commit comments

Comments
 (0)