summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@elliehuxtable.com>2023-05-21 17:42:44 +0100
committerGitHub <noreply@github.com>2023-05-21 17:42:44 +0100
commit5b5e4eaa865454134891fd1e45ad2b07b4015825 (patch)
treec389eb36085f9069cd45bbcb2420a99248122128
parentd2240e1163a62f96dfb1cb99c38ffa2390d53c33 (diff)
Input bar at the top if we are in inline mode (#866)
* Put input chunk at the top in inline mode * Invert the search results if bar is at top * fix styling on reversed rendering * add setting * settings --------- Co-authored-by: Patrick Decat <pdecat@gmail.com> Co-authored-by: Conrad Ludgate <conrad.ludgate@truelayer.com>
-rw-r--r--atuin-client/src/settings.rs2
-rw-r--r--atuin/src/command/client/search/history_list.rs14
-rw-r--r--atuin/src/command/client/search/interactive.rs142
3 files changed, 109 insertions, 49 deletions
diff --git a/atuin-client/src/settings.rs b/atuin-client/src/settings.rs
index 7da83913..71f72730 100644
--- a/atuin-client/src/settings.rs
+++ b/atuin-client/src/settings.rs
@@ -147,6 +147,7 @@ pub struct Settings {
pub filter_mode_shell_up_key_binding: Option<FilterMode>,
pub shell_up_key_binding: bool,
pub inline_height: u16,
+ pub invert: bool,
pub show_preview: bool,
pub exit_mode: ExitMode,
pub word_jump_mode: WordJumpMode,
@@ -336,6 +337,7 @@ impl Settings {
.set_default("style", "auto")?
.set_default("inline_height", 0)?
.set_default("show_preview", false)?
+ .set_default("invert", false)?
.set_default("exit_mode", "return-original")?
.set_default("word_jump_mode", "emacs")?
.set_default(
diff --git a/atuin/src/command/client/search/history_list.rs b/atuin/src/command/client/search/history_list.rs
index eedab1a5..bc554900 100644
--- a/atuin/src/command/client/search/history_list.rs
+++ b/atuin/src/command/client/search/history_list.rs
@@ -13,6 +13,7 @@ use super::format_duration;
pub struct HistoryList<'a> {
history: &'a [History],
block: Option<Block<'a>>,
+ inverted: bool,
}
#[derive(Default)]
@@ -61,6 +62,7 @@ impl<'a> StatefulWidget for HistoryList<'a> {
x: 0,
y: 0,
state,
+ inverted: self.inverted,
};
for item in self.history.iter().skip(state.offset).take(end - start) {
@@ -77,10 +79,11 @@ impl<'a> StatefulWidget for HistoryList<'a> {
}
impl<'a> HistoryList<'a> {
- pub fn new(history: &'a [History]) -> Self {
+ pub fn new(history: &'a [History], inverted: bool) -> Self {
Self {
history,
block: None,
+ inverted,
}
}
@@ -110,6 +113,7 @@ struct DrawState<'a> {
x: u16,
y: u16,
state: &'a ListState,
+ inverted: bool,
}
// longest line prefix I could come up with
@@ -176,7 +180,13 @@ impl DrawState<'_> {
fn draw(&mut self, s: &str, style: Style) {
let cx = self.list_area.left() + self.x;
- let cy = self.list_area.bottom() - self.y - 1;
+
+ let cy = if self.inverted {
+ self.list_area.top() + self.y
+ } else {
+ self.list_area.bottom() - self.y - 1
+ };
+
let w = (self.list_area.width - self.x) as usize;
self.x += self.buf.set_stringn(cx, cy, s, w, style).0 - cx;
}
diff --git a/atuin/src/command/client/search/interactive.rs b/atuin/src/command/client/search/interactive.rs
index 300bc791..d35d806d 100644
--- a/atuin/src/command/client/search/interactive.rs
+++ b/atuin/src/command/client/search/interactive.rs
@@ -47,6 +47,13 @@ struct State {
engine: Box<dyn SearchEngine>,
}
+#[derive(Clone, Copy)]
+struct StyleState {
+ compact: bool,
+ invert: bool,
+ inner_width: usize,
+}
+
impl State {
async fn query_results(&mut self, db: &mut dyn Database) -> Result<Vec<History>> {
let results = self.engine.query(&self.search, db).await?;
@@ -99,6 +106,7 @@ impl State {
let ctrl = input.modifiers.contains(KeyModifiers::CONTROL);
let alt = input.modifiers.contains(KeyModifiers::ALT);
+
// reset the state, will be set to true later if user really did change it
self.switched_search_mode = false;
match input.code {
@@ -190,13 +198,23 @@ impl State {
self.search_mode = self.search_mode.next(settings);
self.engine = engines::engine(self.search_mode);
}
- KeyCode::Down if self.results_state.selected() == 0 => {
+ KeyCode::Down if !settings.invert && self.results_state.selected() == 0 => {
return Some(match settings.exit_mode {
ExitMode::ReturnOriginal => RETURN_ORIGINAL,
ExitMode::ReturnQuery => RETURN_QUERY,
})
}
- KeyCode::Down => {
+ KeyCode::Up if settings.invert && self.results_state.selected() == 0 => {
+ return Some(match settings.exit_mode {
+ ExitMode::ReturnOriginal => RETURN_ORIGINAL,
+ ExitMode::ReturnQuery => RETURN_QUERY,
+ })
+ }
+ KeyCode::Down if !settings.invert => {
+ let i = self.results_state.selected().saturating_sub(1);
+ self.results_state.select(i);
+ }
+ KeyCode::Up if settings.invert => {
let i = self.results_state.selected().saturating_sub(1);
self.results_state.select(i);
}
@@ -204,7 +222,11 @@ impl State {
let i = self.results_state.selected().saturating_sub(1);
self.results_state.select(i);
}
- KeyCode::Up => {
+ KeyCode::Up if !settings.invert => {
+ let i = self.results_state.selected() + 1;
+ self.results_state.select(i.min(len - 1));
+ }
+ KeyCode::Down if settings.invert => {
let i = self.results_state.selected() + 1;
self.results_state.select(i.min(len - 1));
}
@@ -231,16 +253,16 @@ impl State {
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::bool_to_int_with_if)]
- fn draw<T: Backend>(
- &mut self,
- f: &mut Frame<'_, T>,
- results: &[History],
- compact: bool,
- show_preview: bool,
- ) {
+ fn draw<T: Backend>(&mut self, f: &mut Frame<'_, T>, results: &[History], settings: &Settings) {
+ let compact = match settings.style {
+ atuin_client::settings::Style::Auto => f.size().height < 14,
+ atuin_client::settings::Style::Compact => true,
+ atuin_client::settings::Style::Full => false,
+ };
+ let invert = settings.invert;
let border_size = if compact { 0 } else { 1 };
let preview_width = f.size().width - 2;
- let preview_height = if show_preview {
+ let preview_height = if settings.show_preview {
let longest_command = results
.iter()
.max_by(|h1, h2| h1.command.len().cmp(&h2.command.len()));
@@ -262,15 +284,34 @@ impl State {
.margin(0)
.horizontal_margin(1)
.constraints(
- [
- Constraint::Length(if show_help { 1 } else { 0 }),
- Constraint::Min(1),
- Constraint::Length(1 + border_size),
- Constraint::Length(preview_height),
- ]
+ if invert {
+ [
+ Constraint::Length(1 + border_size), // input
+ Constraint::Min(1), // results list
+ Constraint::Length(preview_height), // preview
+ Constraint::Length(if show_help { 1 } else { 0 }), // header (sic)
+ ]
+ } else {
+ [
+ Constraint::Length(if show_help { 1 } else { 0 }), // header
+ Constraint::Min(1), // results list
+ Constraint::Length(1 + border_size), // input
+ Constraint::Length(preview_height), // preview
+ ]
+ }
.as_ref(),
)
.split(f.size());
+ let input_chunk = if invert { chunks[0] } else { chunks[2] };
+ let results_list_chunk = chunks[1];
+ let preview_chunk = if invert { chunks[2] } else { chunks[3] };
+ let header_chunk = if invert { chunks[3] } else { chunks[0] };
+
+ let style = StyleState {
+ compact,
+ invert,
+ inner_width: input_chunk.width.into(),
+ };
let header_chunks = Layout::default()
.direction(Direction::Horizontal)
@@ -282,7 +323,7 @@ impl State {
]
.as_ref(),
)
- .split(chunks[0]);
+ .split(header_chunk);
let title = self.build_title();
f.render_widget(title, header_chunks[0]);
@@ -293,22 +334,23 @@ impl State {
let stats = self.build_stats();
f.render_widget(stats, header_chunks[2]);
- let results_list = Self::build_results_list(compact, results);
- f.render_stateful_widget(results_list, chunks[1], &mut self.results_state);
+ let results_list = Self::build_results_list(style, results);
+ f.render_stateful_widget(results_list, results_list_chunk, &mut self.results_state);
- let input = self.build_input(compact, chunks[2].width.into());
- f.render_widget(input, chunks[2]);
+ let input = self.build_input(style);
+ f.render_widget(input, input_chunk);
- let preview = self.build_preview(results, compact, preview_width, chunks[3].width.into());
- f.render_widget(preview, chunks[3]);
+ let preview =
+ self.build_preview(results, compact, preview_width, preview_chunk.width.into());
+ f.render_widget(preview, preview_chunk);
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
- chunks[2].x + extra_width as u16 + PREFIX_LENGTH + 1 + cursor_offset,
- chunks[2].y + cursor_offset,
+ input_chunk.x + extra_width as u16 + PREFIX_LENGTH + 1 + cursor_offset,
+ input_chunk.y + cursor_offset,
);
}
@@ -350,20 +392,27 @@ impl State {
stats
}
- fn build_results_list(compact: bool, results: &[History]) -> HistoryList {
- let results_list = if compact {
- HistoryList::new(results)
+ fn build_results_list(style: StyleState, results: &[History]) -> HistoryList {
+ let results_list = HistoryList::new(results, style.invert);
+ if style.compact {
+ results_list
+ } else if style.invert {
+ results_list.block(
+ Block::default()
+ .borders(Borders::LEFT | Borders::RIGHT)
+ .border_type(BorderType::Rounded)
+ .title(format!("{:─>width$}", "", width = style.inner_width - 2)),
+ )
} else {
- HistoryList::new(results).block(
+ results_list.block(
Block::default()
.borders(Borders::TOP | Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::Rounded),
)
- };
- results_list
+ }
}
- fn build_input(&mut self, compact: bool, chunk_width: usize) -> Paragraph {
+ fn build_input(&mut self, style: StyleState) -> Paragraph {
/// Max width of the UI box showing current mode
const MAX_WIDTH: usize = 14;
let (pref, mode) = if self.switched_search_mode {
@@ -375,17 +424,23 @@ impl State {
// sanity check to ensure we don't exceed the layout limits
debug_assert!(mode_width >= mode.len(), "mode name '{mode}' is too long!");
let input = format!("[{pref}{mode:^mode_width$}] {}", self.search.input.as_str(),);
- let input = if compact {
- Paragraph::new(input)
+ let input = Paragraph::new(input);
+ if style.compact {
+ input
+ } else if style.invert {
+ input.block(
+ Block::default()
+ .borders(Borders::LEFT | Borders::RIGHT | Borders::TOP)
+ .border_type(BorderType::Rounded),
+ )
} else {
- Paragraph::new(input).block(
+ input.block(
Block::default()
.borders(Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::Rounded)
- .title(format!("{:─>width$}", "", width = chunk_width - 2)),
+ .title(format!("{:─>width$}", "", width = style.inner_width - 2)),
)
- };
- input
+ }
}
fn build_preview(
@@ -529,14 +584,7 @@ pub async fn history(
let mut results = app.query_results(&mut db).await?;
let index = 'render: loop {
- let compact = match settings.style {
- atuin_client::settings::Style::Auto => {
- terminal.size().map(|size| size.height < 14).unwrap_or(true)
- }
- atuin_client::settings::Style::Compact => true,
- atuin_client::settings::Style::Full => false,
- };
- terminal.draw(|f| app.draw(f, &results, compact, settings.show_preview))?;
+ terminal.draw(|f| app.draw(f, &results, settings))?;
let initial_input = app.search.input.as_str().to_owned();
let initial_filter_mode = app.search.filter_mode;