diff options
Diffstat (limited to 'helix-term/src/commands.rs')
-rw-r--r-- | helix-term/src/commands.rs | 610 |
1 files changed, 258 insertions, 352 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 89042acb..a37d2d26 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -13,7 +13,6 @@ use helix_core::{ use helix_view::{ document::{IndentStyle, Mode}, editor::Action, - info::Info, input::KeyEvent, keyboard::KeyCode, view::{View, PADDING}, @@ -36,7 +35,6 @@ use crate::{ use crate::job::{self, Job, Jobs}; use futures_util::{FutureExt, TryFutureExt}; -use std::collections::HashMap; use std::num::NonZeroUsize; use std::{fmt, future::Future}; @@ -45,7 +43,7 @@ use std::{ path::{Path, PathBuf}, }; -use once_cell::sync::{Lazy, OnceCell}; +use once_cell::sync::Lazy; use serde::de::{self, Deserialize, Deserializer}; pub struct Context<'a> { @@ -75,18 +73,6 @@ impl<'a> Context<'a> { } #[inline] - pub fn on_next_key_mode(&mut self, map: HashMap<KeyEvent, fn(&mut Context)>) { - let count = self.count; - self.on_next_key(move |cx, event| { - cx.count = count; - cx.editor.autoinfo = None; - if let Some(func) = map.get(&event) { - func(cx); - } - }); - } - - #[inline] pub fn callback<T, F>( &mut self, call: impl Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send, @@ -139,13 +125,21 @@ fn align_view(doc: &Document, view: &mut View, align: Align) { /// A command is composed of a static name, and a function that takes the current state plus a count, /// and does a side-effect on the state (usually by creating and applying a transaction). #[derive(Copy, Clone)] -pub struct Command(&'static str, fn(cx: &mut Context)); +pub struct Command { + name: &'static str, + fun: fn(cx: &mut Context), + doc: &'static str, +} macro_rules! commands { - ( $($name:ident),* ) => { + ( $($name:ident, $doc:literal),* ) => { $( #[allow(non_upper_case_globals)] - pub const $name: Self = Self(stringify!($name), $name); + pub const $name: Self = Self { + name: stringify!($name), + fun: $name, + doc: $doc + }; )* pub const COMMAND_LIST: &'static [Self] = &[ @@ -156,144 +150,159 @@ macro_rules! commands { impl Command { pub fn execute(&self, cx: &mut Context) { - (self.1)(cx); + (self.fun)(cx); } pub fn name(&self) -> &'static str { - self.0 + self.name + } + + pub fn doc(&self) -> &'static str { + self.doc } + #[rustfmt::skip] commands!( - move_char_left, - move_char_right, - move_line_up, - move_line_down, - move_next_word_start, - move_prev_word_start, - move_next_word_end, - move_next_long_word_start, - move_prev_long_word_start, - move_next_long_word_end, - extend_next_word_start, - extend_prev_word_start, - extend_next_word_end, - find_till_char, - find_next_char, - extend_till_char, - extend_next_char, - till_prev_char, - find_prev_char, - extend_till_prev_char, - extend_prev_char, - replace, - switch_case, - switch_to_uppercase, - switch_to_lowercase, - page_up, - page_down, - half_page_up, - half_page_down, - extend_char_left, - extend_char_right, - extend_line_up, - extend_line_down, - select_all, - select_regex, - split_selection, - split_selection_on_newline, - search, - search_next, - extend_search_next, - search_selection, - extend_line, - extend_to_line_bounds, - delete_selection, - change_selection, - collapse_selection, - flip_selections, - insert_mode, - append_mode, - command_mode, - file_picker, - buffer_picker, - symbol_picker, - last_picker, - prepend_to_line, - append_to_line, - open_below, - open_above, - normal_mode, - goto_mode, - select_mode, - exit_select_mode, - goto_definition, - goto_type_definition, - goto_implementation, - goto_file_start, - goto_file_end, - goto_reference, - goto_first_diag, - goto_last_diag, - goto_next_diag, - goto_prev_diag, - goto_line_start, - goto_line_end, - goto_line_end_newline, - goto_first_nonwhitespace, - signature_help, - insert_tab, - insert_newline, - delete_char_backward, - delete_char_forward, - delete_word_backward, - undo, - redo, - yank, - yank_joined_to_clipboard, - yank_main_selection_to_clipboard, - replace_with_yanked, - replace_selections_with_clipboard, - paste_after, - paste_before, - paste_clipboard_after, - paste_clipboard_before, - indent, - unindent, - format_selections, - join_selections, - keep_selections, - keep_primary_selection, - completion, - hover, - toggle_comments, - expand_selection, - match_brackets, - jump_forward, - jump_backward, - window_mode, - rotate_view, - hsplit, - vsplit, - wclose, - select_register, - space_mode, - view_mode, - left_bracket_mode, - right_bracket_mode, - match_mode + move_char_left, "Move left", + move_char_right, "Move right", + move_line_up, "Move up", + move_line_down, "Move down", + extend_char_left, "Extend left", + extend_char_right, "Extend right", + extend_line_up, "Extend up", + extend_line_down, "Extend down", + move_next_word_start, "Move to beginning of next word", + move_prev_word_start, "Move to beginning of previous word", + move_next_word_end, "Move to end of next word", + move_next_long_word_start, "Move to beginning of next long word", + move_prev_long_word_start, "Move to beginning of previous long word", + move_next_long_word_end, "Move to end of next long word", + extend_next_word_start, "Extend to beginning of next word", + extend_prev_word_start, "Extend to beginning of previous word", + extend_next_word_end, "Extend to end of next word", + find_till_char, "Move till next occurance of char", + find_next_char, "Move to next occurance of char", + extend_till_char, "Extend till next occurance of char", + extend_next_char, "Extend to next occurance of char", + till_prev_char, "Move till previous occurance of char", + find_prev_char, "Move to previous occurance of char", + extend_till_prev_char, "Extend till previous occurance of char", + extend_prev_char, "Extend to previous occurance of char", + replace, "Replace with new char", + switch_case, "Switch (toggle) case", + switch_to_uppercase, "Switch to uppercase", + switch_to_lowercase, "Switch to lowercase", + page_up, "Move page up", + page_down, "Move page down", + half_page_up, "Move half page up", + half_page_down, "Move half page down", + select_all, "Select whole document", + select_regex, "Select all regex matches inside selections", + split_selection, "Split selection into subselections on regex matches", + split_selection_on_newline, "Split selection on newlines", + search, "Search for regex pattern", + search_next, "Select next search match", + extend_search_next, "Add next search match to selection", + search_selection, "Use current selection as search pattern", + extend_line, "Select current line, if already selected, extend to next line", + extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)", + delete_selection, "Delete selection", + change_selection, "Change selection (delete and enter insert mode)", + collapse_selection, "Collapse selection onto a single cursor", + flip_selections, "Flip selection cursor and anchor", + insert_mode, "Insert before selection", + append_mode, "Insert after selection (append)", + command_mode, "Enter command mode", + file_picker, "Open file picker", + code_action, "Perform code action", + buffer_picker, "Open buffer picker", + symbol_picker, "Open symbol picker", + last_picker, "Open last picker", + prepend_to_line, "Insert at start of line", + append_to_line, "Insert at end of line", + open_below, "Open new line below selection", + open_above, "Open new line above selection", + normal_mode, "Enter normal mode", + select_mode, "Enter selection extend mode", + exit_select_mode, "Exit selection mode", + goto_definition, "Goto definition", + goto_type_definition, "Goto type definition", + goto_implementation, "Goto implementation", + goto_file_start, "Goto file start", + goto_file_end, "Goto file end", + goto_reference, "Goto references", + goto_window_top, "Goto window top", + goto_window_middle, "Goto window middle", + goto_window_bottom, "Goto window bottom", + goto_last_accessed_file, "Goto last accessed file", + goto_first_diag, "Goto first diagnostic", + goto_last_diag, "Goto last diagnostic", + goto_next_diag, "Goto next diagnostic", + goto_prev_diag, "Goto previous diagnostic", + goto_line_start, "Goto line start", + goto_line_end, "Goto line end", + // TODO: different description ? + goto_line_end_newline, "Goto line end", + goto_first_nonwhitespace, "Goto first non-blank in line", + signature_help, "Show signature help", + insert_tab, "Insert tab char", + insert_newline, "Insert newline char", + delete_char_backward, "Delete previous char", + delete_char_forward, "Delete next char", + delete_word_backward, "Delete previous word", + undo, "Undo change", + redo, "Redo change", + yank, "Yank selection", + yank_joined_to_clipboard, "Join and yank selections to clipboard", + yank_main_selection_to_clipboard, "Yank main selection to clipboard", + replace_with_yanked, "Replace with yanked text", + replace_selections_with_clipboard, "Replace selections by clipboard content", + paste_after, "Paste after selection", + paste_before, "Paste before selection", + paste_clipboard_after, "Paste clipboard after selections", + paste_clipboard_before, "Paste clipboard before selections", + indent, "Indent selection", + unindent, "Unindent selection", + format_selections, "Format selection", + join_selections, "Join lines inside selection", + keep_selections, "Keep selections matching regex", + keep_primary_selection, "Keep primary selection", + completion, "Invoke completion popup", + hover, "Show docs for item under cursor", + toggle_comments, "Comment/uncomment selections", + expand_selection, "Expand selection to parent syntax node", + jump_forward, "Jump forward on jumplist", + jump_backward, "Jump backward on jumplist", + rotate_view, "Goto next window", + hsplit, "Horizontal bottom split", + vsplit, "Vertical right split", + wclose, "Close window", + select_register, "Select register", + align_view_middle, "Align view middle", + align_view_top, "Align view top", + align_view_center, "Align view center", + align_view_bottom, "Align view bottom", + scroll_up, "Scroll view up", + scroll_down, "Scroll view down", + match_brackets, "Goto matching bracket", + surround_add, "Surround add", + surround_replace, "Surround replace", + surround_delete, "Surround delete", + select_textobject_around, "Select around object", + select_textobject_inner, "Select inside object" ); } impl fmt::Debug for Command { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Command(name, _) = self; + let Command { name, .. } = self; f.debug_tuple("Command").field(name).finish() } } impl fmt::Display for Command { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Command(name, _) = self; + let Command { name, .. } = self; f.write_str(name) } } @@ -305,7 +314,7 @@ impl std::str::FromStr for Command { Command::COMMAND_LIST .iter() .copied() - .find(|cmd| cmd.0 == s) + .find(|cmd| cmd.name == s) .ok_or_else(|| anyhow!("No command named '{}'", s)) } } @@ -1171,7 +1180,7 @@ fn delete_selection(cx: &mut Context) { let reg_name = cx.selected_register.name(); let (view, doc) = current!(cx.editor); let registers = &mut cx.editor.registers; - let reg = registers.get_or_insert(reg_name); + let reg = registers.get_mut(reg_name); delete_selection_impl(reg, doc, view.id); doc.append_changes_to_history(view.id); @@ -1184,7 +1193,7 @@ fn change_selection(cx: &mut Context) { let reg_name = cx.selected_register.name(); let (view, doc) = current!(cx.editor); let registers = &mut cx.editor.registers; - let reg = registers.get_or_insert(reg_name); + let reg = registers.get_mut(reg_name); delete_selection_impl(reg, doc, view.id); enter_insert_mode(doc); } @@ -1974,6 +1983,7 @@ mod cmd { fn command_mode(cx: &mut Context) { let mut prompt = Prompt::new( ":".to_owned(), + Some(':'), |input: &str| { // we use .this over split_whitespace() because we care about empty segments let parts = input.split(' ').collect::<Vec<&str>>(); @@ -2147,6 +2157,112 @@ 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, +) { + 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 + } + }) + .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); + } + } + 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| { @@ -2360,20 +2476,6 @@ fn exit_select_mode(cx: &mut Context) { doc_mut!(cx.editor).mode = Mode::Normal; } -fn goto_prehook(cx: &mut Context) -> bool { - if let Some(count) = cx.count { - push_jump(cx.editor); - - let (view, doc) = current!(cx.editor); - let line_idx = std::cmp::min(count.get() - 1, doc.text().len_lines().saturating_sub(1)); - let pos = doc.text().line_to_char(line_idx); - doc.set_selection(view.id, Selection::point(pos)); - true - } else { - false - } -} - fn goto_impl( editor: &mut Editor, compositor: &mut Compositor, @@ -3823,199 +3925,3 @@ fn surround_delete(cx: &mut Context) { } }) } - -/// Do nothing, just for modeinfo. -fn noop(_cx: &mut Context) -> bool { - false -} - -/// Generate modeinfo. -/// -/// If prehook returns true then it will stop the rest. -macro_rules! mode_info { - // TODO: reuse $mode for $stat - (@join $first:expr $(,$rest:expr)*) => { - concat!($first, $(", ", $rest),*) - }; - (@name #[doc = $name:literal] $(#[$rest:meta])*) => { - $name - }; - { - #[doc = $name:literal] $(#[$doc:meta])* $mode:ident, $stat:ident, - $(#[doc = $desc:literal] $($key:tt)|+ => $func:expr),+, - } => { - mode_info! { - #[doc = $name] - $(#[$doc])* - $mode, $stat, noop, - $( - #[doc = $desc] - $($key)|+ => $func - ),+, - } - }; - { - #[doc = $name:literal] $(#[$doc:meta])* $mode:ident, $stat:ident, $prehook:expr, - $(#[doc = $desc:literal] $($key:tt)|+ => $func:expr),+, - } => { - #[doc = $name] - $(#[$doc])* - #[doc = ""] - #[doc = "<table><tr><th>key</th><th>desc</th></tr><tbody>"] - $( - #[doc = "<tr><td>"] - // TODO switch to this once we use rust 1.54 - // right now it will produce multiple rows - // #[doc = mode_info!(@join $($key),+)] - $( - #[doc = $key] - )+ - // <- - #[doc = "</td><td>"] - #[doc = $desc] - #[doc = "</td></tr>"] - )+ - #[doc = "</tbody></table>"] - pub fn $mode(cx: &mut Context) { - if $prehook(cx) { - return; - } - static $stat: OnceCell<Info> = OnceCell::new(); - cx.editor.autoinfo = Some($stat.get_or_init(|| Info::key( - $name.trim(), - vec![$((&[$($key.parse().unwrap()),+], $desc)),+], - ))); - use helix_core::hashmap; - // TODO: try and convert this to match later - let map = hashmap! { - $($($key.parse::<KeyEvent>().unwrap() => $func as for<'r, 's> fn(&'r mut Context<'s>)),+),* - }; - cx.on_next_key_mode(map); - } - }; -} - -mode_info! { - /// space mode - space_mode, SPACE_MODE, - /// resume last picker - "'" => last_picker, - /// file picker - "f" => file_picker, - /// buffer picker - "b" => buffer_picker, - /// symbol picker - "s" => symbol_picker, - /// window mode - "w" => window_mode, - /// yank joined to clipboard - "y" => yank_joined_to_clipboard, - /// yank main selection to clipboard - "Y" => yank_main_selection_to_clipboard, - /// paste system clipboard after selections - "p" => paste_clipboard_after, - /// paste system clipboard before selections - "P" => paste_clipboard_before, - /// replace selections with clipboard - "R" => replace_selections_with_clipboard, - /// keep primary selection - "space" => keep_primary_selection, -} - -mode_info! { - /// goto - /// - /// When specified with a count, it will go to that line without entering the mode. - goto_mode, GOTO_MODE, goto_prehook, - /// file start - "g" => goto_file_start, - /// file end - "e" => goto_file_end, - /// line start - "h" => goto_line_start, - /// line end - "l" => goto_line_end, - /// line first non blank - "s" => goto_first_nonwhitespace, - /// definition - "d" => goto_definition, - /// type references - "y" => goto_type_definition, - /// references - "r" => goto_reference, - /// implementation - "i" => goto_implementation, - /// window top - "t" => goto_window_top, - /// window middle - "m" => goto_window_middle, - /// window bottom - "b" => goto_window_bottom, - /// last accessed file - "a" => goto_last_accessed_file, -} - -mode_info! { - /// window - window_mode, WINDOW_MODE, - /// rotate - "w" | "C-w" => rotate_view, - /// horizontal split - "h" => hsplit, - /// vertical split - "v" => vsplit, - /// close - "q" => wclose, -} - -mode_info! { - /// match - match_mode, MATCH_MODE, - /// matching character - "m" => match_brackets, - /// surround add - "s" => surround_add, - /// surround replace - "r" => surround_replace, - /// surround delete - "d" => surround_delete, - /// around object - "a" => select_textobject_around, - /// inside object - "i" => select_textobject_inner, -} - -mode_info! { - /// select to previous - left_bracket_mode, LEFT_BRACKET_MODE, - /// previous diagnostic - "d" => goto_prev_diag, - /// diagnostic (first) - "D" => goto_first_diag, -} - -mode_info! { - /// select to next - right_bracket_mode, RIGHT_BRACKET_MODE, - /// diagnostic - "d" => goto_next_diag, - /// diagnostic (last) - "D" => goto_last_diag, -} - -mode_info! { - /// view - view_mode, VIEW_MODE, - /// align view top - "t" => align_view_top, - /// align view center - "z" | "c" => align_view_center, - /// align view bottom - "b" => align_view_bottom, - /// align view middle - "m" => align_view_middle, - /// scroll up - "k" => scroll_up, - /// scroll down - "j" => scroll_down, -} |