aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src
diff options
context:
space:
mode:
authorGrzegorz Baranski2021-07-24 01:26:43 +0000
committerGitHub2021-07-24 01:26:43 +0000
commit48e344a2a813b875ca813be726387692b7ba0762 (patch)
treef9582a940e326e663200fb46d4fded6738950276 /helix-term/src
parentbda4f5c1cdd2f84a06647f7dce45a8f6d06401ae (diff)
feat: code actions - document edits (#478)
* wip: Code actions * fix(term): use current macro instead Context::context * feat(lsp): set code_action capabilities * feat(term): set SPC-a to code_action * feat(term): wip on applying code actions * deps: `cargo update` * feat(term): applying code actions edits * fix(term): cleanup of apply_edit * fix(term): applying edits as a whole thing instead one by one * refactor(term): move apply_edits below * fix(term): improve unimplemented messages for further investigation * fix(term): change code action command comment Co-authored-by: Ivan Tham <pickfire@riseup.net> * fix(term): add matching `}` * fix(term): cleanup, todo!() on workspace edit * fix(term): remove unrelated workspace_symbol_picker * fix(term): apply cargo-clippy suggestions * fix(term): replace todo!'s with editor.set_error Co-authored-by: Blaž Hrastnik <blaz@mxxn.io> Co-authored-by: Ivan Tham <pickfire@riseup.net>
Diffstat (limited to 'helix-term/src')
-rw-r--r--helix-term/src/commands.rs121
1 files changed, 119 insertions, 2 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 9b72a8e9..24c53897 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -9,8 +9,8 @@ use helix_core::{
object, pos_at_coords,
regex::{self, Regex},
register::Register,
- search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
- RopeSlice, Selection, SmallVec, Tendril, Transaction,
+ search, selection, surround, textobject, Change, LineEnding, Position, Range, Rope,
+ RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, Transaction,
};
use helix_view::{
@@ -215,6 +215,7 @@ impl Command {
append_mode,
command_mode,
file_picker,
+ code_action,
buffer_picker,
symbol_picker,
last_picker,
@@ -2092,6 +2093,120 @@ fn symbol_picker(cx: &mut Context) {
)
}
+pub fn code_action(cx: &mut Context) {
+ let (view, doc) = current!(cx.editor);
+
+ let language_server = match doc.language_server() {
+ Some(language_server) => language_server,
+ None => return,
+ };
+
+ let range = range_to_lsp_range(
+ doc.text(),
+ doc.selection(view.id).primary(),
+ language_server.offset_encoding(),
+ );
+
+ let future = language_server.code_actions(doc.identifier(), range);
+ let offset_encoding = language_server.offset_encoding();
+
+ cx.callback(
+ future,
+ move |_editor: &mut Editor,
+ compositor: &mut Compositor,
+ response: Option<lsp::CodeActionResponse>| {
+ if let Some(actions) = response {
+ let picker = Picker::new(
+ actions,
+ |action| match action {
+ lsp::CodeActionOrCommand::CodeAction(action) => {
+ action.title.as_str().into()
+ }
+ lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(),
+ },
+ move |editor, code_action, _action| match code_action {
+ lsp::CodeActionOrCommand::Command(command) => {
+ log::debug!("code action command: {:?}", command);
+ editor.set_error(String::from("Handling code action command is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
+ }
+ lsp::CodeActionOrCommand::CodeAction(code_action) => {
+ log::debug!("code action: {:?}", code_action);
+ if let Some(ref workspace_edit) = code_action.edit {
+ apply_workspace_edit(editor, offset_encoding, workspace_edit)
+ }
+ }
+ },
+ );
+ compositor.push(Box::new(picker))
+ }
+ },
+ )
+}
+
+fn apply_workspace_edit(
+ editor: &mut Editor,
+ offset_encoding: OffsetEncoding,
+ workspace_edit: &lsp::WorkspaceEdit,
+) {
+ let edits_to_transaction = |doc: &Rope, edits: &Vec<&lsp::TextEdit>| {
+ let lsp_pos_to_pos = |lsp_pos| lsp_pos_to_pos(&doc, lsp_pos, offset_encoding).unwrap();
+ let changes = edits.iter().map(|edit| -> Change {
+ log::debug!("text edit: {:?}", edit);
+ // This clone probably could be optimized if Picker::new would give T instead of &T
+ let text_replacement = Tendril::from(edit.new_text.clone());
+ (
+ lsp_pos_to_pos(edit.range.start),
+ lsp_pos_to_pos(edit.range.end),
+ Some(text_replacement),
+ )
+ });
+ Transaction::change(doc, changes)
+ };
+
+ if let Some(ref changes) = workspace_edit.changes {
+ log::debug!("workspace changes: {:?}", changes);
+ editor.set_error(String::from("Handling workspace changesis not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
+ 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
+ // for (url, edits) in changes.iter() {
+ // let file_path = url.origin().ascii_serialization();
+ // let file_path = std::path::PathBuf::from(file_path);
+ // let file = std::fs::File::open(file_path).unwrap();
+ // let mut text = Rope::from_reader(file).unwrap();
+ // let transaction = edits_to_changes(&text, edits);
+ // transaction.apply(&mut text);
+ // }
+ }
+
+ if let Some(ref document_changes) = workspace_edit.document_changes {
+ match document_changes {
+ lsp::DocumentChanges::Edits(document_edits) => {
+ for document_edit in document_edits {
+ let (view, doc) = current!(editor);
+ assert_eq!(doc.url().unwrap(), document_edit.text_document.uri);
+ 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
+ }
+ })
+ .collect();
+ let transaction = edits_to_transaction(doc.text(), &edits);
+ doc.apply(&transaction, view.id);
+ }
+ }
+ 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"));
+ }
+ }
+ }
+}
+
fn last_picker(cx: &mut Context) {
// TODO: last picker does not seemed to work well with buffer_picker
cx.callback = Some(Box::new(|compositor: &mut Compositor| {
@@ -3781,6 +3896,8 @@ mode_info! {
"P" => paste_clipboard_before,
/// replace selections with clipboard
"R" => replace_selections_with_clipboard,
+ /// perform code action
+ "a" => code_action,
/// keep primary selection
"space" => keep_primary_selection,
}