diff --git a/crates/atuin-client/config.toml b/crates/atuin-client/config.toml index 9cd833aa..4b2810e5 100644 --- a/crates/atuin-client/config.toml +++ b/crates/atuin-client/config.toml @@ -125,6 +125,13 @@ ## Configure whether or not to show tabs for search and inspect # show_tabs = true +## Configure whether or not the tabs row may be auto-hidden, which includes the current Atuin +## tab, such as Search or Inspector, and other tabs you may wish to see. This will +## only be hidden if there are fewer than this count of lines available, and does not affect the use +## of keyboard shortcuts to switch tab. 0 to never auto-hide, default is 8 (lines). +## This is ignored except in `compact` mode. +# auto_hide_height = 8 + ## Defaults to true. This matches history against a set of default regex, and will not save it if we get a match. Defaults include ## 1. AWS key id ## 2. Github pat (old and new) diff --git a/crates/atuin-client/src/settings.rs b/crates/atuin-client/src/settings.rs index 5fa319e7..efbdb33d 100644 --- a/crates/atuin-client/src/settings.rs +++ b/crates/atuin-client/src/settings.rs @@ -440,6 +440,7 @@ pub struct Settings { pub max_preview_height: u16, pub show_help: bool, pub show_tabs: bool, + pub auto_hide_height: u16, pub exit_mode: ExitMode, pub keymap_mode: KeymapMode, pub keymap_mode_shell: KeymapMode, @@ -722,6 +723,7 @@ impl Settings { .set_default("max_preview_height", 4)? .set_default("show_help", true)? .set_default("show_tabs", true)? + .set_default("auto_hide_height", 8)? .set_default("invert", false)? .set_default("exit_mode", "return-original")? .set_default("word_jump_mode", "emacs")? diff --git a/crates/atuin-client/src/theme.rs b/crates/atuin-client/src/theme.rs index d45f1e9d..9ebe6f9c 100644 --- a/crates/atuin-client/src/theme.rs +++ b/crates/atuin-client/src/theme.rs @@ -53,8 +53,8 @@ pub struct ThemeDefinitionConfigBlock { use crossterm::style::{Color, ContentStyle}; -// For now, a theme is specifically a mapping of meanings to colors, but it may be desirable to -// expand that in the future to general styles. +// For now, a theme is loaded as a mapping of meanings to colors, but it may be desirable to +// expand that in the future to general styles, so we populate a Meaning->ContentStyle hashmap. pub struct Theme { pub name: String, pub parent: Option, diff --git a/crates/atuin/src/command/client/search/history_list.rs b/crates/atuin/src/command/client/search/history_list.rs index 87f803aa..ace8234b 100644 --- a/crates/atuin/src/command/client/search/history_list.rs +++ b/crates/atuin/src/command/client/search/history_list.rs @@ -22,6 +22,7 @@ pub struct HistoryList<'a> { /// Apply an alternative highlighting to the selected row alternate_highlight: bool, now: &'a dyn Fn() -> OffsetDateTime, + indicator: &'a str, theme: &'a Theme, } @@ -74,6 +75,7 @@ impl<'a> StatefulWidget for HistoryList<'a> { inverted: self.inverted, alternate_highlight: self.alternate_highlight, now: &self.now, + indicator: self.indicator, theme: self.theme, }; @@ -96,6 +98,7 @@ impl<'a> HistoryList<'a> { inverted: bool, alternate_highlight: bool, now: &'a dyn Fn() -> OffsetDateTime, + indicator: &'a str, theme: &'a Theme, ) -> Self { Self { @@ -104,6 +107,7 @@ impl<'a> HistoryList<'a> { inverted, alternate_highlight, now, + indicator, theme, } } @@ -137,6 +141,7 @@ struct DrawState<'a> { inverted: bool, alternate_highlight: bool, now: &'a dyn Fn() -> OffsetDateTime, + indicator: &'a str, theme: &'a Theme, } @@ -155,7 +160,12 @@ impl DrawState<'_> { let i = self.y as usize + self.state.offset; let i = i.checked_sub(self.state.selected); let i = i.unwrap_or(10).min(10) * 2; - self.draw(&SLICES[i..i + 3], Style::default()); + let prompt: &str = if i == 0 { + self.indicator + } else { + &SLICES[i..i + 3] + }; + self.draw(prompt, Style::default()); } fn duration(&mut self, h: &History) { diff --git a/crates/atuin/src/command/client/search/interactive.rs b/crates/atuin/src/command/client/search/interactive.rs index dd9f4ed4..c87fff1c 100644 --- a/crates/atuin/src/command/client/search/interactive.rs +++ b/crates/atuin/src/command/client/search/interactive.rs @@ -622,7 +622,12 @@ impl State { preview_width, ); let show_help = settings.show_help && (!compact || f.size().height > 1); - let show_tabs = settings.show_tabs; + // This is an OR, as it seems more likely for someone to wish to override + // tabs unexpectedly being missed, than unexpectedly present. + let hide_extra = settings.auto_hide_height != 0 + && compact + && f.size().height <= settings.auto_hide_height; + let show_tabs = settings.show_tabs && !hide_extra; let chunks = Layout::default() .direction(Direction::Vertical) .margin(0) @@ -636,6 +641,14 @@ impl State { Constraint::Length(if show_tabs { 1 } else { 0 }), // tabs Constraint::Length(if show_help { 1 } else { 0 }), // header (sic) ] + } else if hide_extra { + [ + Constraint::Length(if show_help { 1 } else { 0 }), // header + Constraint::Length(0), // tabs + Constraint::Min(1), // results list + Constraint::Length(0), + Constraint::Length(0), + ] } else { [ Constraint::Length(if show_help { 1 } else { 0 }), // header @@ -660,13 +673,15 @@ impl State { // also allocate less 🙈 let titles: Vec<_> = TAB_TITLES.iter().copied().map(Line::from).collect(); - let tabs = Tabs::new(titles) - .block(Block::default().borders(Borders::NONE)) - .select(self.tab_index) - .style(Style::default()) - .highlight_style(Style::default().bold().white().on_black()); + if show_tabs { + let tabs = Tabs::new(titles) + .block(Block::default().borders(Borders::NONE)) + .select(self.tab_index) + .style(Style::default()) + .highlight_style(Style::default().bold().white().on_black()); - f.render_widget(tabs, tabs_chunk); + f.render_widget(tabs, tabs_chunk); + } let style = StyleState { compact, @@ -695,10 +710,27 @@ impl State { let stats_tab = self.build_stats(theme); f.render_widget(stats_tab, header_chunks[2]); + let indicator: String = if !hide_extra { + " > ".to_string() + } else if self.switched_search_mode { + format!("S{}>", self.search_mode.as_str().chars().next().unwrap()) + } else { + format!( + "{}> ", + self.search.filter_mode.as_str().chars().next().unwrap() + ) + }; + match self.tab_index { 0 => { - let results_list = - Self::build_results_list(style, results, self.keymap_mode, &self.now, theme); + let results_list = Self::build_results_list( + style, + results, + self.keymap_mode, + &self.now, + indicator.as_str(), + theme, + ); f.render_stateful_widget(results_list, results_list_chunk, &mut self.results_state); } @@ -738,31 +770,33 @@ impl State { } } - let input = self.build_input(style); - f.render_widget(input, input_chunk); + if !hide_extra { + let input = self.build_input(style); + f.render_widget(input, input_chunk); - let preview_width = if compact { - preview_width - } else { - preview_width - 2 - }; - let preview = self.build_preview( - results, - compact, - preview_width, - preview_chunk.width.into(), - theme, - ); - f.render_widget(preview, preview_chunk); + let preview_width = if compact { + preview_width + } else { + preview_width - 2 + }; + let preview = self.build_preview( + results, + compact, + preview_width, + preview_chunk.width.into(), + theme, + ); + f.render_widget(preview, preview_chunk); - let extra_width = UnicodeWidthStr::width(self.search.input.substring()); + let extra_width = UnicodeWidthStr::width(self.search.input.substring()); - let cursor_offset = if compact { 0 } else { 1 }; - f.set_cursor( - // Put cursor past the end of the input text - input_chunk.x + extra_width as u16 + PREFIX_LENGTH + 1 + cursor_offset, - input_chunk.y + cursor_offset, - ); + let cursor_offset = if compact { 0 } else { 1 }; + f.set_cursor( + // Put cursor past the end of the input text + input_chunk.x + extra_width as u16 + PREFIX_LENGTH + 1 + cursor_offset, + input_chunk.y + cursor_offset, + ); + } } fn build_title(&self, theme: &Theme) -> Paragraph { @@ -836,6 +870,7 @@ impl State { results: &'a [History], keymap_mode: KeymapMode, now: &'a dyn Fn() -> OffsetDateTime, + indicator: &'a str, theme: &'a Theme, ) -> HistoryList<'a> { let results_list = HistoryList::new( @@ -843,6 +878,7 @@ impl State { style.invert, keymap_mode == KeymapMode::VimNormal, now, + indicator, theme, );