summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCossonLeo2021-11-08 15:17:54 +0000
committerGitHub2021-11-08 15:17:54 +0000
commit29e684941389f949491efe4d9807a0edd1facee3 (patch)
tree118f252ae10aff36a91361c16aef4b33c6aac2b2
parentbf4c70e02739601b7657c69a3876f9d1090a42c0 (diff)
Add LSP rename_symbol (space-r) (#1011)
improve apply_workspace_edit
-rw-r--r--helix-lsp/src/client.rs27
-rw-r--r--helix-term/src/commands.rs188
-rw-r--r--helix-term/src/keymap.rs1
-rw-r--r--helix-view/src/editor.rs6
4 files changed, 187 insertions, 35 deletions
diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs
index b810feef..271fd9d5 100644
--- a/helix-lsp/src/client.rs
+++ b/helix-lsp/src/client.rs
@@ -257,6 +257,12 @@ impl Client {
content_format: Some(vec![lsp::MarkupKind::Markdown]),
..Default::default()
}),
+ rename: Some(lsp::RenameClientCapabilities {
+ dynamic_registration: Some(false),
+ prepare_support: Some(false),
+ prepare_support_default_behavior: None,
+ honors_change_annotations: Some(false),
+ }),
code_action: Some(lsp::CodeActionClientCapabilities {
code_action_literal_support: Some(lsp::CodeActionLiteralSupport {
code_action_kind: lsp::CodeActionKindLiteralSupport {
@@ -773,4 +779,25 @@ impl Client {
self.call::<lsp::request::CodeActionRequest>(params)
}
+
+ pub async fn rename_symbol(
+ &self,
+ text_document: lsp::TextDocumentIdentifier,
+ position: lsp::Position,
+ new_name: String,
+ ) -> anyhow::Result<lsp::WorkspaceEdit> {
+ let params = lsp::RenameParams {
+ text_document_position: lsp::TextDocumentPositionParams {
+ text_document,
+ position,
+ },
+ new_name,
+ work_done_progress_params: lsp::WorkDoneProgressParams {
+ work_done_token: None,
+ },
+ };
+
+ let response = self.request::<lsp::request::Rename>(params).await?;
+ Ok(response.unwrap_or_default())
+ }
}
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 5f091775..245fbe4e 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -23,7 +23,7 @@ use helix_view::{
use anyhow::{anyhow, bail, Context as _};
use helix_lsp::{
- lsp,
+ block_on, lsp,
util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
OffsetEncoding,
};
@@ -339,6 +339,7 @@ impl Command {
shell_append_output, "Append output of shell command after each selection",
shell_keep_pipe, "Filter selections with shell predicate",
suspend, "Suspend",
+ rename_symbol, "Rename symbol",
);
}
@@ -2749,14 +2750,104 @@ pub fn code_action(cx: &mut Context) {
)
}
+pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
+ use lsp::ResourceOp;
+ use std::fs;
+ match op {
+ ResourceOp::Create(op) => {
+ let path = op.uri.to_file_path().unwrap();
+ let ignore_if_exists = if let Some(options) = &op.options {
+ !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
+ } else {
+ false
+ };
+ if ignore_if_exists && path.exists() {
+ Ok(())
+ } else {
+ fs::write(&path, [])
+ }
+ }
+ ResourceOp::Delete(op) => {
+ let path = op.uri.to_file_path().unwrap();
+ if path.is_dir() {
+ let recursive = if let Some(options) = &op.options {
+ options.recursive.unwrap_or(false)
+ } else {
+ false
+ };
+ if recursive {
+ fs::remove_dir_all(&path)
+ } else {
+ fs::remove_dir(&path)
+ }
+ } else if path.is_file() {
+ fs::remove_file(&path)
+ } else {
+ Ok(())
+ }
+ }
+ ResourceOp::Rename(op) => {
+ let from = op.old_uri.to_file_path().unwrap();
+ let to = op.new_uri.to_file_path().unwrap();
+ let ignore_if_exists = if let Some(options) = &op.options {
+ !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
+ } else {
+ false
+ };
+ if ignore_if_exists && to.exists() {
+ Ok(())
+ } else {
+ fs::rename(&from, &to)
+ }
+ }
+ }
+}
+
fn apply_workspace_edit(
editor: &mut Editor,
offset_encoding: OffsetEncoding,
workspace_edit: &lsp::WorkspaceEdit,
) {
+ let mut apply_edits = |uri: &helix_lsp::Url, text_edits: Vec<lsp::TextEdit>| {
+ let path = uri
+ .to_file_path()
+ .expect("unable to convert URI to filepath");
+
+ let current_view_id = view!(editor).id;
+ let doc_id = editor.open(path, Action::Load).unwrap();
+ let doc = editor
+ .document_mut(doc_id)
+ .expect("Document for document_changes not found");
+
+ // Need to determine a view for apply/append_changes_to_history
+ let selections = doc.selections();
+ let view_id = if selections.contains_key(&current_view_id) {
+ // use current if possible
+ current_view_id
+ } else {
+ // Hack: we take the first available view_id
+ selections
+ .keys()
+ .next()
+ .copied()
+ .expect("No view_id available")
+ };
+
+ let transaction = helix_lsp::util::generate_transaction_from_edits(
+ doc.text(),
+ text_edits,
+ offset_encoding,
+ );
+ doc.apply(&transaction, view_id);
+ doc.append_changes_to_history(view_id);
+ };
+
if let Some(ref changes) = workspace_edit.changes {
log::debug!("workspace changes: {:?}", changes);
- editor.set_error(String::from("Handling workspace_edit.changes is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
+ for (uri, text_edits) in changes {
+ let text_edits = text_edits.to_vec();
+ apply_edits(uri, text_edits);
+ }
return;
// 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
// TODO: find some example that uses workspace changes, and test it
@@ -2774,30 +2865,6 @@ fn apply_workspace_edit(
match document_changes {
lsp::DocumentChanges::Edits(document_edits) => {
for document_edit in document_edits {
- let path = document_edit
- .text_document
- .uri
- .to_file_path()
- .expect("unable to convert URI to filepath");
- let current_view_id = view!(editor).id;
- let doc = editor
- .document_by_path_mut(path)
- .expect("Document for document_changes not found");
-
- // Need to determine a view for apply/append_changes_to_history
- let selections = doc.selections();
- let view_id = if selections.contains_key(&current_view_id) {
- // use current if possible
- current_view_id
- } else {
- // Hack: we take the first available view_id
- selections
- .keys()
- .next()
- .copied()
- .expect("No view_id available")
- };
-
let edits = document_edit
.edits
.iter()
@@ -2809,19 +2876,33 @@ fn apply_workspace_edit(
})
.cloned()
.collect();
-
- let transaction = helix_lsp::util::generate_transaction_from_edits(
- doc.text(),
- edits,
- offset_encoding,
- );
- doc.apply(&transaction, view_id);
- doc.append_changes_to_history(view_id);
+ apply_edits(&document_edit.text_document.uri, edits);
}
}
lsp::DocumentChanges::Operations(operations) => {
log::debug!("document changes - operations: {:?}", operations);
- editor.set_error(String::from("Handling document operations is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
+ for operateion in operations {
+ match operateion {
+ lsp::DocumentChangeOperation::Op(op) => {
+ apply_document_resource_op(op).unwrap();
+ }
+
+ lsp::DocumentChangeOperation::Edit(document_edit) => {
+ let edits = document_edit
+ .edits
+ .iter()
+ .map(|edit| match edit {
+ lsp::OneOf::Left(text_edit) => text_edit,
+ lsp::OneOf::Right(annotated_text_edit) => {
+ &annotated_text_edit.text_edit
+ }
+ })
+ .cloned()
+ .collect();
+ apply_edits(&document_edit.text_document.uri, edits);
+ }
+ }
+ }
}
}
}
@@ -4982,3 +5063,40 @@ fn add_newline_impl(cx: &mut Context, open: Open) {
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view.id);
}
+
+fn rename_symbol(cx: &mut Context) {
+ let prompt = Prompt::new(
+ "Rename to: ".into(),
+ None,
+ |_input: &str| Vec::new(),
+ move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
+ if event != PromptEvent::Validate {
+ return;
+ }
+
+ log::debug!("renaming to: {:?}", input);
+
+ let (view, doc) = current!(cx.editor);
+ let language_server = match doc.language_server() {
+ Some(language_server) => language_server,
+ None => return,
+ };
+
+ let offset_encoding = language_server.offset_encoding();
+
+ let pos = pos_to_lsp_pos(
+ doc.text(),
+ doc.selection(view.id)
+ .primary()
+ .cursor(doc.text().slice(..)),
+ offset_encoding,
+ );
+
+ let task = language_server.rename_symbol(doc.identifier(), pos, input.to_string());
+ let edits = block_on(task).unwrap_or_default();
+ log::debug!("Edits from LSP: {:?}", edits);
+ apply_workspace_edit(&mut cx.editor, offset_encoding, &edits);
+ },
+ );
+ cx.push_layer(Box::new(prompt));
+}
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index ce50f0ab..d497401f 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -584,6 +584,7 @@ impl Default for Keymaps {
"R" => replace_selections_with_clipboard,
"/" => global_search,
"k" => hover,
+ "r" => rename_symbol,
},
"z" => { "View"
"z" | "c" => align_view_center,
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 17cd3d7b..631dcf0c 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -287,6 +287,12 @@ impl Editor {
return;
}
Action::Load => {
+ let view_id = view!(self).id;
+ if let Some(doc) = self.document_mut(id) {
+ if doc.selections().is_empty() {
+ doc.selections.insert(view_id, Selection::point(0));
+ }
+ }
return;
}
Action::HorizontalSplit => {