feat(search): introduce keymap-dependent vim-mode (#1570)

* feat(search): introduce keymap-dependent vim-mode

* fix(zsh): provide widgets with specific keymaps

* fix(settings): unify "vim" and "keymap_mode"
This commit is contained in:
Koichi Murase 2024-01-16 22:35:10 +09:00 committed by GitHub
parent a2578c4521
commit 6bff8c8e1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 112 additions and 46 deletions

View File

@ -126,8 +126,13 @@
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
## Defaults to "emacs". This specifies the keymap on the startup of `atuin
## search`. If this is set to "auto", the startup keymap mode in the Atuin
## search is automatically selected based on the shell's keymap where the
## keybinding is defined. If this is set to "emacs", "vim-insert", or
## "vim-normal", the startup keymap mode in the Atuin search is forced to be
## the specified one.
# keymap_mode = "auto"
#[stats]
# Set commands where we should consider the subcommand for statistics. Eg, kubectl get vs just kubectl

View File

@ -143,6 +143,32 @@ pub enum WordJumpMode {
Subl,
}
#[derive(Clone, Debug, Deserialize, Copy, PartialEq, Eq, ValueEnum)]
pub enum KeymapMode {
#[serde(rename = "emacs")]
Emacs,
#[serde(rename = "vim-normal")]
VimNormal,
#[serde(rename = "vim-insert")]
VimInsert,
#[serde(rename = "auto")]
Auto,
}
impl KeymapMode {
pub fn as_str(&self) -> &'static str {
match self {
KeymapMode::Emacs => "EMACS",
KeymapMode::VimNormal => "VIMNORMAL",
KeymapMode::VimInsert => "VIMINSERT",
KeymapMode::Auto => "AUTO",
}
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct Stats {
#[serde(default = "Stats::common_prefix_default")]
@ -201,7 +227,7 @@ pub struct Settings {
pub max_preview_height: u16,
pub show_help: bool,
pub exit_mode: ExitMode,
pub vim: bool,
pub keymap_mode: KeymapMode,
pub word_jump_mode: WordJumpMode,
pub word_chars: String,
pub scroll_context_lines: usize,
@ -437,7 +463,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)?
.set_default("keymap_mode", "emacs")?
.add_source(
Environment::with_prefix("atuin")
.prefix_separator("_")

View File

@ -6,7 +6,7 @@ use atuin_client::{
database::Database,
database::{current_context, OptFilters},
history::History,
settings::{FilterMode, SearchMode, Settings},
settings::{FilterMode, KeymapMode, SearchMode, Settings},
};
use super::history::ListMode;
@ -71,6 +71,10 @@ pub struct Cmd {
#[arg(long = "shell-up-key-binding", hide = true)]
shell_up_key_binding: bool,
/// Notify the keymap at the shell's side
#[arg(long = "keymap-mode", default_value = "auto")]
keymap_mode: KeymapMode,
/// Use human-readable formatting for time
#[arg(long)]
human: bool,
@ -142,6 +146,13 @@ impl Cmd {
settings.shell_up_key_binding = self.shell_up_key_binding;
// `keymap_mode` specified in config.toml overrides the `--keymap-mode`
// option specified in the keybindings.
settings.keymap_mode = match settings.keymap_mode {
KeymapMode::Auto => self.keymap_mode,
value => value,
};
if self.interactive {
let item = interactive::history(&self.query, settings, db).await?;
eprintln!("{item}");

View File

@ -21,7 +21,7 @@ use unicode_width::UnicodeWidthStr;
use atuin_client::{
database::{current_context, Database},
history::{History, HistoryStats},
settings::{ExitMode, FilterMode, SearchMode, Settings},
settings::{ExitMode, FilterMode, KeymapMode, SearchMode, Settings},
};
use super::{
@ -54,12 +54,6 @@ pub enum InputAction {
Redraw,
}
#[derive(PartialEq)]
enum VimMode {
Normal,
Insert,
}
#[allow(clippy::struct_field_names)]
pub struct State {
history_count: i64,
@ -69,7 +63,7 @@ pub struct State {
search_mode: SearchMode,
results_len: usize,
accept: bool,
vim_mode: VimMode,
keymap_mode: KeymapMode,
tab_index: usize,
search: SearchState,
@ -159,9 +153,9 @@ 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 => {
KeyCode::Esc if self.keymap_mode == KeymapMode::VimInsert => {
let _ = execute!(stdout(), SetCursorStyle::SteadyBlock);
self.vim_mode = VimMode::Normal;
self.keymap_mode = KeymapMode::VimNormal;
return InputAction::Continue;
}
KeyCode::Esc => {
@ -311,8 +305,7 @@ impl State {
KeyCode::Char('j')
if !ctrl
&& !settings.invert
&& settings.vim
&& self.vim_mode == VimMode::Normal
&& self.keymap_mode == KeymapMode::VimNormal
&& self.results_state.selected() == 0 =>
{
do_exit!();
@ -320,16 +313,15 @@ impl State {
KeyCode::Char('k')
if !ctrl
&& settings.invert
&& settings.vim
&& self.vim_mode == VimMode::Normal
&& self.keymap_mode == KeymapMode::VimNormal
&& self.results_state.selected() == 0 =>
{
do_exit!();
}
KeyCode::Char('k') if !ctrl && settings.vim && self.vim_mode == VimMode::Normal => {
KeyCode::Char('k') if !ctrl && self.keymap_mode == KeymapMode::VimNormal => {
self.scroll_up(1);
}
KeyCode::Char('j') if !ctrl && settings.vim && self.vim_mode == VimMode::Normal => {
KeyCode::Char('j') if !ctrl && self.keymap_mode == KeymapMode::VimNormal => {
self.scroll_down(1);
}
KeyCode::Down if !settings.invert => {
@ -359,11 +351,11 @@ impl State {
KeyCode::Char('l') if ctrl => {
return InputAction::Redraw;
}
KeyCode::Char('i') if settings.vim && self.vim_mode == VimMode::Normal => {
KeyCode::Char('i') if self.keymap_mode == KeymapMode::VimNormal => {
let _ = execute!(stdout(), SetCursorStyle::BlinkingBlock);
self.vim_mode = VimMode::Insert;
self.keymap_mode = KeymapMode::VimInsert;
}
KeyCode::Char(c) if !settings.vim || self.vim_mode == VimMode::Insert => {
KeyCode::Char(c) if self.keymap_mode != KeymapMode::VimNormal => {
self.search.input.insert(c);
}
KeyCode::PageDown if !settings.invert => {
@ -832,7 +824,10 @@ pub async fn history(
engine: engines::engine(search_mode),
results_len: 0,
accept: false,
vim_mode: VimMode::Normal,
keymap_mode: match settings.keymap_mode {
KeymapMode::Auto => KeymapMode::Emacs,
value => value,
},
};
let mut results = app.query_results(&mut db).await?;

View File

@ -33,16 +33,16 @@ impl Cmd {
if std::env::var("ATUIN_NOBIND").is_err() {
const BIND_CTRL_R: &str = r"bindkey -M emacs '^r' _atuin_search_widget
bindkey -M vicmd '^r' _atuin_search_widget
bindkey -M viins '^r' _atuin_search_widget";
bindkey -M vicmd '^r' _atuin_search_vicmd_widget
bindkey -M viins '^r' _atuin_search_viins_widget";
const BIND_UP_ARROW: &str = r"bindkey -M emacs '^[[A' _atuin_up_search_widget
bindkey -M vicmd '^[[A' _atuin_up_search_widget
bindkey -M viins '^[[A' _atuin_up_search_widget
bindkey -M vicmd '^[[A' _atuin_up_search_vicmd_widget
bindkey -M viins '^[[A' _atuin_up_search_viins_widget
bindkey -M emacs '^[OA' _atuin_up_search_widget
bindkey -M vicmd '^[OA' _atuin_up_search_widget
bindkey -M viins '^[OA' _atuin_up_search_widget
bindkey -M vicmd 'k' _atuin_up_search_widget";
bindkey -M vicmd '^[OA' _atuin_up_search_vicmd_widget
bindkey -M viins '^[OA' _atuin_up_search_viins_widget
bindkey -M vicmd 'k' _atuin_up_search_vicmd_widget";
if !self.disable_ctrl_r {
println!("{BIND_CTRL_R}");

View File

@ -227,33 +227,33 @@ if [[ $__atuin_bind_ctrl_r == true ]]; then
# Note: We do not overwrite [C-r] in the vi-command keymap for Bash because
# we do not want to overwrite "redo", which is already bound to [C-r] in
# the vi_nmap keymap in ble.sh.
bind -m emacs -x '"\C-r": __atuin_history'
bind -m vi-insert -x '"\C-r": __atuin_history'
bind -m emacs -x '"\C-r": __atuin_history --keymap-mode=emacs'
bind -m vi-insert -x '"\C-r": __atuin_history --keymap-mode=vim-insert'
fi
# shellcheck disable=SC2154
if [[ $__atuin_bind_up_arrow == true ]]; then
if ((BASH_VERSINFO[0] > 4 || BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 3)); then
bind -m emacs -x '"\e[A": __atuin_history --shell-up-key-binding'
bind -m emacs -x '"\eOA": __atuin_history --shell-up-key-binding'
bind -m vi-insert -x '"\e[A": __atuin_history --shell-up-key-binding'
bind -m vi-insert -x '"\eOA": __atuin_history --shell-up-key-binding'
bind -m vi-command -x '"\e[A": __atuin_history --shell-up-key-binding'
bind -m vi-command -x '"\eOA": __atuin_history --shell-up-key-binding'
bind -m vi-command -x '"k": __atuin_history --shell-up-key-binding'
bind -m emacs -x '"\e[A": __atuin_history --shell-up-key-binding --keymap-mode=emacs'
bind -m emacs -x '"\eOA": __atuin_history --shell-up-key-binding --keymap-mode=emacs'
bind -m vi-insert -x '"\e[A": __atuin_history --shell-up-key-binding --keymap-mode=vim-insert'
bind -m vi-insert -x '"\eOA": __atuin_history --shell-up-key-binding --keymap-mode=vim-insert'
bind -m vi-command -x '"\e[A": __atuin_history --shell-up-key-binding --keymap-mode=vim-normal'
bind -m vi-command -x '"\eOA": __atuin_history --shell-up-key-binding --keymap-mode=vim-normal'
bind -m vi-command -x '"k": __atuin_history --shell-up-key-binding --keymap-mode=vim-normal'
else
# In bash < 4.3, "bind -x" cannot bind a shell command to a keyseq
# having more than two bytes. To work around this, we first translate
# the keyseqs to the two-byte sequence \C-x\C-p (which is not used by
# default) using string macros and run the shell command through the
# keybinding to \C-x\C-p.
bind -m emacs -x '"\C-x\C-p": __atuin_history --shell-up-key-binding'
bind -m emacs -x '"\C-x\C-p": __atuin_history --shell-up-key-binding --keymap-mode=emacs'
bind -m emacs '"\e[A": "\C-x\C-p"'
bind -m emacs '"\eOA": "\C-x\C-p"'
bind -m vi-insert -x '"\C-x\C-p": __atuin_history --shell-up-key-binding'
bind -m vi-insert -x '"\C-x\C-p": __atuin_history --shell-up-key-binding --keymap-mode=vim-insert'
bind -m vi-insert -x '"\e[A": "\C-x\C-p"'
bind -m vi-insert -x '"\eOA": "\C-x\C-p"'
bind -m vi-command -x '"\C-x\C-p": __atuin_history --shell-up-key-binding'
bind -m vi-command -x '"\C-x\C-p": __atuin_history --shell-up-key-binding --keymap-mode=vim-normal'
bind -m vi-command -x '"\e[A": "\C-x\C-p"'
bind -m vi-command -x '"\eOA": "\C-x\C-p"'
bind -m vi-command -x '"k": "\C-x\C-p"'

View File

@ -19,10 +19,23 @@ function _atuin_postexec --on-event fish_postexec
end
function _atuin_search
set -l keymap_mode
switch $fish_key_bindings
case fish_vi_key_bindings
switch $fish_bind_mode
case default
set keymap_mode vim-normal
case insert
set keymap_mode vim-insert
end
case '*'
set keymap_mode emacs
end
# In fish 3.4 and above we can use `"$(some command)"` to keep multiple lines separate;
# but to support fish 3.3 we need to use `(some command | string collect)`.
# https://fishshell.com/docs/current/relnotes.html#id24 (fish 3.4 "Notable improvements and fixes")
set -l ATUIN_H (ATUIN_SHELL_FISH=t ATUIN_LOG=error atuin search $argv -i -- (commandline -b) 3>&1 1>&2 2>&3 | string collect)
set -l ATUIN_H (ATUIN_SHELL_FISH=t ATUIN_LOG=error atuin search --keymap-mode=$keymap_mode $argv -i -- (commandline -b) 3>&1 1>&2 2>&3 | string collect)
if test -n "$ATUIN_H"
if string match --quiet '__atuin_accept__:*' "$ATUIN_H"

View File

@ -71,18 +71,34 @@ _atuin_search() {
fi
fi
}
_atuin_search_vicmd() {
_atuin_search --keymap-mode=vim-normal
}
_atuin_search_viins() {
_atuin_search --keymap-mode=vim-insert
}
_atuin_up_search() {
# Only trigger if the buffer is a single line
if [[ ! $BUFFER == *$'\n'* ]]; then
_atuin_search --shell-up-key-binding
_atuin_search --shell-up-key-binding "$@"
else
zle up-line
fi
}
_atuin_up_search_vicmd() {
_atuin_up_search --keymap-mode=vim-normal
}
_atuin_up_search_viins() {
_atuin_up_search --keymap-mode=vim-insert
}
add-zsh-hook preexec _atuin_preexec
add-zsh-hook precmd _atuin_precmd
zle -N _atuin_search_widget _atuin_search
zle -N _atuin_search_vicmd_widget _atuin_search_vicmd
zle -N _atuin_search_viins_widget _atuin_search_viins
zle -N _atuin_up_search_widget _atuin_up_search
zle -N _atuin_up_search_vicmd_widget _atuin_up_search_vicmd
zle -N _atuin_up_search_viins_widget _atuin_up_search_viins