diff options
author | YummyOreo <bobgim20@gmail.com> | 2024-01-13 09:15:49 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-13 17:15:49 +0000 |
commit | a56085f059b2444812353e715e5a4b93ea75b018 (patch) | |
tree | 6384c32f9ab242bf3fa7bfece81d29faa4a735d2 | |
parent | 4d41a741f0a47904a75db2981f5295732f7549c5 (diff) |
feat(ui): vim mode (#1553)
* feat(config): add vim option to config
* feat(ui): simple vim mode
* fix(windows): windows sadly doesn't support the stuff
* feat(ui): blinking
* fix(merge)
* revert: reverts some debugging stuff
* feat(ui): changes the defaut to insert, don't know what should be the default
* feat(ui): implements some vim parity
* doc: adds this to the docs
* docs(keybindings): adds vim mode keybindsings to the list of keybindings
* refactor: rustfmt and remove the docs for pr in own repo
* refactor: use execute!
* Update atuin/src/command/client/search/interactive.rs
---------
Co-authored-by: Ellie Huxtable <ellie@elliehuxtable.com>
-rw-r--r-- | atuin-client/config.toml | 6 | ||||
-rw-r--r-- | atuin-client/src/settings.rs | 2 | ||||
-rw-r--r-- | atuin/src/command/client/search/interactive.rs | 74 | ||||
-rw-r--r-- | docs/docs/key-binding.md | 3 |
4 files changed, 81 insertions, 4 deletions
diff --git a/atuin-client/config.toml b/atuin-client/config.toml index 511662cc8..29581d1fa 100644 --- a/atuin-client/config.toml +++ b/atuin-client/config.toml @@ -125,6 +125,10 @@ # This applies for new installs. Old installs will keep the old behaviour unless configured otherwise. enter_accept = true + +## Defaults to false. If enabled you may use 'j' and 'k' to navigate the history list and 'i' to enter Insert mode. +# vim = false + #[stats] # Set commands where we should consider the subcommand for statistics. Eg, kubectl get vs just kubectl #common_subcommands = [ @@ -136,6 +140,6 @@ enter_accept = true # "pnpm", # "kubectl", #] -# +# # Set commands that should be totally stripped and ignored from stats #common_prefix = ["sudo"] diff --git a/atuin-client/src/settings.rs b/atuin-client/src/settings.rs index 0798a890a..ea01961c7 100644 --- a/atuin-client/src/settings.rs +++ b/atuin-client/src/settings.rs @@ -201,6 +201,7 @@ pub struct Settings { pub max_preview_height: u16, pub show_help: bool, pub exit_mode: ExitMode, + pub vim: bool, pub word_jump_mode: WordJumpMode, pub word_chars: String, pub scroll_context_lines: usize, @@ -436,6 +437,7 @@ impl Settings { // New users will get the new default, that is more similar to what they are used to. .set_default("enter_accept", false)? .set_default("sync.records", false)? + .set_default("vim", false)? .add_source( Environment::with_prefix("atuin") .prefix_separator("_") diff --git a/atuin/src/command/client/search/interactive.rs b/atuin/src/command/client/search/interactive.rs index 3e7e89e13..9f0e03008 100644 --- a/atuin/src/command/client/search/interactive.rs +++ b/atuin/src/command/client/search/interactive.rs @@ -5,6 +5,7 @@ use std::{ use atuin_common::utils; use crossterm::{ + cursor::SetCursorStyle, event::{ self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, KeyboardEnhancementFlags, MouseEvent, PopKeyboardEnhancementFlags, @@ -53,6 +54,12 @@ pub enum InputAction { Redraw, } +#[derive(PartialEq)] +enum VimMode { + Normal, + Insert, +} + #[allow(clippy::struct_field_names)] pub struct State { history_count: i64, @@ -62,6 +69,7 @@ pub struct State { search_mode: SearchMode, results_len: usize, accept: bool, + vim_mode: VimMode, tab_index: usize, search: SearchState, @@ -141,6 +149,10 @@ impl State { // core input handling, common for all tabs match input.code { KeyCode::Char('c' | 'g') if ctrl => return InputAction::ReturnOriginal, + KeyCode::Esc if settings.vim && self.vim_mode == VimMode::Insert => { + let _ = execute!(stdout(), SetCursorStyle::SteadyBlock); + self.vim_mode = VimMode::Normal; + } KeyCode::Esc => { return match settings.exit_mode { ExitMode::ReturnOriginal => InputAction::ReturnOriginal, @@ -294,6 +306,22 @@ impl State { ExitMode::ReturnQuery => InputAction::ReturnQuery, } } + KeyCode::Char('j') + if settings.vim + && self.vim_mode == VimMode::Normal + && self.results_state.selected() == 0 => + { + return match settings.exit_mode { + ExitMode::ReturnOriginal => InputAction::ReturnOriginal, + ExitMode::ReturnQuery => InputAction::ReturnQuery, + } + } + KeyCode::Char('k') if settings.vim && self.vim_mode == VimMode::Normal => { + self.scroll_up(1); + } + KeyCode::Char('j') if settings.vim && self.vim_mode == VimMode::Normal => { + self.scroll_down(1); + } KeyCode::Down if !settings.invert => { self.scroll_down(1); } @@ -321,7 +349,13 @@ impl State { KeyCode::Char('l') if ctrl => { return InputAction::Redraw; } - KeyCode::Char(c) => self.search.input.insert(c), + KeyCode::Char('i') if settings.vim && self.vim_mode == VimMode::Normal => { + let _ = execute!(stdout(), SetCursorStyle::BlinkingBlock); + self.vim_mode = VimMode::Insert; + } + KeyCode::Char(c) if !settings.vim || self.vim_mode == VimMode::Insert => { + self.search.input.insert(c); + } KeyCode::PageDown if !settings.invert => { let scroll_len = self.results_state.max_entries() - settings.scroll_context_lines; self.scroll_down(scroll_len); @@ -669,6 +703,28 @@ struct Stdout { } impl Stdout { + #[cfg(target_os = "windows")] + pub fn new(inline_mode: bool) -> std::io::Result<Self> { + terminal::enable_raw_mode()?; + let mut stdout = stdout(); + + if !inline_mode { + execute!(stdout, terminal::EnterAlternateScreen)?; + } + + execute!( + stdout, + event::EnableMouseCapture, + event::EnableBracketedPaste, + )?; + + Ok(Self { + stdout, + inline_mode, + }) + } + + #[cfg(not(target_os = "windows"))] pub fn new(inline_mode: bool) -> std::io::Result<Self> { terminal::enable_raw_mode()?; let mut stdout = stdout(); @@ -695,6 +751,21 @@ impl Stdout { } impl Drop for Stdout { + #[cfg(target_os = "windows")] + fn drop(&mut self) { + if !self.inline_mode { + execute!(self.stdout, terminal::LeaveAlternateScreen).unwrap(); + } + execute!( + self.stdout, + event::DisableMouseCapture, + event::DisableBracketedPaste, + ) + .unwrap(); + terminal::disable_raw_mode().unwrap(); + } + + #[cfg(not(target_os = "windows"))] fn drop(&mut self) { if !self.inline_mode { execute!(self.stdout, terminal::LeaveAlternateScreen).unwrap(); @@ -783,6 +854,7 @@ pub async fn history( engine: engines::engine(search_mode), results_len: 0, accept: false, + vim_mode: VimMode::Normal, }; let mut results = app.query_results(&mut db).await?; diff --git a/docs/docs/key-binding.md b/docs/docs/key-binding.md index c0bd28857..74dc5506b 100644 --- a/docs/docs/key-binding.md +++ b/docs/docs/key-binding.md @@ -18,7 +18,7 @@ filter_mode_shell_up_key_binding = "directory" # or global, host, directory, etc ## Disable up arrow -Our default up-arrow binding can be a bit contentious. Some people love it, some people hate it. Many people who found it a bit jarring at first have since come to love it, so give it a try! +Our default up-arrow binding can be a bit contentious. Some people love it, some people hate it. Many people who found it a bit jarring at first have since come to love it, so give it a try! It becomes much more powerful if you consider binding a different filter mode to the up arrow. For example, on "up" Atuin can default to searching all history for the current directory only, while ctrl-r searches history globally. See the [config](https://atuin.sh/docs/config/#filter_mode_shell_up_key_binding) for more. @@ -159,4 +159,3 @@ $env.config = ( | page up | Scroll search results one page up | | ⬇ (with no entry selected) | Return original or return query depending on settings | | ⬇ | Select the next item on the list | - |