Merge pull request #533 from zed-industries/range-formatting

Antonio Scandurra created

Use LSP range formatting when document formatting is not available

Change summary

crates/lsp/src/lsp.rs         |  2 +
crates/project/src/project.rs | 50 ++++++++++++++++++++++++++++++------
crates/text/src/rope.rs       |  4 ++
crates/text/src/text.rs       |  4 ++
4 files changed, 51 insertions(+), 9 deletions(-)

Detailed changes

crates/lsp/src/lsp.rs 🔗

@@ -524,6 +524,8 @@ impl LanguageServer {
         ServerCapabilities {
             document_highlight_provider: Some(OneOf::Left(true)),
             code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
+            document_formatting_provider: Some(OneOf::Left(true)),
+            document_range_formatting_provider: Some(OneOf::Left(true)),
             ..Default::default()
         }
     }

crates/project/src/project.rs 🔗

@@ -1314,15 +1314,47 @@ impl Project {
             }
 
             for (buffer, buffer_abs_path, lang_server) in local_buffers {
-                let lsp_edits = lang_server
-                    .request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
-                        text_document: lsp::TextDocumentIdentifier::new(
-                            lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
-                        ),
-                        options: Default::default(),
-                        work_done_progress_params: Default::default(),
-                    })
-                    .await?;
+                let capabilities = if let Some(capabilities) = lang_server.capabilities().await {
+                    capabilities
+                } else {
+                    continue;
+                };
+
+                let text_document = lsp::TextDocumentIdentifier::new(
+                    lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
+                );
+                let lsp_edits = if capabilities
+                    .document_formatting_provider
+                    .map_or(false, |provider| provider != lsp::OneOf::Left(false))
+                {
+                    lang_server
+                        .request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
+                            text_document,
+                            options: Default::default(),
+                            work_done_progress_params: Default::default(),
+                        })
+                        .await?
+                } else if capabilities
+                    .document_range_formatting_provider
+                    .map_or(false, |provider| provider != lsp::OneOf::Left(false))
+                {
+                    let buffer_start = lsp::Position::new(0, 0);
+                    let buffer_end = buffer
+                        .read_with(&cx, |buffer, _| buffer.max_point_utf16())
+                        .to_lsp_position();
+                    lang_server
+                        .request::<lsp::request::RangeFormatting>(
+                            lsp::DocumentRangeFormattingParams {
+                                text_document,
+                                range: lsp::Range::new(buffer_start, buffer_end),
+                                options: Default::default(),
+                                work_done_progress_params: Default::default(),
+                            },
+                        )
+                        .await?
+                } else {
+                    continue;
+                };
 
                 if let Some(lsp_edits) = lsp_edits {
                     let edits = buffer

crates/text/src/rope.rs 🔗

@@ -126,6 +126,10 @@ impl Rope {
         self.chunks.extent(&())
     }
 
+    pub fn max_point_utf16(&self) -> PointUtf16 {
+        self.chunks.extent(&())
+    }
+
     pub fn cursor(&self, offset: usize) -> Cursor {
         Cursor::new(self, offset)
     }

crates/text/src/text.rs 🔗

@@ -1526,6 +1526,10 @@ impl BufferSnapshot {
         self.visible_text.max_point()
     }
 
+    pub fn max_point_utf16(&self) -> PointUtf16 {
+        self.visible_text.max_point_utf16()
+    }
+
     pub fn point_to_offset(&self, point: Point) -> usize {
         self.visible_text.point_to_offset(point)
     }