Match user selection when renaming (#10748)

Kirill Bulatov created

Initial state:
<img width="337" alt="Screenshot 2024-04-19 at 01 35 34"
src="https://github.com/zed-industries/zed/assets/2690773/1720d06c-54ed-4479-b694-ea478ac5a55a">

Before the fix:
<img width="319" alt="Screenshot 2024-04-19 at 01 35 39"
src="https://github.com/zed-industries/zed/assets/2690773/64429088-e75b-44c3-b5d4-31a841e69a1d">

After:
<img width="336" alt="Screenshot 2024-04-19 at 01 36 43"
src="https://github.com/zed-industries/zed/assets/2690773/c523e549-c546-4a70-aa33-629912598466">
 

Release Notes:

- Improved rename selections to match the user ones

Change summary

crates/collab/src/tests/editor_tests.rs | 48 +++++++++++++++++++++++++++
crates/editor/src/editor.rs             | 23 +++++++++++-
2 files changed, 69 insertions(+), 2 deletions(-)

Detailed changes

crates/collab/src/tests/editor_tests.rs 🔗

@@ -736,12 +736,60 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
             6..9
         );
         rename.editor.update(cx, |rename_editor, cx| {
+            let rename_selection = rename_editor.selections.newest::<usize>(cx);
+            assert_eq!(
+                rename_selection.range(),
+                0..3,
+                "Rename that was triggered from zero selection caret, should propose the whole word."
+            );
             rename_editor.buffer().update(cx, |rename_buffer, cx| {
                 rename_buffer.edit([(0..3, "THREE")], None, cx);
             });
         });
     });
 
+    // Cancel the rename, and repeat the same, but use selections instead of cursor movement
+    editor_b.update(cx_b, |editor, cx| {
+        editor.cancel(&editor::actions::Cancel, cx);
+    });
+    let prepare_rename = editor_b.update(cx_b, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges([7..8]));
+        editor.rename(&Rename, cx).unwrap()
+    });
+
+    fake_language_server
+        .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
+            assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
+            assert_eq!(params.position, lsp::Position::new(0, 8));
+            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
+                lsp::Position::new(0, 6),
+                lsp::Position::new(0, 9),
+            ))))
+        })
+        .next()
+        .await
+        .unwrap();
+    prepare_rename.await.unwrap();
+    editor_b.update(cx_b, |editor, cx| {
+        use editor::ToOffset;
+        let rename = editor.pending_rename().unwrap();
+        let buffer = editor.buffer().read(cx).snapshot(cx);
+        let lsp_rename_start = rename.range.start.to_offset(&buffer);
+        let lsp_rename_end = rename.range.end.to_offset(&buffer);
+        assert_eq!(lsp_rename_start..lsp_rename_end, 6..9);
+        rename.editor.update(cx, |rename_editor, cx| {
+            let rename_selection = rename_editor.selections.newest::<usize>(cx);
+            assert_eq!(
+                rename_selection.range(),
+                1..2,
+                "Rename that was triggered from a selection, should have the same selection range in the rename proposal"
+            );
+            rename_editor.buffer().update(cx, |rename_buffer, cx| {
+                rename_buffer.edit([(0..lsp_rename_end - lsp_rename_start, "THREE")], None, cx);
+            });
+        });
+    });
+
     let confirm_rename = editor_b.update(cx_b, |editor, cx| {
         Editor::confirm_rename(editor, &ConfirmRename, cx).unwrap()
     });

crates/editor/src/editor.rs 🔗

@@ -8094,7 +8094,7 @@ impl Editor {
             .buffer
             .read(cx)
             .text_anchor_for_position(selection.head(), cx)?;
-        let (tail_buffer, _) = self
+        let (tail_buffer, cursor_buffer_position_end) = self
             .buffer
             .read(cx)
             .text_anchor_for_position(selection.tail(), cx)?;
@@ -8104,6 +8104,7 @@ impl Editor {
 
         let snapshot = cursor_buffer.read(cx).snapshot();
         let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
+        let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
         let prepare_rename = project.update(cx, |project, cx| {
             project.prepare_rename(cursor_buffer.clone(), cursor_buffer_offset, cx)
         });
@@ -8132,6 +8133,8 @@ impl Editor {
                     let rename_buffer_range = rename_range.to_offset(&snapshot);
                     let cursor_offset_in_rename_range =
                         cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
+                    let cursor_offset_in_rename_range_end =
+                        cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
 
                     this.take_rename(false, cx);
                     let buffer = this.buffer.read(cx).read(cx);
@@ -8160,7 +8163,23 @@ impl Editor {
                         editor.buffer.update(cx, |buffer, cx| {
                             buffer.edit([(0..0, old_name.clone())], None, cx)
                         });
-                        editor.select_all(&SelectAll, cx);
+                        let rename_selection_range = match cursor_offset_in_rename_range
+                            .cmp(&cursor_offset_in_rename_range_end)
+                        {
+                            Ordering::Equal => {
+                                editor.select_all(&SelectAll, cx);
+                                return editor;
+                            }
+                            Ordering::Less => {
+                                cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
+                            }
+                            Ordering::Greater => {
+                                cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
+                            }
+                        };
+                        editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                            s.select_ranges([rename_selection_range]);
+                        });
                         editor
                     });