Add LSP rename_symbol (space-r) (#1011)

improve apply_workspace_edit
pull/993/head^2
CossonLeo 3 years ago committed by GitHub
parent bf4c70e027
commit 29e6849413
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -257,6 +257,12 @@ impl Client {
content_format: Some(vec![lsp::MarkupKind::Markdown]), content_format: Some(vec![lsp::MarkupKind::Markdown]),
..Default::default() ..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: Some(lsp::CodeActionClientCapabilities {
code_action_literal_support: Some(lsp::CodeActionLiteralSupport { code_action_literal_support: Some(lsp::CodeActionLiteralSupport {
code_action_kind: lsp::CodeActionKindLiteralSupport { code_action_kind: lsp::CodeActionKindLiteralSupport {
@ -773,4 +779,25 @@ impl Client {
self.call::<lsp::request::CodeActionRequest>(params) 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())
}
} }

@ -23,7 +23,7 @@ use helix_view::{
use anyhow::{anyhow, bail, Context as _}; use anyhow::{anyhow, bail, Context as _};
use helix_lsp::{ use helix_lsp::{
lsp, block_on, lsp,
util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range}, util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
OffsetEncoding, OffsetEncoding,
}; };
@ -339,6 +339,7 @@ impl Command {
shell_append_output, "Append output of shell command after each selection", shell_append_output, "Append output of shell command after each selection",
shell_keep_pipe, "Filter selections with shell predicate", shell_keep_pipe, "Filter selections with shell predicate",
suspend, "Suspend", 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( fn apply_workspace_edit(
editor: &mut Editor, editor: &mut Editor,
offset_encoding: OffsetEncoding, offset_encoding: OffsetEncoding,
workspace_edit: &lsp::WorkspaceEdit, 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 { if let Some(ref changes) = workspace_edit.changes {
log::debug!("workspace changes: {:?}", 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; 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 // 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 // TODO: find some example that uses workspace changes, and test it
@ -2774,30 +2865,6 @@ fn apply_workspace_edit(
match document_changes { match document_changes {
lsp::DocumentChanges::Edits(document_edits) => { lsp::DocumentChanges::Edits(document_edits) => {
for document_edit in 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 let edits = document_edit
.edits .edits
.iter() .iter()
@ -2809,19 +2876,33 @@ fn apply_workspace_edit(
}) })
.cloned() .cloned()
.collect(); .collect();
apply_edits(&document_edit.text_document.uri, edits);
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);
} }
} }
lsp::DocumentChanges::Operations(operations) => { lsp::DocumentChanges::Operations(operations) => {
log::debug!("document changes - 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.apply(&transaction, view.id);
doc.append_changes_to_history(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));
}

@ -584,6 +584,7 @@ impl Default for Keymaps {
"R" => replace_selections_with_clipboard, "R" => replace_selections_with_clipboard,
"/" => global_search, "/" => global_search,
"k" => hover, "k" => hover,
"r" => rename_symbol,
}, },
"z" => { "View" "z" => { "View"
"z" | "c" => align_view_center, "z" | "c" => align_view_center,

@ -287,6 +287,12 @@ impl Editor {
return; return;
} }
Action::Load => { 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; return;
} }
Action::HorizontalSplit => { Action::HorizontalSplit => {

Loading…
Cancel
Save