feat: Ultracompact Mode (search-only) (#2357)

* feat: add always_show_tabs setting

* feat(ultracompact): extra-compact options

* feat(ultracompact): enable via auto_hide_height

* feat(ultracompact): clarify comment

* fix(theming): minor tidyup to theming comment

* chore: rustfmt/clippy corrections

* chore: testing if CI issue was transient
This commit is contained in:
P T Weir 2024-10-07 16:54:07 +01:00 committed by GitHub
parent 5651036d8f
commit 4c2d8201dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 89 additions and 34 deletions

View File

@ -125,6 +125,13 @@
## Configure whether or not to show tabs for search and inspect ## Configure whether or not to show tabs for search and inspect
# show_tabs = true # 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 ## 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 ## 1. AWS key id
## 2. Github pat (old and new) ## 2. Github pat (old and new)

View File

@ -440,6 +440,7 @@ pub struct Settings {
pub max_preview_height: u16, pub max_preview_height: u16,
pub show_help: bool, pub show_help: bool,
pub show_tabs: bool, pub show_tabs: bool,
pub auto_hide_height: u16,
pub exit_mode: ExitMode, pub exit_mode: ExitMode,
pub keymap_mode: KeymapMode, pub keymap_mode: KeymapMode,
pub keymap_mode_shell: KeymapMode, pub keymap_mode_shell: KeymapMode,
@ -722,6 +723,7 @@ impl Settings {
.set_default("max_preview_height", 4)? .set_default("max_preview_height", 4)?
.set_default("show_help", true)? .set_default("show_help", true)?
.set_default("show_tabs", true)? .set_default("show_tabs", true)?
.set_default("auto_hide_height", 8)?
.set_default("invert", false)? .set_default("invert", false)?
.set_default("exit_mode", "return-original")? .set_default("exit_mode", "return-original")?
.set_default("word_jump_mode", "emacs")? .set_default("word_jump_mode", "emacs")?

View File

@ -53,8 +53,8 @@ pub struct ThemeDefinitionConfigBlock {
use crossterm::style::{Color, ContentStyle}; use crossterm::style::{Color, ContentStyle};
// For now, a theme is specifically a mapping of meanings to colors, but it may be desirable to // 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. // expand that in the future to general styles, so we populate a Meaning->ContentStyle hashmap.
pub struct Theme { pub struct Theme {
pub name: String, pub name: String,
pub parent: Option<String>, pub parent: Option<String>,

View File

@ -22,6 +22,7 @@ pub struct HistoryList<'a> {
/// Apply an alternative highlighting to the selected row /// Apply an alternative highlighting to the selected row
alternate_highlight: bool, alternate_highlight: bool,
now: &'a dyn Fn() -> OffsetDateTime, now: &'a dyn Fn() -> OffsetDateTime,
indicator: &'a str,
theme: &'a Theme, theme: &'a Theme,
} }
@ -74,6 +75,7 @@ impl<'a> StatefulWidget for HistoryList<'a> {
inverted: self.inverted, inverted: self.inverted,
alternate_highlight: self.alternate_highlight, alternate_highlight: self.alternate_highlight,
now: &self.now, now: &self.now,
indicator: self.indicator,
theme: self.theme, theme: self.theme,
}; };
@ -96,6 +98,7 @@ impl<'a> HistoryList<'a> {
inverted: bool, inverted: bool,
alternate_highlight: bool, alternate_highlight: bool,
now: &'a dyn Fn() -> OffsetDateTime, now: &'a dyn Fn() -> OffsetDateTime,
indicator: &'a str,
theme: &'a Theme, theme: &'a Theme,
) -> Self { ) -> Self {
Self { Self {
@ -104,6 +107,7 @@ impl<'a> HistoryList<'a> {
inverted, inverted,
alternate_highlight, alternate_highlight,
now, now,
indicator,
theme, theme,
} }
} }
@ -137,6 +141,7 @@ struct DrawState<'a> {
inverted: bool, inverted: bool,
alternate_highlight: bool, alternate_highlight: bool,
now: &'a dyn Fn() -> OffsetDateTime, now: &'a dyn Fn() -> OffsetDateTime,
indicator: &'a str,
theme: &'a Theme, theme: &'a Theme,
} }
@ -155,7 +160,12 @@ impl DrawState<'_> {
let i = self.y as usize + self.state.offset; let i = self.y as usize + self.state.offset;
let i = i.checked_sub(self.state.selected); let i = i.checked_sub(self.state.selected);
let i = i.unwrap_or(10).min(10) * 2; 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) { fn duration(&mut self, h: &History) {

View File

@ -622,7 +622,12 @@ impl State {
preview_width, preview_width,
); );
let show_help = settings.show_help && (!compact || f.size().height > 1); 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() let chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.margin(0) .margin(0)
@ -636,6 +641,14 @@ impl State {
Constraint::Length(if show_tabs { 1 } else { 0 }), // tabs Constraint::Length(if show_tabs { 1 } else { 0 }), // tabs
Constraint::Length(if show_help { 1 } else { 0 }), // header (sic) 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 { } else {
[ [
Constraint::Length(if show_help { 1 } else { 0 }), // header Constraint::Length(if show_help { 1 } else { 0 }), // header
@ -660,13 +673,15 @@ impl State {
// also allocate less 🙈 // also allocate less 🙈
let titles: Vec<_> = TAB_TITLES.iter().copied().map(Line::from).collect(); let titles: Vec<_> = TAB_TITLES.iter().copied().map(Line::from).collect();
let tabs = Tabs::new(titles) if show_tabs {
.block(Block::default().borders(Borders::NONE)) let tabs = Tabs::new(titles)
.select(self.tab_index) .block(Block::default().borders(Borders::NONE))
.style(Style::default()) .select(self.tab_index)
.highlight_style(Style::default().bold().white().on_black()); .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 { let style = StyleState {
compact, compact,
@ -695,10 +710,27 @@ impl State {
let stats_tab = self.build_stats(theme); let stats_tab = self.build_stats(theme);
f.render_widget(stats_tab, header_chunks[2]); 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 { match self.tab_index {
0 => { 0 => {
let results_list = let results_list = Self::build_results_list(
Self::build_results_list(style, results, self.keymap_mode, &self.now, theme); style,
results,
self.keymap_mode,
&self.now,
indicator.as_str(),
theme,
);
f.render_stateful_widget(results_list, results_list_chunk, &mut self.results_state); 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); if !hide_extra {
f.render_widget(input, input_chunk); let input = self.build_input(style);
f.render_widget(input, input_chunk);
let preview_width = if compact { let preview_width = if compact {
preview_width preview_width
} else { } else {
preview_width - 2 preview_width - 2
}; };
let preview = self.build_preview( let preview = self.build_preview(
results, results,
compact, compact,
preview_width, preview_width,
preview_chunk.width.into(), preview_chunk.width.into(),
theme, theme,
); );
f.render_widget(preview, preview_chunk); 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 }; let cursor_offset = if compact { 0 } else { 1 };
f.set_cursor( f.set_cursor(
// Put cursor past the end of the input text // Put cursor past the end of the input text
input_chunk.x + extra_width as u16 + PREFIX_LENGTH + 1 + cursor_offset, input_chunk.x + extra_width as u16 + PREFIX_LENGTH + 1 + cursor_offset,
input_chunk.y + cursor_offset, input_chunk.y + cursor_offset,
); );
}
} }
fn build_title(&self, theme: &Theme) -> Paragraph { fn build_title(&self, theme: &Theme) -> Paragraph {
@ -836,6 +870,7 @@ impl State {
results: &'a [History], results: &'a [History],
keymap_mode: KeymapMode, keymap_mode: KeymapMode,
now: &'a dyn Fn() -> OffsetDateTime, now: &'a dyn Fn() -> OffsetDateTime,
indicator: &'a str,
theme: &'a Theme, theme: &'a Theme,
) -> HistoryList<'a> { ) -> HistoryList<'a> {
let results_list = HistoryList::new( let results_list = HistoryList::new(
@ -843,6 +878,7 @@ impl State {
style.invert, style.invert,
keymap_mode == KeymapMode::VimNormal, keymap_mode == KeymapMode::VimNormal,
now, now,
indicator,
theme, theme,
); );