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
116 changes: 67 additions & 49 deletions crates/oxc_language_server/src/formatter/server_formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,78 @@ use tower_lsp_server::{

use crate::formatter::options::FormatOptions as LSPFormatOptions;
use crate::{FORMAT_CONFIG_FILES, utils::normalize_path};

pub struct ServerFormatterBuilder {
root_uri: Uri,
options: LSPFormatOptions,
}

impl ServerFormatterBuilder {
pub fn new(root_uri: Uri, options: LSPFormatOptions) -> Self {
Self { root_uri, options }
}

pub fn build(self) -> ServerFormatter {
let root_path = self.root_uri.to_file_path().unwrap();

ServerFormatter::new(Self::get_format_options(
&root_path,
self.options.config_path.as_ref(),
))
}

fn get_format_options(root_path: &Path, config_path: Option<&String>) -> FormatOptions {
let oxfmtrc = if let Some(config) = Self::search_config_file(root_path, config_path) {
if let Ok(oxfmtrc) = Oxfmtrc::from_file(&config) {
oxfmtrc
} else {
warn!("Failed to initialize oxfmtrc config: {}", config.to_string_lossy());
Oxfmtrc::default()
}
} else {
warn!(
"Config file not found: {}, fallback to default config",
config_path.unwrap_or(&FORMAT_CONFIG_FILES.join(", "))
);
Oxfmtrc::default()
};

match oxfmtrc.into_format_options() {
Ok(options) => options,
Err(err) => {
warn!("Failed to parse oxfmtrc config: {err}, fallback to default config");
FormatOptions::default()
}
}
}

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)
})
}
}
pub struct ServerFormatter {
options: FormatOptions,
}

impl ServerFormatter {
pub fn new(root_uri: &Uri, options: &LSPFormatOptions) -> Self {
let root_path = root_uri.to_file_path().unwrap();

Self { options: Self::get_format_options(&root_path, options.config_path.as_ref()) }
pub fn new(options: FormatOptions) -> Self {
Self { options }
}

pub fn run_single(&self, uri: &Uri, content: Option<String>) -> Option<Vec<TextEdit>> {
Expand Down Expand Up @@ -71,51 +134,6 @@ 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 oxfmtrc = if let Some(config) = Self::search_config_file(root_path, config_path) {
if let Ok(oxfmtrc) = Oxfmtrc::from_file(&config) {
oxfmtrc
} else {
warn!("Failed to initialize oxfmtrc config: {}", config.to_string_lossy());
Oxfmtrc::default()
}
} else {
warn!(
"Config file not found: {}, fallback to default config",
config_path.unwrap_or(&FORMAT_CONFIG_FILES.join(", "))
);
Oxfmtrc::default()
};

match oxfmtrc.into_format_options() {
Ok(options) => options,
Err(err) => {
warn!("Failed to parse oxfmtrc config: {err}, fallback to default config");
FormatOptions::default()
}
}
}

#[expect(clippy::unused_self)]
pub fn get_watcher_patterns(&self, options: &LSPFormatOptions) -> Vec<Pattern> {
if let Some(config_path) = options.config_path.as_ref() {
Expand Down
16 changes: 10 additions & 6 deletions crates/oxc_language_server/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use tower_lsp_server::{

use crate::{
code_actions::{apply_all_fix_code_action, apply_fix_code_actions, fix_all_text_edit},
formatter::{options::FormatOptions, server_formatter::ServerFormatter},
formatter::{
options::FormatOptions,
server_formatter::{ServerFormatter, ServerFormatterBuilder},
},
linter::{
error_with_position::DiagnosticReport,
options::{LintOptions, Run},
Expand Down Expand Up @@ -80,7 +83,7 @@ impl WorkspaceWorker {
if options.format.experimental {
debug!("experimental formatter enabled");
*self.server_formatter.write().await =
Some(ServerFormatter::new(&self.root_uri, &options.format));
Some(ServerFormatterBuilder::new(self.root_uri.clone(), options.format).build());
}
}

Expand Down Expand Up @@ -186,8 +189,9 @@ impl WorkspaceWorker {
/// Restart the server formatter with the current options
/// This will recreate the formatter and re-read the config files.
/// Call this when the options have changed and the formatter needs to be updated.
async fn refresh_server_formatter(&self, format_options: &FormatOptions) {
let server_formatter = ServerFormatter::new(&self.root_uri, format_options);
async fn refresh_server_formatter(&self, format_options: FormatOptions) {
let server_formatter =
ServerFormatterBuilder::new(self.root_uri.clone(), format_options).build();

*self.server_formatter.write().await = Some(server_formatter);
}
Expand Down Expand Up @@ -347,7 +351,7 @@ impl WorkspaceWorker {

if options.format.experimental {
tokio::join!(
self.refresh_server_formatter(&options.format),
self.refresh_server_formatter(options.format),
self.refresh_server_linter(options.lint)
);
} else {
Expand Down Expand Up @@ -404,7 +408,7 @@ impl WorkspaceWorker {

if current_option.format != changed_options.format {
if changed_options.format.experimental {
self.refresh_server_formatter(&changed_options.format).await;
self.refresh_server_formatter(changed_options.format.clone()).await;
formatting = true;

// Extract pattern data without holding the lock
Expand Down
Loading