diff --git a/apps/oxfmt/src/lsp/server_formatter.rs b/apps/oxfmt/src/lsp/server_formatter.rs index 4adf2aa1bec87..acdc6ae1cc656 100644 --- a/apps/oxfmt/src/lsp/server_formatter.rs +++ b/apps/oxfmt/src/lsp/server_formatter.rs @@ -243,7 +243,12 @@ impl Tool for ServerFormatter { } } - fn run_format(&self, uri: &Uri, content: Option<&str>) -> Result, String> { + fn run_format( + &self, + uri: &Uri, + content: Option<&str>, + _range: Option<&Range>, + ) -> Result, String> { let Some(path) = uri.to_file_path() else { return Err("Invalid file URI".to_string()) }; if self.is_ignored(&path) { diff --git a/crates/oxc_language_server/README.md b/crates/oxc_language_server/README.md index 6e00d37bbc39e..7cdb594933e36 100644 --- a/crates/oxc_language_server/README.md +++ b/crates/oxc_language_server/README.md @@ -189,6 +189,10 @@ The server will lint the file and report the diagnostics back to the client. Returns a list of [TextEdit](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textEdit) +#### [textDocument/rangeFormatting](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rangeFormatting) + +Returns a list of [TextEdit](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textEdit) only specific for the provided range. + ## Optional LSP Specifications from Client ### Client diff --git a/crates/oxc_language_server/src/backend.rs b/crates/oxc_language_server/src/backend.rs index 237954c02c451..48d3d818700a0 100644 --- a/crates/oxc_language_server/src/backend.rs +++ b/crates/oxc_language_server/src/backend.rs @@ -13,9 +13,9 @@ use tower_lsp_server::{ DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportKind, DocumentDiagnosticReportResult, DocumentFormattingParams, - ExecuteCommandParams, FullDocumentDiagnosticReport, InitializeParams, InitializeResult, - InitializedParams, MessageType, RelatedFullDocumentDiagnosticReport, ServerInfo, TextEdit, - Uri, + DocumentRangeFormattingParams, ExecuteCommandParams, FullDocumentDiagnosticReport, + InitializeParams, InitializeResult, InitializedParams, MessageType, + RelatedFullDocumentDiagnosticReport, ServerInfo, TextEdit, Uri, }, }; use tracing::{debug, error, info, warn}; @@ -800,7 +800,40 @@ impl LanguageServer for Backend { let Some(worker) = Self::find_worker_for_uri(&workers, uri) else { return Ok(None); }; - match worker.format_file(uri, self.file_system.read().await.get(uri).as_deref()).await { + match worker.format_file(uri, self.file_system.read().await.get(uri).as_deref(), None).await + { + Ok(edits) => { + if edits.is_empty() { + return Ok(None); + } + Ok(Some(edits)) + } + Err(err) => { + Err(Error { code: ErrorCode::ServerError(1), message: Cow::Owned(err), data: None }) + } + } + } + + /// It will return text edits to format the document if formatting is enabled for the workspace. + /// + /// See: + async fn range_formatting( + &self, + params: DocumentRangeFormattingParams, + ) -> Result>> { + let uri = ¶ms.text_document.uri; + let workers = self.workspace_workers.read().await; + let Some(worker) = Self::find_worker_for_uri(&workers, uri) else { + return Ok(None); + }; + match worker + .format_file( + uri, + self.file_system.read().await.get(uri).as_deref(), + Some(¶ms.range), + ) + .await + { Ok(edits) => { if edits.is_empty() { return Ok(None); diff --git a/crates/oxc_language_server/src/tool.rs b/crates/oxc_language_server/src/tool.rs index 9658e481e5c00..6784b74d78a88 100644 --- a/crates/oxc_language_server/src/tool.rs +++ b/crates/oxc_language_server/src/tool.rs @@ -91,13 +91,19 @@ pub trait Tool: Send + Sync { /// Format the content of the given URI. /// If `content` is `None`, the tool should read the content from the file system. + /// If `range` is `Some`, the tool should format only the specified range. /// Returns a vector of `TextEdit` representing the formatting changes. /// /// Not all tools will implement formatting, so the default implementation returns empty vector. /// /// # Errors /// Return [`Err`] when an error occurs, ignoring formatting should return [`Ok`] with an empty vector. - fn run_format(&self, _uri: &Uri, _content: Option<&str>) -> Result, String> { + fn run_format( + &self, + _uri: &Uri, + _content: Option<&str>, + _range: Option<&Range>, + ) -> Result, String> { Ok(Vec::new()) } diff --git a/crates/oxc_language_server/src/worker.rs b/crates/oxc_language_server/src/worker.rs index 21d1a276cb7b7..bc4282f100096 100644 --- a/crates/oxc_language_server/src/worker.rs +++ b/crates/oxc_language_server/src/worker.rs @@ -192,9 +192,10 @@ impl WorkspaceWorker { &self, uri: &Uri, content: Option<&str>, + range: Option<&Range>, ) -> Result, String> { for tool in self.tools.read().await.iter() { - let edits = tool.run_format(uri, content)?; + let edits = tool.run_format(uri, content, range)?; // If no edits are made, continue to the next tool if edits.is_empty() { continue;