Add support for rename with language servers that lack prepareRename (#23000)
This adds support for LSPs that use the old rename flow which does not first ask the LSP for the rename range and check that it is a valid range to rename. Closes #16663 Release Notes: * Fixed rename symbols action when the language server does not have the capability to prepare renames - such as `luau-lsp`.
This commit is contained in:
parent
b65dc8c566
commit
bda0c67ece
7 changed files with 268 additions and 66 deletions
|
@ -132,7 +132,7 @@ use project::{
|
|||
lsp_store::{FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
|
||||
project_settings::{GitGutterSetting, ProjectSettings},
|
||||
CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink,
|
||||
LspStore, Project, ProjectItem, ProjectTransaction, TaskSourceKind,
|
||||
LspStore, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction, TaskSourceKind,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use rpc::{proto::*, ErrorExt};
|
||||
|
@ -13941,7 +13941,28 @@ impl SemanticsProvider for Model<Project> {
|
|||
cx: &mut AppContext,
|
||||
) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
|
||||
Some(self.update(cx, |project, cx| {
|
||||
project.prepare_rename(buffer.clone(), position, cx)
|
||||
let buffer = buffer.clone();
|
||||
let task = project.prepare_rename(buffer.clone(), position, cx);
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
Ok(match task.await? {
|
||||
PrepareRenameResponse::Success(range) => Some(range),
|
||||
PrepareRenameResponse::InvalidPosition => None,
|
||||
PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
|
||||
// Fallback on using TreeSitter info to determine identifier range
|
||||
buffer.update(&mut cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
let (range, kind) = snapshot.surrounding_word(position);
|
||||
if kind != Some(CharKind::Word) {
|
||||
return None;
|
||||
}
|
||||
Some(
|
||||
snapshot.anchor_before(range.start)
|
||||
..snapshot.anchor_after(range.end),
|
||||
)
|
||||
})?
|
||||
}
|
||||
})
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
@ -14734,7 +14734,14 @@ fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
|
|||
#[gpui::test]
|
||||
async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
|
||||
let capabilities = lsp::ServerCapabilities {
|
||||
rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
|
||||
prepare_provider: Some(true),
|
||||
work_done_progress_options: Default::default(),
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
struct Fˇoo {}
|
||||
|
@ -14750,10 +14757,25 @@ async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
|
|||
);
|
||||
});
|
||||
|
||||
cx.update_editor(|e, cx| e.rename(&Rename, cx))
|
||||
.expect("Rename was not started")
|
||||
.await
|
||||
.expect("Rename failed");
|
||||
let mut prepare_rename_handler =
|
||||
cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
|
||||
Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
|
||||
start: lsp::Position {
|
||||
line: 0,
|
||||
character: 7,
|
||||
},
|
||||
end: lsp::Position {
|
||||
line: 0,
|
||||
character: 10,
|
||||
},
|
||||
})))
|
||||
});
|
||||
let prepare_rename_task = cx
|
||||
.update_editor(|e, cx| e.rename(&Rename, cx))
|
||||
.expect("Prepare rename was not started");
|
||||
prepare_rename_handler.next().await.unwrap();
|
||||
prepare_rename_task.await.expect("Prepare rename failed");
|
||||
|
||||
let mut rename_handler =
|
||||
cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
|
||||
let edit = lsp::TextEdit {
|
||||
|
@ -14774,11 +14796,11 @@ async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
|
|||
std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
|
||||
)))
|
||||
});
|
||||
cx.update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
|
||||
.expect("Confirm rename was not started")
|
||||
.await
|
||||
.expect("Confirm rename failed");
|
||||
let rename_task = cx
|
||||
.update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
|
||||
.expect("Confirm rename was not started");
|
||||
rename_handler.next().await.unwrap();
|
||||
rename_task.await.expect("Confirm rename failed");
|
||||
cx.run_until_parked();
|
||||
|
||||
// Despite two edits, only one is actually applied as those are identical
|
||||
|
@ -14787,6 +14809,67 @@ async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
|
|||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_rename_without_prepare(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
// These capabilities indicate that the server does not support prepare rename.
|
||||
let capabilities = lsp::ServerCapabilities {
|
||||
rename_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
};
|
||||
let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
struct Fˇoo {}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
let highlight_range = Point::new(0, 7)..Point::new(0, 10);
|
||||
let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
|
||||
editor.highlight_background::<DocumentHighlightRead>(
|
||||
&[highlight_range],
|
||||
|c| c.editor_document_highlight_read_background,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
cx.update_editor(|e, cx| e.rename(&Rename, cx))
|
||||
.expect("Prepare rename was not started")
|
||||
.await
|
||||
.expect("Prepare rename failed");
|
||||
|
||||
let mut rename_handler =
|
||||
cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
|
||||
let edit = lsp::TextEdit {
|
||||
range: lsp::Range {
|
||||
start: lsp::Position {
|
||||
line: 0,
|
||||
character: 7,
|
||||
},
|
||||
end: lsp::Position {
|
||||
line: 0,
|
||||
character: 10,
|
||||
},
|
||||
},
|
||||
new_text: "FooRenamed".to_string(),
|
||||
};
|
||||
Ok(Some(lsp::WorkspaceEdit::new(
|
||||
std::collections::HashMap::from_iter(Some((url, vec![edit]))),
|
||||
)))
|
||||
});
|
||||
let rename_task = cx
|
||||
.update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
|
||||
.expect("Confirm rename was not started");
|
||||
rename_handler.next().await.unwrap();
|
||||
rename_task.await.expect("Confirm rename failed");
|
||||
cx.run_until_parked();
|
||||
|
||||
// Correct range is renamed, as `surrounding_word` is used to find it.
|
||||
cx.assert_editor_state(indoc! {"
|
||||
struct FooRenamedˇ {}
|
||||
"});
|
||||
}
|
||||
|
||||
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
|
||||
point..point
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue