aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src/commands.rs
diff options
context:
space:
mode:
authorNathan Vegdahl2021-07-26 18:19:10 +0000
committerNathan Vegdahl2021-07-26 18:19:10 +0000
commitf62ec6e51e99dce9e93600801c3637c196c592b6 (patch)
tree8821ee23ade21c0da4fbb5a70143717ac42a2bc1 /helix-term/src/commands.rs
parent5ee6ba5b38ebeb86006bb2e42734a2285eb354df (diff)
parent88d6f652390922b389667f469b6d308db569bdaf (diff)
Merge branch 'master' into great_line_ending_and_cursor_range_cleanup
Diffstat (limited to 'helix-term/src/commands.rs')
-rw-r--r--helix-term/src/commands.rs610
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,
-}