Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add option to set the position of the preview window #2064

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/atuin-client/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ records = true
## static: length of the longest command stored in the history.
# strategy = "auto"

## Sets the position of the preview window.
## possible values: bottom, top
#position = "bottom"

[daemon]
## Enables using the daemon to sync. Requires the daemon to be running in the background. Start it with `atuin daemon`
# enabled = false
Expand Down
15 changes: 15 additions & 0 deletions crates/atuin-client/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ pub struct Keys {
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Preview {
pub strategy: PreviewStrategy,
pub position: PreviewPosition,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
Expand Down Expand Up @@ -374,6 +375,7 @@ impl Default for Preview {
fn default() -> Self {
Self {
strategy: PreviewStrategy::Auto,
position: PreviewPosition::Bottom,
}
}
}
Expand Down Expand Up @@ -412,6 +414,18 @@ pub enum PreviewStrategy {
Static,
}

// The preview position.
#[derive(Clone, Debug, Deserialize, Copy, PartialEq, Eq, ValueEnum, Serialize)]
pub enum PreviewPosition {
// Preview position Top.
#[serde(rename = "top")]
Top,

// Preview position Bottom.
#[serde(rename = "bottom")]
Bottom,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Settings {
pub dialect: Dialect,
Expand Down Expand Up @@ -715,6 +729,7 @@ impl Settings {
.set_default("inline_height", 0)?
.set_default("show_preview", true)?
.set_default("preview.strategy", "auto")?
.set_default("preview.position", "bottom")?
.set_default("max_preview_height", 4)?
.set_default("show_help", true)?
.set_default("show_tabs", true)?
Expand Down
148 changes: 126 additions & 22 deletions crates/atuin/src/command/client/search/interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ use atuin_client::{
database::{current_context, Database},
history::{store::HistoryStore, History, HistoryStats},
settings::{
CursorStyle, ExitMode, FilterMode, KeymapMode, PreviewStrategy, SearchMode, Settings,
CursorStyle, ExitMode, FilterMode, KeymapMode, PreviewPosition, PreviewStrategy,
SearchMode, Settings,
},
};

Expand All @@ -42,7 +43,10 @@ use ratatui::{
prelude::*,
style::{Modifier, Style},
text::{Line, Span, Text},
widgets::{block::Title, Block, BorderType, Borders, Padding, Paragraph, Tabs},
widgets::{
block::{title, Title},
Block, BorderType, Borders, Padding, Paragraph, Tabs,
},
Frame, Terminal, TerminalOptions, Viewport,
};

Expand Down Expand Up @@ -82,6 +86,7 @@ struct StyleState {
compact: bool,
invert: bool,
inner_width: usize,
position_bottom: bool,
}

impl State {
Expand Down Expand Up @@ -593,6 +598,7 @@ impl State {
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::bool_to_int_with_if)]
#[allow(clippy::too_many_lines)]
#[allow(clippy::cognitive_complexity)]
fn draw(
&mut self,
f: &mut Frame,
Expand Down Expand Up @@ -620,35 +626,72 @@ impl State {
);
let show_help = settings.show_help && (!compact || f.size().height > 1);
let show_tabs = settings.show_tabs;
let position_bottom = settings.preview.position == PreviewPosition::Bottom;
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(0)
.horizontal_margin(1)
.constraints::<&[Constraint]>(
if invert {
if position_bottom && invert {
[
Constraint::Length(1 + border_size), // input
Constraint::Min(1), // results list
Constraint::Length(preview_height), // preview
Constraint::Length(if show_tabs { 1 } else { 0 }), // tabs
Constraint::Length(if show_help { 1 } else { 0 }), // header (sic)
]
} else {
} else if position_bottom && !invert {
[
Constraint::Length(if show_help { 1 } else { 0 }), // header
Constraint::Length(if show_tabs { 1 } else { 0 }), // tabs
Constraint::Min(1), // results list
Constraint::Length(1 + border_size), // input
Constraint::Length(preview_height), // preview
]
} else if !position_bottom && invert {
[
Constraint::Length(preview_height), // preview
Constraint::Length(1 + border_size), // input
Constraint::Min(1), // results list
Constraint::Length(if show_tabs { 1 } else { 0 }), // tabs
Constraint::Length(if show_help { 1 } else { 0 }), // header (sic)
]
} else {
[
Constraint::Length(if show_help { 1 } else { 0 }), // header
Constraint::Length(if show_tabs { 1 } else { 0 }), // tabs
Constraint::Length(preview_height), // preview
Constraint::Min(1), // results list
Constraint::Length(1 + border_size), // input
]
}
.as_ref(),
)
.split(f.size());

let input_chunk = if invert { chunks[0] } else { chunks[3] };
let results_list_chunk = if invert { chunks[1] } else { chunks[2] };
let preview_chunk = if invert { chunks[2] } else { chunks[4] };
let input_chunk = if position_bottom && invert {
chunks[0]
} else if position_bottom && !invert {
chunks[3]
} else if !position_bottom && invert {
chunks[1]
} else {
chunks[4]
};
let results_list_chunk = if position_bottom && invert {
chunks[1]
} else if !position_bottom && !invert {
chunks[3]
} else {
chunks[2]
};
let preview_chunk = if position_bottom && !invert {
chunks[4]
} else if !position_bottom && invert {
chunks[0]
} else {
chunks[2]
};
let tabs_chunk = if invert { chunks[3] } else { chunks[1] };
let header_chunk = if invert { chunks[4] } else { chunks[0] };

Expand All @@ -669,6 +712,7 @@ impl State {
compact,
invert,
inner_width: input_chunk.width.into(),
position_bottom,
};

let header_chunks = Layout::default()
Expand Down Expand Up @@ -745,7 +789,7 @@ impl State {
};
let preview = self.build_preview(
results,
compact,
style,
preview_width,
preview_chunk.width.into(),
theme,
Expand All @@ -754,11 +798,12 @@ impl State {

let extra_width = UnicodeWidthStr::width(self.search.input.substring());

let cursor_offset = if compact { 0 } else { 1 };
let compact_cursor_offset = if compact { 0 } else { 1 };
let position_cursor_offset = if !compact && !position_bottom { 1 } else { 0 };
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,
input_chunk.x + extra_width as u16 + PREFIX_LENGTH + 1 + compact_cursor_offset,
input_chunk.y + compact_cursor_offset - position_cursor_offset,
);
}

Expand Down Expand Up @@ -847,19 +892,34 @@ impl State {

if style.compact {
results_list
} else if style.invert {
} else if style.position_bottom && style.invert {
results_list.block(
Block::default()
.borders(Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::Rounded)
.title(format!("{:─>width$}", "", width = style.inner_width - 2)),
.title(format!("{:─>width$}", "", width = style.inner_width - 2))
.title_position(title::Position::Top),
)
} else {
} else if style.position_bottom && !style.invert {
results_list.block(
Block::default()
.borders(Borders::TOP | Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::Rounded),
)
} else if !style.position_bottom && style.invert {
results_list.block(
Block::default()
.borders(Borders::BOTTOM | Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::Rounded),
)
} else {
results_list.block(
Block::default()
.borders(Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::Rounded)
.title(format!("{:─>width$}", "", width = style.inner_width - 2))
.title_position(title::Position::Bottom),
)
}
}

Expand All @@ -878,26 +938,42 @@ impl State {
let input = Paragraph::new(input);
if style.compact {
input
} else if style.invert {
} else if style.position_bottom && style.invert {
input.block(
Block::default()
.borders(Borders::LEFT | Borders::RIGHT | Borders::TOP)
.border_type(BorderType::Rounded),
)
} else {
} else if style.position_bottom && !style.invert {
input.block(
Block::default()
.borders(Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::Rounded)
.title(format!("{:─>width$}", "", width = style.inner_width - 2))
.title_position(title::Position::Top),
)
} else if !style.position_bottom && style.invert {
input.block(
Block::default()
.borders(Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::Rounded)
.title(format!("{:─>width$}", "", width = style.inner_width - 2)),
.title(format!("{:─>width$}", "", width = style.inner_width - 2))
.title_position(title::Position::Bottom),
)
} else {
input.block(
Block::default()
.borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM)
.border_type(BorderType::Rounded),
)
}
}

#[allow(clippy::if_same_then_else)] // for code legibility
fn build_preview(
&mut self,
results: &[History],
compact: bool,
style: StyleState,
preview_width: u16,
chunk_width: usize,
theme: &Theme,
Expand All @@ -919,14 +995,39 @@ impl State {
})
.join("\n")
};
let preview = if compact {
let preview = if style.compact {
Paragraph::new(command).style(theme.as_style(Meaning::Annotation))
} else {
} else if style.position_bottom && style.invert {
Paragraph::new(command).block(
Block::default()
.borders(Borders::BOTTOM | Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::Rounded)
.title(format!("{:─>width$}", "", width = chunk_width - 2)),
.title(format!("{:─>width$}", "", width = chunk_width - 2))
.title_position(title::Position::Top),
)
} else if style.position_bottom && !style.invert {
Paragraph::new(command).block(
Block::default()
.borders(Borders::BOTTOM | Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::Rounded)
.title(format!("{:─>width$}", "", width = chunk_width - 2))
.title_position(title::Position::Top),
)
} else if !style.position_bottom && style.invert {
Paragraph::new(command).block(
Block::default()
.borders(Borders::TOP | Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::Rounded)
.title(format!("{:─>width$}", "", width = chunk_width - 2))
.title_position(title::Position::Bottom),
)
} else {
Paragraph::new(command).block(
Block::default()
.borders(Borders::TOP | Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::Rounded)
.title(format!("{:─>width$}", "", width = chunk_width - 2))
.title_position(title::Position::Bottom),
)
};
preview
Expand Down Expand Up @@ -1212,7 +1313,7 @@ fn set_clipboard(_s: String) {}
#[cfg(test)]
mod tests {
use atuin_client::history::History;
use atuin_client::settings::{Preview, PreviewStrategy, Settings};
use atuin_client::settings::{Preview, PreviewPosition, PreviewStrategy, Settings};

use super::State;

Expand All @@ -1221,6 +1322,7 @@ mod tests {
let settings_preview_auto = Settings {
preview: Preview {
strategy: PreviewStrategy::Auto,
position: PreviewPosition::Bottom,
},
show_preview: true,
..Settings::utc()
Expand All @@ -1229,6 +1331,7 @@ mod tests {
let settings_preview_auto_h2 = Settings {
preview: Preview {
strategy: PreviewStrategy::Auto,
position: PreviewPosition::Bottom,
},
show_preview: true,
max_preview_height: 2,
Expand All @@ -1238,6 +1341,7 @@ mod tests {
let settings_preview_h4 = Settings {
preview: Preview {
strategy: PreviewStrategy::Static,
position: PreviewPosition::Bottom,
},
show_preview: true,
max_preview_height: 4,
Expand Down