on master help sign in
Add LSP rename_symbol (space-r) (#1011)

improve apply_workspace_edit

commit29e684941389f949491efe4d9807a0edd1facee3 authorCossonLeo <20379044+cossonleo@users.noreply.github.com> dateMon 8 Nov 2021, 23:17 (5 years ago) parentbf4c70e
4 files changed+187−35
helix-lsp/src/client.rs+27 helix-term/src/commands.rs+153−35 helix-term/src/keymap.rs+1 helix-view/src/editor.rs+6
helix-lsp/src/client.rs+27
@@ -257,6 +257,12 @@ impl Client {
257257 content_format: Some(vec![lsp::MarkupKind::Markdown]),
258258 ..Default::default()
259259 }),
260+ rename: Some(lsp::RenameClientCapabilities {
261+ dynamic_registration: Some(false),
262+ prepare_support: Some(false),
263+ prepare_support_default_behavior: None,
264+ honors_change_annotations: Some(false),
265+ }),
260266 code_action: Some(lsp::CodeActionClientCapabilities {
261267 code_action_literal_support: Some(lsp::CodeActionLiteralSupport {
262268 code_action_kind: lsp::CodeActionKindLiteralSupport {
@@ -773,4 +779,25 @@ impl Client {
773779
774780 self.call::<lsp::request::CodeActionRequest>(params)
775781 }
782+
783+ pub async fn rename_symbol(
784+ &self,
785+ text_document: lsp::TextDocumentIdentifier,
786+ position: lsp::Position,
787+ new_name: String,
788+ ) -> anyhow::Result<lsp::WorkspaceEdit> {
789+ let params = lsp::RenameParams {
790+ text_document_position: lsp::TextDocumentPositionParams {
791+ text_document,
792+ position,
793+ },
794+ new_name,
795+ work_done_progress_params: lsp::WorkDoneProgressParams {
796+ work_done_token: None,
797+ },
798+ };
799+
800+ let response = self.request::<lsp::request::Rename>(params).await?;
801+ Ok(response.unwrap_or_default())
802+ }
776803}
helix-term/src/commands.rs+153−35
@@ -23,7 +23,7 @@ use helix_view::{
2323
2424use anyhow::{anyhow, bail, Context as _};
2525use helix_lsp::{
26− lsp,
26+ block_on, lsp,
2727 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
2828 OffsetEncoding,
2929};
@@ -339,6 +339,7 @@ impl Command {
339339 shell_append_output, "Append output of shell command after each selection",
340340 shell_keep_pipe, "Filter selections with shell predicate",
341341 suspend, "Suspend",
342+ rename_symbol, "Rename symbol",
342343 );
343344}
344345
@@ -2749,14 +2750,104 @@ pub fn code_action(cx: &mut Context) {
27492750 )
27502751}
27512752
2753+pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
2754+ use lsp::ResourceOp;
2755+ use std::fs;
2756+ match op {
2757+ ResourceOp::Create(op) => {
2758+ let path = op.uri.to_file_path().unwrap();
2759+ let ignore_if_exists = if let Some(options) = &op.options {
2760+ !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
2761+ } else {
2762+ false
2763+ };
2764+ if ignore_if_exists && path.exists() {
2765+ Ok(())
2766+ } else {
2767+ fs::write(&path, [])
2768+ }
2769+ }
2770+ ResourceOp::Delete(op) => {
2771+ let path = op.uri.to_file_path().unwrap();
2772+ if path.is_dir() {
2773+ let recursive = if let Some(options) = &op.options {
2774+ options.recursive.unwrap_or(false)
2775+ } else {
2776+ false
2777+ };
2778+ if recursive {
2779+ fs::remove_dir_all(&path)
2780+ } else {
2781+ fs::remove_dir(&path)
2782+ }
2783+ } else if path.is_file() {
2784+ fs::remove_file(&path)
2785+ } else {
2786+ Ok(())
2787+ }
2788+ }
2789+ ResourceOp::Rename(op) => {
2790+ let from = op.old_uri.to_file_path().unwrap();
2791+ let to = op.new_uri.to_file_path().unwrap();
2792+ let ignore_if_exists = if let Some(options) = &op.options {
2793+ !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
2794+ } else {
2795+ false
2796+ };
2797+ if ignore_if_exists && to.exists() {
2798+ Ok(())
2799+ } else {
2800+ fs::rename(&from, &to)
2801+ }
2802+ }
2803+ }
2804+}
2805+
27522806fn apply_workspace_edit(
27532807 editor: &mut Editor,
27542808 offset_encoding: OffsetEncoding,
27552809 workspace_edit: &lsp::WorkspaceEdit,
27562810) {
2811+ let mut apply_edits = |uri: &helix_lsp::Url, text_edits: Vec<lsp::TextEdit>| {
2812+ let path = uri
2813+ .to_file_path()
2814+ .expect("unable to convert URI to filepath");
2815+
2816+ let current_view_id = view!(editor).id;
2817+ let doc_id = editor.open(path, Action::Load).unwrap();
2818+ let doc = editor
2819+ .document_mut(doc_id)
2820+ .expect("Document for document_changes not found");
2821+
2822+ // Need to determine a view for apply/append_changes_to_history
2823+ let selections = doc.selections();
2824+ let view_id = if selections.contains_key(&current_view_id) {
2825+ // use current if possible
2826+ current_view_id
2827+ } else {
2828+ // Hack: we take the first available view_id
2829+ selections
2830+ .keys()
2831+ .next()
2832+ .copied()
2833+ .expect("No view_id available")
2834+ };
2835+
2836+ let transaction = helix_lsp::util::generate_transaction_from_edits(
2837+ doc.text(),
2838+ text_edits,
2839+ offset_encoding,
2840+ );
2841+ doc.apply(&transaction, view_id);
2842+ doc.append_changes_to_history(view_id);
2843+ };
2844+
27572845 if let Some(ref changes) = workspace_edit.changes {
27582846 log::debug!("workspace changes: {:?}", changes);
2759− editor.set_error(String::from("Handling workspace_edit.changes is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
2847+ for (uri, text_edits) in changes {
2848+ let text_edits = text_edits.to_vec();
2849+ apply_edits(uri, text_edits);
2850+ }
27602851 return;
27612852 // Not sure if it works properly, it'll be safer to just panic here to avoid breaking some parts of code on which code actions will be used
27622853 // TODO: find some example that uses workspace changes, and test it
@@ -2774,30 +2865,6 @@ fn apply_workspace_edit(
27742865 match document_changes {
27752866 lsp::DocumentChanges::Edits(document_edits) => {
27762867 for document_edit in document_edits {
2777− let path = document_edit
2778− .text_document
2779− .uri
2780− .to_file_path()
2781− .expect("unable to convert URI to filepath");
2782− let current_view_id = view!(editor).id;
2783− let doc = editor
2784− .document_by_path_mut(path)
2785− .expect("Document for document_changes not found");
2786−
2787− // Need to determine a view for apply/append_changes_to_history
2788− let selections = doc.selections();
2789− let view_id = if selections.contains_key(&current_view_id) {
2790− // use current if possible
2791− current_view_id
2792− } else {
2793− // Hack: we take the first available view_id
2794− selections
2795− .keys()
2796− .next()
2797− .copied()
2798− .expect("No view_id available")
2799− };
2800−
28012868 let edits = document_edit
28022869 .edits
28032870 .iter()
@@ -2809,19 +2876,33 @@ fn apply_workspace_edit(
28092876 })
28102877 .cloned()
28112878 .collect();
2812−
2813− let transaction = helix_lsp::util::generate_transaction_from_edits(
2814− doc.text(),
2815− edits,
2816− offset_encoding,
2817− );
2818− doc.apply(&transaction, view_id);
2819− doc.append_changes_to_history(view_id);
2879+ apply_edits(&document_edit.text_document.uri, edits);
28202880 }
28212881 }
28222882 lsp::DocumentChanges::Operations(operations) => {
28232883 log::debug!("document changes - operations: {:?}", operations);
2824− editor.set_error(String::from("Handling document operations is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
2884+ for operateion in operations {
2885+ match operateion {
2886+ lsp::DocumentChangeOperation::Op(op) => {
2887+ apply_document_resource_op(op).unwrap();
2888+ }
2889+
2890+ lsp::DocumentChangeOperation::Edit(document_edit) => {
2891+ let edits = document_edit
2892+ .edits
2893+ .iter()
2894+ .map(|edit| match edit {
2895+ lsp::OneOf::Left(text_edit) => text_edit,
2896+ lsp::OneOf::Right(annotated_text_edit) => {
2897+ &annotated_text_edit.text_edit
2898+ }
2899+ })
2900+ .cloned()
2901+ .collect();
2902+ apply_edits(&document_edit.text_document.uri, edits);
2903+ }
2904+ }
2905+ }
28252906 }
28262907 }
28272908 }
@@ -4982,3 +5063,40 @@ fn add_newline_impl(cx: &mut Context, open: Open) {
49825063 doc.apply(&transaction, view.id);
49835064 doc.append_changes_to_history(view.id);
49845065}
5066+
5067+fn rename_symbol(cx: &mut Context) {
5068+ let prompt = Prompt::new(
5069+ "Rename to: ".into(),
5070+ None,
5071+ |_input: &str| Vec::new(),
5072+ move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
5073+ if event != PromptEvent::Validate {
5074+ return;
5075+ }
5076+
5077+ log::debug!("renaming to: {:?}", input);
5078+
5079+ let (view, doc) = current!(cx.editor);
5080+ let language_server = match doc.language_server() {
5081+ Some(language_server) => language_server,
5082+ None => return,
5083+ };
5084+
5085+ let offset_encoding = language_server.offset_encoding();
5086+
5087+ let pos = pos_to_lsp_pos(
5088+ doc.text(),
5089+ doc.selection(view.id)
5090+ .primary()
5091+ .cursor(doc.text().slice(..)),
5092+ offset_encoding,
5093+ );
5094+
5095+ let task = language_server.rename_symbol(doc.identifier(), pos, input.to_string());
5096+ let edits = block_on(task).unwrap_or_default();
5097+ log::debug!("Edits from LSP: {:?}", edits);
5098+ apply_workspace_edit(&mut cx.editor, offset_encoding, &edits);
5099+ },
5100+ );
5101+ cx.push_layer(Box::new(prompt));
5102+}
helix-term/src/keymap.rs+1
@@ -584,6 +584,7 @@ impl Default for Keymaps {
584584 "R" => replace_selections_with_clipboard,
585585 "/" => global_search,
586586 "k" => hover,
587+ "r" => rename_symbol,
587588 },
588589 "z" => { "View"
589590 "z" | "c" => align_view_center,
helix-view/src/editor.rs+6
@@ -287,6 +287,12 @@ impl Editor {
287287 return;
288288 }
289289 Action::Load => {
290+ let view_id = view!(self).id;
291+ if let Some(doc) = self.document_mut(id) {
292+ if doc.selections().is_empty() {
293+ doc.selections.insert(view_id, Selection::point(0));
294+ }
295+ }
290296 return;
291297 }
292298 Action::HorizontalSplit => {