diff --git a/crates/oxc_language_server/src/capabilities.rs b/crates/oxc_language_server/src/capabilities.rs index 5502cc041d273..c8dcf99d4e570 100644 --- a/crates/oxc_language_server/src/capabilities.rs +++ b/crates/oxc_language_server/src/capabilities.rs @@ -14,6 +14,7 @@ pub struct Capabilities { pub workspace_execute_command: bool, pub workspace_configuration: bool, pub dynamic_watchers: bool, + pub dynamic_formatting: bool, } impl From for Capabilities { @@ -39,6 +40,12 @@ impl From for Capabilities { watched_files.dynamic_registration.is_some_and(|dynamic| dynamic) }) }); + // TODO: enable it when we support formatting + // let formatting = value.text_document.as_ref().is_some_and(|text_document| { + // text_document.formatting.is_some_and(|formatting| { + // formatting.dynamic_registration.is_some_and(|dynamic| dynamic) + // }) + // }); Self { code_action_provider, @@ -46,6 +53,7 @@ impl From for Capabilities { workspace_execute_command, workspace_configuration, dynamic_watchers, + dynamic_formatting: false, } } } diff --git a/crates/oxc_language_server/src/file_system.rs b/crates/oxc_language_server/src/file_system.rs new file mode 100644 index 0000000000000..34e36bbcdc794 --- /dev/null +++ b/crates/oxc_language_server/src/file_system.rs @@ -0,0 +1,27 @@ +use tower_lsp_server::lsp_types::Uri; + +use crate::ConcurrentHashMap; + +#[derive(Debug, Default)] +pub struct LSPFileSystem { + files: ConcurrentHashMap, +} + +impl LSPFileSystem { + pub fn clear(&self) { + self.files.pin().clear(); + } + + pub fn set(&self, uri: &Uri, content: String) { + self.files.pin().insert(uri.clone(), content); + } + + #[expect(dead_code)] // used for the oxc_formatter in the future + pub fn get(&self, uri: &Uri) -> Option { + self.files.pin().get(uri).cloned() + } + + pub fn remove(&self, uri: &Uri) { + self.files.pin().remove(uri); + } +} diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index 4253076772897..ad09b3636b7f6 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -22,6 +22,7 @@ use tower_lsp_server::{ mod capabilities; mod code_actions; mod commands; +mod file_system; mod formatter; mod linter; mod options; @@ -36,6 +37,8 @@ use linter::server_linter::ServerLinterRun; use options::{Options, WorkspaceOption}; use worker::WorkspaceWorker; +use crate::file_system::LSPFileSystem; + type ConcurrentHashMap = papaya::HashMap; const OXC_CONFIG_FILE: &str = ".oxlintrc.json"; @@ -51,6 +54,7 @@ struct Backend { // 2. `workspace/didChangeWorkspaceFolders` request workspace_workers: Arc>>, capabilities: OnceCell, + file_system: Arc>, } impl LanguageServer for Backend { @@ -203,6 +207,9 @@ impl LanguageServer for Backend { async fn shutdown(&self) -> Result<()> { self.clear_all_diagnostics().await; + if self.capabilities.get().is_some_and(|option| option.dynamic_formatting) { + self.file_system.write().await.clear(); + } Ok(()) } @@ -444,6 +451,12 @@ impl LanguageServer for Backend { let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else { return; }; + + if self.capabilities.get().is_some_and(|option| option.dynamic_formatting) { + // saving the file means we can read again from the file system + self.file_system.write().await.remove(uri); + } + if let Some(diagnostics) = worker.lint_file(uri, None, ServerLinterRun::OnSave).await { self.client .publish_diagnostics( @@ -464,6 +477,13 @@ impl LanguageServer for Backend { return; }; let content = params.content_changes.first().map(|c| c.text.clone()); + + if self.capabilities.get().is_some_and(|option| option.dynamic_formatting) + && let Some(content) = &content + { + self.file_system.write().await.set(uri, content.to_string()); + } + if let Some(diagnostics) = worker.lint_file(uri, content, ServerLinterRun::OnType).await { self.client .publish_diagnostics( @@ -483,6 +503,11 @@ impl LanguageServer for Backend { }; let content = params.text_document.text; + + if self.capabilities.get().is_some_and(|option| option.dynamic_formatting) { + self.file_system.write().await.set(uri, content.to_string()); + } + if let Some(diagnostics) = worker.lint_file(uri, Some(content), ServerLinterRun::Always).await { @@ -502,6 +527,9 @@ impl LanguageServer for Backend { let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else { return; }; + if self.capabilities.get().is_some_and(|option| option.dynamic_formatting) { + self.file_system.write().await.remove(uri); + } worker.remove_diagnostics(¶ms.text_document.uri).await; } @@ -626,6 +654,7 @@ async fn main() { client, workspace_workers: Arc::new(RwLock::new(vec![])), capabilities: OnceCell::new(), + file_system: Arc::new(RwLock::new(LSPFileSystem::default())), }) .finish();