mirror of
https://github.com/atuinsh/atuin.git
synced 2025-01-12 17:28:16 +01:00
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>
This commit is contained in:
parent
4d41a741f0
commit
a56085f059
@ -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"]
|
||||
|
@ -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("_")
|
||||
|
@ -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?;
|
||||
|
@ -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 |
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user