summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKoichi Murase <myoga.murase@gmail.com>2024-01-22 19:56:44 +0900
committerGitHub <noreply@github.com>2024-01-22 10:56:44 +0000
commite484a68b8b740362a73d4e949806ffe9511ce0fe (patch)
treecffef03df27baf77a0fa012f784d2ac118cf5894
parentd13b7c31c11643f95f2e5f402ed7337377dc9309 (diff)
feat(search): make cursor style configurable (#1595)
* 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
-rw-r--r--atuin-client/config.toml5
-rw-r--r--atuin-client/src/settings.rs48
-rw-r--r--atuin/src/command/client/search.rs1
-rw-r--r--atuin/src/command/client/search/interactive.rs58
4 files changed, 109 insertions, 3 deletions
diff --git a/atuin-client/config.toml b/atuin-client/config.toml
index 9e097a41..d18d9783 100644
--- a/atuin-client/config.toml
+++ b/atuin-client/config.toml
@@ -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
diff --git a/atuin-client/src/settings.rs b/atuin-client/src/settings.rs
index e57b61e3..c95c8ba5 100644
--- a/atuin-client/src/settings.rs
+++ b/atuin-client/src/settings.rs
@@ -1,4 +1,5 @@
use std::{
+ collections::HashMap,
convert::TryFrom,
io::prelude::*,
path::{Path, PathBuf},
@@ -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")]
@@ -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,
@@ -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("_")
diff --git a/atuin/src/command/client/search.rs b/atuin/src/command/client/search.rs
index a929abd9..6a70ed62 100644
--- a/atuin/src/command/client/search.rs
+++ b/atuin/src/command/client/search.rs
@@ -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();
diff --git a/atuin/src/command/client/search/interactive.rs b/atuin/src/command/client/search/interactive.rs
index 66cbb064..b5c63eae 100644
--- a/atuin/src/command/client/search/interactive.rs
+++ b/atuin/src/command/client/search/interactive.rs
@@ -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::{
@@ -64,6 +64,7 @@ pub struct State {
results_len: usize,
accept: bool,
keymap_mode: KeymapMode,
+ current_cursor: Option<CursorStyle>,
tab_index: usize,
search: SearchState,
@@ -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 {
@@ -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;
}
@@ -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 => {
@@ -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;
@@ -904,6 +954,8 @@ pub async fn history(
};
};
+ app.finalize_keymap_cursor(settings);
+
if settings.inline_height > 0 {
terminal.clear()?;
}