Merge pull request #887 from zed-industries/fix-typescript-rename

Antonio Scandurra created

Use document highlights to prepare rename if LSP doesn't support it

Change summary

crates/collab/src/rpc.rs          | 11 ++++++
crates/editor/src/editor.rs       | 55 ++++++++++++++++++++++++++++++++
crates/lsp/src/lsp.rs             |  4 ++
crates/project/src/lsp_command.rs |  8 ++++
crates/project/src/project.rs     | 11 ++++++
5 files changed, 86 insertions(+), 3 deletions(-)

Detailed changes

crates/collab/src/rpc.rs 🔗

@@ -3829,7 +3829,16 @@ mod tests {
             },
             Some(tree_sitter_rust::language()),
         );
-        let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
+        let mut fake_language_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
+                    prepare_provider: Some(true),
+                    work_done_progress_options: Default::default(),
+                })),
+                ..Default::default()
+            },
+            ..Default::default()
+        });
         lang_registry.add(Arc::new(language));
 
         // Connect to a server as 2 clients.

crates/editor/src/editor.rs 🔗

@@ -4632,7 +4632,23 @@ impl Editor {
         });
 
         Some(cx.spawn(|this, mut cx| async move {
-            if let Some(rename_range) = prepare_rename.await? {
+            let rename_range = if let Some(range) = prepare_rename.await? {
+                Some(range)
+            } else {
+                this.read_with(&cx, |this, cx| {
+                    let buffer = this.buffer.read(cx).snapshot(cx);
+                    let mut buffer_highlights = this
+                        .document_highlights_for_position(selection.head(), &buffer)
+                        .filter(|highlight| {
+                            highlight.start.excerpt_id() == selection.head().excerpt_id()
+                                && highlight.end.excerpt_id() == selection.head().excerpt_id()
+                        });
+                    buffer_highlights
+                        .next()
+                        .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
+                })
+            };
+            if let Some(rename_range) = rename_range {
                 let rename_buffer_range = rename_range.to_offset(&snapshot);
                 let cursor_offset_in_rename_range =
                     cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
@@ -5677,6 +5693,43 @@ impl Editor {
         self.background_highlights_in_range(start..end, &snapshot, theme)
     }
 
+    fn document_highlights_for_position<'a>(
+        &'a self,
+        position: Anchor,
+        buffer: &'a MultiBufferSnapshot,
+    ) -> impl 'a + Iterator<Item = &Range<Anchor>> {
+        let read_highlights = self
+            .background_highlights
+            .get(&TypeId::of::<DocumentHighlightRead>())
+            .map(|h| &h.1);
+        let write_highlights = self
+            .background_highlights
+            .get(&TypeId::of::<DocumentHighlightRead>())
+            .map(|h| &h.1);
+        let left_position = position.bias_left(buffer);
+        let right_position = position.bias_right(buffer);
+        read_highlights
+            .into_iter()
+            .chain(write_highlights)
+            .flat_map(move |ranges| {
+                let start_ix = match ranges.binary_search_by(|probe| {
+                    let cmp = probe.end.cmp(&left_position, &buffer);
+                    if cmp.is_ge() {
+                        Ordering::Greater
+                    } else {
+                        Ordering::Less
+                    }
+                }) {
+                    Ok(i) | Err(i) => i,
+                };
+
+                let right_position = right_position.clone();
+                ranges[start_ix..]
+                    .iter()
+                    .take_while(move |range| range.start.cmp(&right_position, &buffer).is_le())
+            })
+    }
+
     pub fn background_highlights_in_range(
         &self,
         search_range: Range<Anchor>,

crates/lsp/src/lsp.rs 🔗

@@ -292,6 +292,10 @@ impl LanguageServer {
                         }),
                         ..Default::default()
                     }),
+                    rename: Some(RenameClientCapabilities {
+                        prepare_support: Some(true),
+                        ..Default::default()
+                    }),
                     ..Default::default()
                 }),
                 experimental: Some(json!({

crates/project/src/lsp_command.rs 🔗

@@ -86,6 +86,14 @@ impl LspCommand for PrepareRename {
     type LspRequest = lsp::request::PrepareRenameRequest;
     type ProtoRequest = proto::PrepareRename;
 
+    fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
+        if let Some(lsp::OneOf::Right(rename)) = &capabilities.rename_provider {
+            rename.prepare_provider == Some(true)
+        } else {
+            false
+        }
+    }
+
     fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::TextDocumentPositionParams {
         lsp::TextDocumentPositionParams {
             text_document: lsp::TextDocumentIdentifier {

crates/project/src/project.rs 🔗

@@ -7466,7 +7466,16 @@ mod tests {
             },
             Some(tree_sitter_rust::language()),
         );
-        let mut fake_servers = language.set_fake_lsp_adapter(Default::default());
+        let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
+                    prepare_provider: Some(true),
+                    work_done_progress_options: Default::default(),
+                })),
+                ..Default::default()
+            },
+            ..Default::default()
+        });
 
         let fs = FakeFs::new(cx.background());
         fs.insert_tree(