Skip to content
Merged
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
40 changes: 31 additions & 9 deletions crates/oxc_language_server/src/formatter/server_formatter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::path::Path;
use std::path::{Path, PathBuf};

use log::warn;
use oxc_allocator::Allocator;
Expand All @@ -13,7 +13,7 @@ use tower_lsp_server::{
};

use crate::formatter::options::FormatOptions as LSPFormatOptions;
use crate::{FORMAT_CONFIG_FILE, utils::normalize_path};
use crate::{FORMAT_CONFIG_FILES, utils::normalize_path};
pub struct ServerFormatter {
options: FormatOptions,
}
Expand Down Expand Up @@ -77,10 +77,28 @@ impl ServerFormatter {
)])
}

fn search_config_file(root_path: &Path, config_path: Option<&String>) -> Option<PathBuf> {
if let Some(config_path) = config_path {
let config = normalize_path(root_path.join(config_path));
if config.try_exists().is_ok_and(|exists| exists) {
return Some(config);
}

warn!(
"Config file not found: {}, searching for `{}` in the root path",
config.to_string_lossy(),
FORMAT_CONFIG_FILES.join(", ")
);
}

FORMAT_CONFIG_FILES.iter().find_map(|&file| {
let config = normalize_path(root_path.join(file));
config.try_exists().is_ok_and(|exists| exists).then_some(config)
})
}

fn get_format_options(root_path: &Path, config_path: Option<&String>) -> FormatOptions {
let config_path = config_path.map_or(FORMAT_CONFIG_FILE, |v| v);
let config = normalize_path(root_path.join(config_path));
let oxfmtrc = if config.try_exists().is_ok_and(|exists| exists) {
let oxfmtrc = if let Some(config) = Self::search_config_file(root_path, config_path) {
if let Ok(oxfmtrc) = Oxfmtrc::from_file(&config) {
oxfmtrc
} else {
Expand All @@ -90,7 +108,7 @@ impl ServerFormatter {
} else {
warn!(
"Config file not found: {}, fallback to default config",
config.to_string_lossy()
config_path.unwrap_or(&FORMAT_CONFIG_FILES.join(", "))
);
Oxfmtrc::default()
};
Expand All @@ -105,15 +123,19 @@ impl ServerFormatter {
}

#[expect(clippy::unused_self)]
pub fn get_watcher_patterns(&self, options: &LSPFormatOptions) -> Pattern {
options.config_path.as_ref().map_or(FORMAT_CONFIG_FILE, |v| v).to_owned()
pub fn get_watcher_patterns(&self, options: &LSPFormatOptions) -> Vec<Pattern> {
if let Some(config_path) = options.config_path.as_ref() {
return vec![config_path.to_string()];
}

FORMAT_CONFIG_FILES.iter().map(|file| (*file).to_string()).collect()
}

pub fn get_changed_watch_patterns(
&self,
old_options: &LSPFormatOptions,
new_options: &LSPFormatOptions,
) -> Option<Pattern> {
) -> Option<Vec<Pattern>> {
if old_options != new_options && new_options.experimental {
return Some(self.get_watcher_patterns(new_options));
}
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_language_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::backend::Backend;
type ConcurrentHashMap<K, V> = papaya::HashMap<K, V, FxBuildHasher>;

const LINT_CONFIG_FILE: &str = ".oxlintrc.json";
const FORMAT_CONFIG_FILE: &str = ".oxfmtrc.json";
const FORMAT_CONFIG_FILES: &[&str; 2] = &[".oxfmtrc.json", ".oxfmtrc.jsonc"];

#[tokio::main]
async fn main() {
Expand Down
50 changes: 32 additions & 18 deletions crates/oxc_language_server/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,16 @@ impl WorkspaceWorker {
id: format!("watcher-formatter-{}", self.root_uri.as_str()),
method: "workspace/didChangeWatchedFiles".to_string(),
register_options: Some(json!(DidChangeWatchedFilesRegistrationOptions {
watchers: vec![FileSystemWatcher {
glob_pattern: GlobPattern::Relative(RelativePattern {
base_uri: OneOf::Right(self.root_uri.clone()),
pattern: format_patterns
}),
kind: Some(WatchKind::all()), // created, deleted, changed
}]
watchers: format_patterns
.into_iter()
.map(|pattern| FileSystemWatcher {
glob_pattern: GlobPattern::Relative(RelativePattern {
base_uri: OneOf::Right(self.root_uri.clone()),
pattern,
}),
kind: Some(WatchKind::all()), // created, deleted, changed
})
.collect::<Vec<_>>(),
})),
});
}
Expand Down Expand Up @@ -378,7 +381,7 @@ impl WorkspaceWorker {
formatting = true;

// Extract pattern data without holding the lock
let pattern = {
let patterns = {
let formatter_guard = self.server_formatter.read().await;
formatter_guard.as_ref().and_then(|formatter| {
formatter.get_changed_watch_patterns(
Expand All @@ -388,7 +391,7 @@ impl WorkspaceWorker {
})
};

if let Some(pattern) = pattern {
if let Some(patterns) = patterns {
if current_option.format.experimental {
// unregister the old watcher
unregistrations.push(Unregistration {
Expand All @@ -401,13 +404,16 @@ impl WorkspaceWorker {
id: format!("watcher-formatter-{}", self.root_uri.as_str()),
method: "workspace/didChangeWatchedFiles".to_string(),
register_options: Some(json!(DidChangeWatchedFilesRegistrationOptions {
watchers: vec![FileSystemWatcher {
glob_pattern: GlobPattern::Relative(RelativePattern {
base_uri: OneOf::Right(self.root_uri.clone()),
pattern
}),
kind: Some(WatchKind::all()), // created, deleted, changed
}]
watchers: patterns
.into_iter()
.map(|pattern| FileSystemWatcher {
glob_pattern: GlobPattern::Relative(RelativePattern {
base_uri: OneOf::Right(self.root_uri.clone()),
pattern,
}),
kind: Some(WatchKind::all()), // created, deleted, changed
})
.collect::<Vec<_>>(),
})),
});
}
Expand Down Expand Up @@ -679,7 +685,11 @@ mod test_watchers {

assert_eq!(watchers.len(), 2);
tester.assert_eq_registration(&watchers[0], "linter", &["**/.oxlintrc.json"]);
tester.assert_eq_registration(&watchers[1], "formatter", &[".oxfmtrc.json"]);
tester.assert_eq_registration(
&watchers[1],
"formatter",
&[".oxfmtrc.json", ".oxfmtrc.jsonc"],
);
}

#[test]
Expand Down Expand Up @@ -835,7 +845,11 @@ mod test_watchers {

assert_eq!(unregistrations.len(), 0);
assert_eq!(registration.len(), 1);
tester.assert_eq_registration(&registration[0], "formatter", &[".oxfmtrc.json"]);
tester.assert_eq_registration(
&registration[0],
"formatter",
&[".oxfmtrc.json", ".oxfmtrc.jsonc"],
);
}

#[test]
Expand Down
Loading