aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-lsp/src/client.rs25
-rw-r--r--helix-term/src/commands/lsp.rs136
2 files changed, 125 insertions, 36 deletions
diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs
index 95f3ea34..9fa118fb 100644
--- a/helix-lsp/src/client.rs
+++ b/helix-lsp/src/client.rs
@@ -359,7 +359,7 @@ impl Client {
}),
rename: Some(lsp::RenameClientCapabilities {
dynamic_registration: Some(false),
- prepare_support: Some(false),
+ prepare_support: Some(true),
prepare_support_default_behavior: None,
honors_change_annotations: Some(false),
}),
@@ -1034,6 +1034,29 @@ impl Client {
Some(self.call::<lsp::request::DocumentSymbolRequest>(params))
}
+ pub fn prepare_rename(
+ &self,
+ text_document: lsp::TextDocumentIdentifier,
+ position: lsp::Position,
+ ) -> Option<impl Future<Output = Result<Value>>> {
+ let capabilities = self.capabilities.get().unwrap();
+
+ match capabilities.rename_provider {
+ Some(lsp::OneOf::Right(lsp::RenameOptions {
+ prepare_provider: Some(true),
+ ..
+ })) => (),
+ _ => return None,
+ }
+
+ let params = lsp::TextDocumentPositionParams {
+ text_document,
+ position,
+ };
+
+ Some(self.call::<lsp::request::PrepareRenameRequest>(params))
+ }
+
// empty string to get all symbols
pub fn workspace_symbols(&self, query: String) -> Option<impl Future<Output = Result<Value>>> {
let capabilities = self.capabilities.get().unwrap();
diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs
index d59eebdb..08519366 100644
--- a/helix-term/src/commands/lsp.rs
+++ b/helix-term/src/commands/lsp.rs
@@ -1232,49 +1232,115 @@ pub fn hover(cx: &mut Context) {
}
pub fn rename_symbol(cx: &mut Context) {
- let (view, doc) = current_ref!(cx.editor);
- let text = doc.text().slice(..);
- let primary_selection = doc.selection(view.id).primary();
- let prefill = if primary_selection.len() > 1 {
- primary_selection
- } else {
- use helix_core::textobject::{textobject_word, TextObject};
- textobject_word(text, primary_selection, TextObject::Inside, 1, false)
+ fn get_prefill_from_word_boundary(editor: &Editor) -> String {
+ let (view, doc) = current_ref!(editor);
+ let text = doc.text().slice(..);
+ let primary_selection = doc.selection(view.id).primary();
+ if primary_selection.len() > 1 {
+ primary_selection
+ } else {
+ use helix_core::textobject::{textobject_word, TextObject};
+ textobject_word(text, primary_selection, TextObject::Inside, 1, false)
+ }
+ .fragment(text)
+ .into()
}
- .fragment(text)
- .into();
- ui::prompt_with_input(
- cx,
- "rename-to:".into(),
- prefill,
- None,
- ui::completers::none,
- move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
- if event != PromptEvent::Validate {
- return;
+
+ fn get_prefill_from_lsp_response(
+ editor: &Editor,
+ offset_encoding: OffsetEncoding,
+ response: Option<lsp::PrepareRenameResponse>,
+ ) -> Result<String, &'static str> {
+ match response {
+ Some(lsp::PrepareRenameResponse::Range(range)) => {
+ let text = doc!(editor).text();
+
+ Ok(lsp_range_to_range(text, range, offset_encoding)
+ .ok_or("lsp sent invalid selection range for rename")?
+ .fragment(text.slice(..))
+ .into())
+ }
+ Some(lsp::PrepareRenameResponse::RangeWithPlaceholder { placeholder, .. }) => {
+ Ok(placeholder)
+ }
+ Some(lsp::PrepareRenameResponse::DefaultBehavior { .. }) => {
+ Ok(get_prefill_from_word_boundary(editor))
}
+ None => Err("lsp did not respond to prepare rename request"),
+ }
+ }
- let (view, doc) = current!(cx.editor);
- let language_server = language_server!(cx.editor, doc);
- let offset_encoding = language_server.offset_encoding();
+ fn create_rename_prompt(editor: &Editor, prefill: String) -> Box<ui::Prompt> {
+ let prompt = ui::Prompt::new(
+ "rename-to:".into(),
+ None,
+ ui::completers::none,
+ move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
+ if event != PromptEvent::Validate {
+ return;
+ }
- let pos = doc.position(view.id, offset_encoding);
+ let (view, doc) = current!(cx.editor);
+ let language_server = language_server!(cx.editor, doc);
+ let offset_encoding = language_server.offset_encoding();
+
+ let pos = doc.position(view.id, offset_encoding);
+
+ let future =
+ match language_server.rename_symbol(doc.identifier(), pos, input.to_string()) {
+ Some(future) => future,
+ None => {
+ cx.editor
+ .set_error("Language server does not support symbol renaming");
+ return;
+ }
+ };
+ match block_on(future) {
+ Ok(edits) => apply_workspace_edit(cx.editor, offset_encoding, &edits),
+ Err(err) => cx.editor.set_error(err.to_string()),
+ }
+ },
+ )
+ .with_line(prefill, editor);
- let future =
- match language_server.rename_symbol(doc.identifier(), pos, input.to_string()) {
- Some(future) => future,
- None => {
- cx.editor
- .set_error("Language server does not support symbol renaming");
+ Box::new(prompt)
+ }
+
+ let (view, doc) = current!(cx.editor);
+ let language_server = language_server!(cx.editor, doc);
+ let offset_encoding = language_server.offset_encoding();
+
+ let pos = doc.position(view.id, offset_encoding);
+
+ match language_server.prepare_rename(doc.identifier(), pos) {
+ // Language server supports textDocument/prepareRename, use it.
+ Some(future) => cx.callback(
+ future,
+ move |editor, compositor, response: Option<lsp::PrepareRenameResponse>| {
+ let prefill = match get_prefill_from_lsp_response(editor, offset_encoding, response)
+ {
+ Ok(p) => p,
+ Err(e) => {
+ editor.set_error(e);
return;
}
};
- match block_on(future) {
- Ok(edits) => apply_workspace_edit(cx.editor, offset_encoding, &edits),
- Err(err) => cx.editor.set_error(err.to_string()),
- }
- },
- );
+
+ let prompt = create_rename_prompt(editor, prefill);
+
+ compositor.push(prompt);
+ },
+ ),
+ // Language server does not support textDocument/prepareRename, fall back
+ // to word boundary selection.
+ None => {
+ let prefill = get_prefill_from_word_boundary(cx.editor);
+
+ let prompt = create_rename_prompt(cx.editor, prefill);
+
+ cx.push_layer(prompt);
+ }
+ };
}
pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {