on master help sign in
LSP: Support textDocument/prepareRename (#6103)
44ff8a1Kyle Smith
helix-lsp/src/client.rs+24−1
@@ -359,7 +359,7 @@ impl Client {
359 }),
359 }),
360 rename: Some(lsp::RenameClientCapabilities {
360 rename: Some(lsp::RenameClientCapabilities {
361 dynamic_registration: Some(false),
361 dynamic_registration: Some(false),
362− prepare_support: Some(false),
362+ prepare_support: Some(true),
363 prepare_support_default_behavior: None,
363 prepare_support_default_behavior: None,
364 honors_change_annotations: Some(false),
364 honors_change_annotations: Some(false),
365 }),
365 }),
@@ -1034,6 +1034,29 @@ impl Client {
1034 Some(self.call::<lsp::request::DocumentSymbolRequest>(params))
1034 Some(self.call::<lsp::request::DocumentSymbolRequest>(params))
1035 }
1035 }
1036
1036
1037+ pub fn prepare_rename(
1038+ &self,
1039+ text_document: lsp::TextDocumentIdentifier,
1040+ position: lsp::Position,
1041+ ) -> Option<impl Future<Output = Result<Value>>> {
1042+ let capabilities = self.capabilities.get().unwrap();
1043+
1044+ match capabilities.rename_provider {
1045+ Some(lsp::OneOf::Right(lsp::RenameOptions {
1046+ prepare_provider: Some(true),
1047+ ..
1048+ })) => (),
1049+ _ => return None,
1050+ }
1051+
1052+ let params = lsp::TextDocumentPositionParams {
1053+ text_document,
1054+ position,
1055+ };
1056+
1057+ Some(self.call::<lsp::request::PrepareRenameRequest>(params))
1058+ }
1059+
1037 // empty string to get all symbols
1060 // empty string to get all symbols
1038 pub fn workspace_symbols(&self, query: String) -> Option<impl Future<Output = Result<Value>>> {
1061 pub fn workspace_symbols(&self, query: String) -> Option<impl Future<Output = Result<Value>>> {
1039 let capabilities = self.capabilities.get().unwrap();
1062 let capabilities = self.capabilities.get().unwrap();
helix-term/src/commands/lsp.rs+101−35
@@ -1232,49 +1232,115 @@ pub fn hover(cx: &mut Context) {
1232}
1232}
1233
1233
1234pub fn rename_symbol(cx: &mut Context) {
1234pub fn rename_symbol(cx: &mut Context) {
1235+ fn get_prefill_from_word_boundary(editor: &Editor) -> String {
1235− let (view, doc) = current_ref!(cx.editor);
1236+ let (view, doc) = current_ref!(editor);
1236− let text = doc.text().slice(..);
1237+ let text = doc.text().slice(..);
1237− let primary_selection = doc.selection(view.id).primary();
1238+ let primary_selection = doc.selection(view.id).primary();
1238− let prefill = if primary_selection.len() > 1 {
1239+ if primary_selection.len() > 1 {
1239− primary_selection
1240+ primary_selection
1240− } else {
1241+ } else {
1241− use helix_core::textobject::{textobject_word, TextObject};
1242+ use helix_core::textobject::{textobject_word, TextObject};
1242− textobject_word(text, primary_selection, TextObject::Inside, 1, false)
1243+ textobject_word(text, primary_selection, TextObject::Inside, 1, false)
1244+ }
1245+ .fragment(text)
1246+ .into()
1243 }
1247 }
1248+
1249+ fn get_prefill_from_lsp_response(
1250+ editor: &Editor,
1251+ offset_encoding: OffsetEncoding,
1252+ response: Option<lsp::PrepareRenameResponse>,
1253+ ) -> Result<String, &'static str> {
1254+ match response {
1255+ Some(lsp::PrepareRenameResponse::Range(range)) => {
1256+ let text = doc!(editor).text();
1257+
1258+ Ok(lsp_range_to_range(text, range, offset_encoding)
1259+ .ok_or("lsp sent invalid selection range for rename")?
1244− .fragment(text)
1260+ .fragment(text.slice(..))
1245− .into();
1246− ui::prompt_with_input(
1247− cx,
1248− "rename-to:".into(),
1261+ .into())
1249− prefill,
1262+ }
1250− None,
1263+ Some(lsp::PrepareRenameResponse::RangeWithPlaceholder { placeholder, .. }) => {
1251− ui::completers::none,
1264+ Ok(placeholder)
1252− move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
1265+ }
1253− if event != PromptEvent::Validate {
1266+ Some(lsp::PrepareRenameResponse::DefaultBehavior { .. }) => {
1254− return;
1267+ Ok(get_prefill_from_word_boundary(editor))
1255 }
1268 }
1269+ None => Err("lsp did not respond to prepare rename request"),
1270+ }
1271+ }
1256
1272
1257− let (view, doc) = current!(cx.editor);
1273+ fn create_rename_prompt(editor: &Editor, prefill: String) -> Box<ui::Prompt> {
1258− let language_server = language_server!(cx.editor, doc);
1274+ let prompt = ui::Prompt::new(
1259− let offset_encoding = language_server.offset_encoding();
1275+ "rename-to:".into(),
1276+ None,
1277+ ui::completers::none,
1278+ move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
1279+ if event != PromptEvent::Validate {
1280+ return;
1281+ }
1260
1282
1283+ let (view, doc) = current!(cx.editor);
1284+ let language_server = language_server!(cx.editor, doc);
1285+ let offset_encoding = language_server.offset_encoding();
1286+
1261− let pos = doc.position(view.id, offset_encoding);
1287+ let pos = doc.position(view.id, offset_encoding);
1288+
1289+ let future =
1290+ match language_server.rename_symbol(doc.identifier(), pos, input.to_string()) {
1291+ Some(future) => future,
1292+ None => {
1293+ cx.editor
1294+ .set_error("Language server does not support symbol renaming");
1295+ return;
1296+ }
1297+ };
1298+ match block_on(future) {
1299+ Ok(edits) => apply_workspace_edit(cx.editor, offset_encoding, &edits),
1300+ Err(err) => cx.editor.set_error(err.to_string()),
1301+ }
1302+ },
1303+ )
1304+ .with_line(prefill, editor);
1262
1305
1263− let future =
1306+ Box::new(prompt)
1307+ }
1308+
1309+ let (view, doc) = current!(cx.editor);
1310+ let language_server = language_server!(cx.editor, doc);
1311+ let offset_encoding = language_server.offset_encoding();
1312+
1313+ let pos = doc.position(view.id, offset_encoding);
1314+
1264− match language_server.rename_symbol(doc.identifier(), pos, input.to_string()) {
1315+ match language_server.prepare_rename(doc.identifier(), pos) {
1316+ // Language server supports textDocument/prepareRename, use it.
1265− Some(future) => future,
1317+ Some(future) => cx.callback(
1266− None => {
1318+ future,
1319+ move |editor, compositor, response: Option<lsp::PrepareRenameResponse>| {
1320+ let prefill = match get_prefill_from_lsp_response(editor, offset_encoding, response)
1321+ {
1322+ Ok(p) => p,
1323+ Err(e) => {
1267− cx.editor
1324+ editor.set_error(e);
1268− .set_error("Language server does not support symbol renaming");
1269 return;
1325 return;
1270 }
1326 }
1271 };
1327 };
1272− match block_on(future) {
1328+
1273− Ok(edits) => apply_workspace_edit(cx.editor, offset_encoding, &edits),
1329+ let prompt = create_rename_prompt(editor, prefill);
1274− Err(err) => cx.editor.set_error(err.to_string()),
1330+
1275− }
1331+ compositor.push(prompt);
1332+ },
1333+ ),
1334+ // Language server does not support textDocument/prepareRename, fall back
1335+ // to word boundary selection.
1336+ None => {
1337+ let prefill = get_prefill_from_word_boundary(cx.editor);
1338+
1339+ let prompt = create_rename_prompt(cx.editor, prefill);
1340+
1341+ cx.push_layer(prompt);
1276− },
1342+ }
1277− );
1343+ };
1278}
1344}
1279
1345
1280pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
1346pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {