Skip to content

Commit

Permalink
feat(search): make cursor style configurable (atuinsh#1595)
Browse files Browse the repository at this point in the history
* feat(search): make cursor style configurable

The vim mode of the interactive Atuin search changes the cursor style
on a mode change, but the current implementation has the following
issues.

* The terminal's cursor style set by the Atuin search remains after
  Atuin exits.  This causes an inconsistency with the shell's setting
  for the cursor style.

* Also, the cursor style for each keymap mode is currently hardcoded
  in the source code, which is not necessarily consistent with the
  user's cursor-style setting in the shell.

* Since the current implementation does not set the cursor style for
  the initial keymap mode but only sets the cursor style when the
  keymap mode is changed, it also causes inconsistency in the cursor
  style and the actual keymap when the shell's keymap and Atuin's
  initial keymap mode are different.

This patch solves those issues by introducing an opt-in configuration
variable `keymap_cursor`.  By default, the vim mode does not change
the cursor style because there is no way to automatically determine
the cursor style consistent with the shell settings.  We enable the
feature only when the user specifies the preferred cursor style in
each mode in their config.  Also, the cursor style is set on the
startup of the Atuin search (based on the initial keymap mode) and is
reset on the termination of the Atuin search (based on the shell's
keymap mode that started the Atuin search).

* chore(settings): remove dependency on crossterm
  • Loading branch information
akinomyoga authored Jan 22, 2024
1 parent d13b7c3 commit e484a68
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 3 deletions.
5 changes: 5 additions & 0 deletions atuin-client/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ enter_accept = true
## the specified one.
# keymap_mode = "auto"

## Cursor style in each keymap mode. If specified, the cursor style is changed
## in entering the cursor shape. Available values are "default" and
## "{blink,steady}-{block,underilne,bar}".
# keymap_cursor = { emacs = "blink-block", vim_insert = "blink-block", vim_normal = "steady-block" }

# network_connect_timeout = 5
# network_timeout = 5

Expand Down
48 changes: 48 additions & 0 deletions atuin-client/src/settings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::{
collections::HashMap,
convert::TryFrom,
io::prelude::*,
path::{Path, PathBuf},
Expand Down Expand Up @@ -169,6 +170,49 @@ impl KeymapMode {
}
}

// We want to translate the config to crossterm::cursor::SetCursorStyle, but
// the original type does not implement trait serde::Deserialize unfortunately.
// It seems impossible to implement Deserialize for external types when it is
// used in HashMap (https://stackoverflow.com/questions/67142663). We instead
// define an adapter type.
#[derive(Clone, Debug, Deserialize, Copy, PartialEq, Eq, ValueEnum)]
pub enum CursorStyle {
#[serde(rename = "default")]
DefaultUserShape,

#[serde(rename = "blink-block")]
BlinkingBlock,

#[serde(rename = "steady-block")]
SteadyBlock,

#[serde(rename = "blink-underline")]
BlinkingUnderScore,

#[serde(rename = "steady-underline")]
SteadyUnderScore,

#[serde(rename = "blink-bar")]
BlinkingBar,

#[serde(rename = "steady-bar")]
SteadyBar,
}

impl CursorStyle {
pub fn as_str(&self) -> &'static str {
match self {
CursorStyle::DefaultUserShape => "DEFAULT",
CursorStyle::BlinkingBlock => "BLINKBLOCK",
CursorStyle::SteadyBlock => "STEADYBLOCK",
CursorStyle::BlinkingUnderScore => "BLINKUNDERLINE",
CursorStyle::SteadyUnderScore => "STEADYUNDERLINE",
CursorStyle::BlinkingBar => "BLINKBAR",
CursorStyle::SteadyBar => "STEADYBAR",
}
}
}

#[derive(Clone, Debug, Deserialize)]
pub struct Stats {
#[serde(default = "Stats::common_prefix_default")]
Expand Down Expand Up @@ -228,6 +272,8 @@ pub struct Settings {
pub show_help: bool,
pub exit_mode: ExitMode,
pub keymap_mode: KeymapMode,
pub keymap_mode_shell: KeymapMode,
pub keymap_cursor: HashMap<String, CursorStyle>,
pub word_jump_mode: WordJumpMode,
pub word_chars: String,
pub scroll_context_lines: usize,
Expand Down Expand Up @@ -466,6 +512,8 @@ impl Settings {
.set_default("enter_accept", false)?
.set_default("sync.records", false)?
.set_default("keymap_mode", "emacs")?
.set_default("keymap_mode_shell", "auto")?
.set_default("keymap_cursor", HashMap::<String, String>::new())?
.add_source(
Environment::with_prefix("atuin")
.prefix_separator("_")
Expand Down
1 change: 1 addition & 0 deletions atuin/src/command/client/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ impl Cmd {
KeymapMode::Auto => self.keymap_mode,
value => value,
};
settings.keymap_mode_shell = self.keymap_mode;

let encryption_key: [u8; 32] = encryption::load_key(settings)?.into();

Expand Down
58 changes: 55 additions & 3 deletions atuin/src/command/client/search/interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use unicode_width::UnicodeWidthStr;
use atuin_client::{
database::{current_context, Database},
history::{store::HistoryStore, History, HistoryStats},
settings::{ExitMode, FilterMode, KeymapMode, SearchMode, Settings},
settings::{CursorStyle, ExitMode, FilterMode, KeymapMode, SearchMode, Settings},
};

use super::{
Expand Down Expand Up @@ -64,6 +64,7 @@ pub struct State {
results_len: usize,
accept: bool,
keymap_mode: KeymapMode,
current_cursor: Option<CursorStyle>,
tab_index: usize,

search: SearchState,
Expand Down Expand Up @@ -127,6 +128,52 @@ impl State {
InputAction::Continue
}

fn cast_cursor_style(style: CursorStyle) -> SetCursorStyle {
match style {
CursorStyle::DefaultUserShape => SetCursorStyle::DefaultUserShape,
CursorStyle::BlinkingBlock => SetCursorStyle::BlinkingBlock,
CursorStyle::SteadyBlock => SetCursorStyle::SteadyBlock,
CursorStyle::BlinkingUnderScore => SetCursorStyle::BlinkingUnderScore,
CursorStyle::SteadyUnderScore => SetCursorStyle::SteadyUnderScore,
CursorStyle::BlinkingBar => SetCursorStyle::BlinkingBar,
CursorStyle::SteadyBar => SetCursorStyle::SteadyBar,
}
}

fn set_keymap_cursor(&mut self, settings: &Settings, keymap_name: &str) {
let cursor_style = if keymap_name == "__clear__" {
None
} else {
settings.keymap_cursor.get(keymap_name).copied()
}
.or_else(|| self.current_cursor.map(|_| CursorStyle::DefaultUserShape));

if cursor_style != self.current_cursor {
if let Some(style) = cursor_style {
self.current_cursor = cursor_style;
let _ = execute!(stdout(), Self::cast_cursor_style(style));
}
}
}

pub fn initialize_keymap_cursor(&mut self, settings: &Settings) {
match self.keymap_mode {
KeymapMode::Emacs => self.set_keymap_cursor(settings, "emacs"),
KeymapMode::VimNormal => self.set_keymap_cursor(settings, "vim_normal"),
KeymapMode::VimInsert => self.set_keymap_cursor(settings, "vim_insert"),
KeymapMode::Auto => {}
}
}

pub fn finalize_keymap_cursor(&mut self, settings: &Settings) {
match settings.keymap_mode_shell {
KeymapMode::Emacs => self.set_keymap_cursor(settings, "emacs"),
KeymapMode::VimNormal => self.set_keymap_cursor(settings, "vim_normal"),
KeymapMode::VimInsert => self.set_keymap_cursor(settings, "vim_insert"),
KeymapMode::Auto => self.set_keymap_cursor(settings, "__clear__"),
}
}

#[allow(clippy::too_many_lines)]
#[allow(clippy::cognitive_complexity)]
fn handle_key_input(&mut self, settings: &Settings, input: &KeyEvent) -> InputAction {
Expand Down Expand Up @@ -154,7 +201,7 @@ impl State {
match input.code {
KeyCode::Char('c' | 'g') if ctrl => return InputAction::ReturnOriginal,
KeyCode::Esc if self.keymap_mode == KeymapMode::VimInsert => {
let _ = execute!(stdout(), SetCursorStyle::SteadyBlock);
self.set_keymap_cursor(settings, "vim_normal");
self.keymap_mode = KeymapMode::VimNormal;
return InputAction::Continue;
}
Expand Down Expand Up @@ -352,7 +399,7 @@ impl State {
return InputAction::Redraw;
}
KeyCode::Char('i') if self.keymap_mode == KeymapMode::VimNormal => {
let _ = execute!(stdout(), SetCursorStyle::BlinkingBlock);
self.set_keymap_cursor(settings, "vim_insert");
self.keymap_mode = KeymapMode::VimInsert;
}
KeyCode::Char(c) if self.keymap_mode != KeymapMode::VimNormal => {
Expand Down Expand Up @@ -830,8 +877,11 @@ pub async fn history(
KeymapMode::Auto => KeymapMode::Emacs,
value => value,
},
current_cursor: None,
};

app.initialize_keymap_cursor(settings);

let mut results = app.query_results(&mut db).await?;

let mut stats: Option<HistoryStats> = None;
Expand Down Expand Up @@ -904,6 +954,8 @@ pub async fn history(
};
};

app.finalize_keymap_cursor(settings);

if settings.inline_height > 0 {
terminal.clear()?;
}
Expand Down

0 comments on commit e484a68

Please sign in to comment.