From 499bfdb8e5b33d1c4c8554908fc3d71abf8bd0b3 Mon Sep 17 00:00:00 2001
From: Alex Pasmantier <47638216+alexpasmantier@users.noreply.github.com>
Date: Sat, 28 Dec 2024 15:21:17 +0100
Subject: [PATCH] refactor(ui): more compact general layout and make preview
panel optional (#148)
Fixes #147
---
.config/config.toml | 37 +++++----
crates/television-screen/src/input.rs | 3 +-
crates/television-screen/src/keybindings.rs | 14 ----
crates/television-screen/src/layout.rs | 61 ++++++---------
crates/television-screen/src/preview.rs | 45 ++++-------
crates/television-screen/src/results.rs | 8 ++
crates/television/action.rs | 3 +
crates/television/config/ui.rs | 22 ++++--
crates/television/television.rs | 87 ++++++++++++---------
9 files changed, 142 insertions(+), 138 deletions(-)
diff --git a/.config/config.toml b/.config/config.toml
index 5ec5a19..1a5e4e0 100644
--- a/.config/config.toml
+++ b/.config/config.toml
@@ -43,11 +43,15 @@ use_nerd_font_icons = false
ui_scale = 100
# Whether to show the top help bar in the UI by default
# This option can be toggled with the (default) `ctrl-g` keybinding
-show_help_bar = true
+show_help_bar = false
+# Whether to show the preview panel in the UI by default
+# This option can be toggled with the (default) `ctrl-o` keybinding
+show_preview_panel = true
# Where to place the input bar in the UI (top or bottom)
-input_bar_position = "bottom"
+input_bar_position = "top"
+# DEPRECATED: title is now always displayed at the top as part of the border
# Where to place the preview title in the UI (top or bottom)
-preview_title_position = "top"
+# preview_title_position = "top"
# The theme to use for the UI
# A list of builtin themes can be found in the `themes` directory of the television
# repository. You may also create your own theme by creating a new file in a `themes`
@@ -88,6 +92,8 @@ toggle_remote_control = "ctrl-r"
toggle_send_to_channel = "ctrl-s"
# Toggle the help bar
toggle_help = "ctrl-g"
+# Toggle the preview panel
+toggle_preview = "ctrl-o"
# Remote control mode
@@ -106,6 +112,8 @@ select_entry = "enter"
toggle_remote_control = "ctrl-r"
# Toggle the help bar
toggle_help = "ctrl-g"
+# Toggle the preview panel
+toggle_preview = "ctrl-o"
# Send to channel mode
@@ -124,24 +132,27 @@ select_entry = "enter"
toggle_send_to_channel = "ctrl-s"
# Toggle the help bar
toggle_help = "ctrl-g"
+# Toggle the preview panel
+toggle_preview = "ctrl-o"
# Shell integration
# ----------------------------------------------------------------------------
#
# The shell integration feature allows you to use television as a picker for
-# your shell commands. E.g. typing `git checkout ` will open television
-# with a list of branches to checkout.
+# your shell commands. E.g. typing `git checkout ` will open television
+# with a list of branches to choose from.
[shell_integration.commands]
-# Which commands should trigger which channels
-# The keys are the commands that should trigger the channels, and the values
-# are the channels that should be triggered.
-# Example: `git checkout` should trigger the `git-branches` channel
-# `ls` should trigger the `dirs` channel
-# `cat` should trigger the `files` channel
+# Add your commands here. Each key is a command that will trigger tv with the
+# corresponding channel as value.
+# Example: say you want the following prompts to trigger the following channels
+# when pressing :
+# `git checkout` should trigger the `git-branches` channel
+# `ls` should trigger the `dirs` channel
+# `cat` should trigger the `files` channel
#
-# Would be written as:
+# You would add the following to your configuration file:
# ```
# [shell_integration.commands]
# "git checkout" = "git-branches"
@@ -149,8 +160,6 @@ toggle_help = "ctrl-g"
# "cat" = "files"
# ```
-# The following are an example, you can remove/modify them and add your own.
-
# shell history (according to your shell)
"" = "zsh-history"
diff --git a/crates/television-screen/src/input.rs b/crates/television-screen/src/input.rs
index b67cc22..479e071 100644
--- a/crates/television-screen/src/input.rs
+++ b/crates/television-screen/src/input.rs
@@ -4,7 +4,7 @@ use ratatui::{
Alignment, Constraint, Direction, Layout as RatatuiLayout, Rect,
},
style::{Style, Stylize},
- text::{Line, Span},
+ text::Span,
widgets::{Block, BorderType, Borders, ListState, Paragraph},
Frame,
};
@@ -30,7 +30,6 @@ pub fn draw_input_box(
colorscheme: &Colorscheme,
) -> Result<()> {
let input_block = Block::default()
- .title_top(Line::from(" Pattern ").alignment(Alignment::Center))
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(colorscheme.general.border_fg))
diff --git a/crates/television-screen/src/keybindings.rs b/crates/television-screen/src/keybindings.rs
index ddee2b8..f555ed2 100644
--- a/crates/television-screen/src/keybindings.rs
+++ b/crates/television-screen/src/keybindings.rs
@@ -150,19 +150,6 @@ fn build_keybindings_table_for_channel<'a>(
colorscheme.mode.channel,
));
- // MISC line (quit, help, etc.)
- // Toggle help bar
- let toggle_help_bar_keys = keybindings
- .bindings
- .get(&DisplayableAction::ToggleHelpBar)
- .unwrap();
- let toggle_help_bar_row = Row::new(build_cells_for_group(
- "Toggle help bar",
- toggle_help_bar_keys,
- colorscheme.help.metadata_field_name_fg,
- colorscheme.mode.channel,
- ));
-
let widths = vec![Constraint::Fill(1), Constraint::Fill(2)];
Table::new(
@@ -173,7 +160,6 @@ fn build_keybindings_table_for_channel<'a>(
copy_entry_row,
send_to_channel_row,
switch_channels_row,
- toggle_help_bar_row,
],
widths,
)
diff --git a/crates/television-screen/src/layout.rs b/crates/television-screen/src/layout.rs
index 718b67a..70093c8 100644
--- a/crates/television-screen/src/layout.rs
+++ b/crates/television-screen/src/layout.rs
@@ -84,8 +84,7 @@ pub struct Layout {
pub help_bar: Option,
pub results: Rect,
pub input: Rect,
- pub preview_title: Rect,
- pub preview_window: Rect,
+ pub preview_window: Option,
pub remote_control: Option,
}
@@ -95,15 +94,13 @@ impl Layout {
help_bar: Option,
results: Rect,
input: Rect,
- preview_title: Rect,
- preview_window: Rect,
+ preview_window: Option,
remote_control: Option,
) -> Self {
Self {
help_bar,
results,
input,
- preview_title,
preview_window,
remote_control,
}
@@ -114,8 +111,8 @@ impl Layout {
area: Rect,
with_remote: bool,
with_help_bar: bool,
+ with_preview: bool,
input_position: InputPosition,
- preview_title_position: PreviewTitlePosition,
) -> Self {
let main_block = centered_rect(dimensions.x, dimensions.y, area);
// split the main block into two vertical chunks (help bar + rest)
@@ -152,16 +149,16 @@ impl Layout {
help_bar_layout = None;
}
- // split the main block into two vertical chunks
- let constraints = if with_remote {
- vec![
- Constraint::Fill(1),
- Constraint::Fill(1),
- Constraint::Length(24),
- ]
- } else {
- vec![Constraint::Percentage(50), Constraint::Percentage(50)]
- };
+ // split the main block into 1, 2, or 3 vertical chunks
+ // (results + preview + remote)
+ let mut constraints = vec![Constraint::Fill(1)];
+ if with_preview {
+ constraints.push(Constraint::Fill(1));
+ }
+ if with_remote {
+ // in order to fit with the help bar logo
+ constraints.push(Constraint::Length(24));
+ }
let vt_chunks = layout::Layout::default()
.direction(Direction::Horizontal)
.constraints(constraints)
@@ -186,34 +183,26 @@ impl Layout {
};
// right block: preview title + preview
- let preview_constraints =
- vec![Constraint::Length(3), Constraint::Min(3)];
+ let mut remote_idx = 1;
+ let preview_window = if with_preview {
+ remote_idx += 1;
+ Some(vt_chunks[1])
+ } else {
+ None
+ };
- let right_chunks = layout::Layout::default()
- .direction(Direction::Vertical)
- .constraints(match preview_title_position {
- PreviewTitlePosition::Bottom => {
- preview_constraints.into_iter().rev().collect()
- }
- PreviewTitlePosition::Top => preview_constraints,
- })
- .split(vt_chunks[1]);
- let (preview_title, preview_window) = match preview_title_position {
- PreviewTitlePosition::Top => (right_chunks[0], right_chunks[1]),
- PreviewTitlePosition::Bottom => (right_chunks[1], right_chunks[0]),
+ let remote_control = if with_remote {
+ Some(vt_chunks[remote_idx])
+ } else {
+ None
};
Self::new(
help_bar_layout,
results,
input,
- preview_title,
preview_window,
- if with_remote {
- Some(vt_chunks[2])
- } else {
- None
- },
+ remote_control,
)
}
}
diff --git a/crates/television-screen/src/preview.rs b/crates/television-screen/src/preview.rs
index 9224ed6..ed1f70d 100644
--- a/crates/television-screen/src/preview.rs
+++ b/crates/television-screen/src/preview.rs
@@ -239,14 +239,18 @@ pub fn build_meta_preview_paragraph<'a>(
Paragraph::new(Text::from(lines))
}
-pub fn draw_preview_title_block(
+#[allow(clippy::too_many_arguments)]
+pub fn draw_preview_content_block(
f: &mut Frame,
rect: Rect,
+ entry: &Entry,
preview: &Arc,
+ rendered_preview_cache: &Arc>>,
+ preview_scroll: u16,
use_nerd_font_icons: bool,
colorscheme: &Colorscheme,
) -> Result<()> {
- let mut preview_title_spans = Vec::new();
+ let mut preview_title_spans = vec![Span::from(" ")];
if preview.icon.is_some() && use_nerd_font_icons {
let icon = preview.icon.as_ref().unwrap();
preview_title_spans.push(Span::styled(
@@ -269,38 +273,18 @@ pub fn draw_preview_title_block(
),
Style::default().fg(colorscheme.preview.title_fg).bold(),
));
- let preview_title = Paragraph::new(Line::from(preview_title_spans))
- .block(
- Block::default()
- .padding(Padding::horizontal(1))
- .borders(Borders::ALL)
- .border_type(BorderType::Rounded)
- .border_style(
- Style::default().fg(colorscheme.general.border_fg),
- ),
- )
- .alignment(Alignment::Left)
- .style(Style::default().bg(colorscheme.general.background));
- f.render_widget(preview_title, rect);
- Ok(())
-}
-
-pub fn draw_preview_content_block(
- f: &mut Frame,
- rect: Rect,
- entry: &Entry,
- preview: &Arc,
- rendered_preview_cache: &Arc>>,
- preview_scroll: u16,
- colorscheme: &Colorscheme,
-) {
+ preview_title_spans.push(Span::from(" "));
let preview_outer_block = Block::default()
- .title_top(Line::from(" Preview ").alignment(Alignment::Center))
+ .title_top(
+ Line::from(preview_title_spans)
+ .alignment(Alignment::Center)
+ .style(Style::default().fg(colorscheme.preview.title_fg)),
+ )
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(colorscheme.general.border_fg))
.style(Style::default().bg(colorscheme.general.background))
- .padding(Padding::right(1));
+ .padding(Padding::new(0, 1, 1, 0));
let preview_inner_block =
Block::default().style(Style::default()).padding(Padding {
@@ -321,7 +305,7 @@ pub fn draw_preview_content_block(
{
let p = preview_paragraph.as_ref().clone();
f.render_widget(p.scroll((preview_scroll, 0)), inner);
- return;
+ return Ok(());
}
// If not, render the preview content and cache it if not empty
let c_scheme = colorscheme.clone();
@@ -343,6 +327,7 @@ pub fn draw_preview_content_block(
Arc::new(rp).as_ref().clone().scroll((preview_scroll, 0)),
inner,
);
+ Ok(())
}
fn build_line_number_span<'a>(line_number: usize) -> Span<'a> {
diff --git a/crates/television-screen/src/results.rs b/crates/television-screen/src/results.rs
index 014dfe5..242fed2 100644
--- a/crates/television-screen/src/results.rs
+++ b/crates/television-screen/src/results.rs
@@ -143,9 +143,17 @@ pub fn draw_results_list(
use_nerd_font_icons: bool,
icon_color_cache: &mut HashMap,
colorscheme: &Colorscheme,
+ help_keybinding: &str,
+ preview_keybinding: &str,
) -> Result<()> {
let results_block = Block::default()
.title_top(Line::from(" Results ").alignment(Alignment::Center))
+ .title_bottom(
+ Line::from(format!(
+ " help: <{help_keybinding}> preview: <{preview_keybinding}> "
+ ))
+ .alignment(Alignment::Center),
+ )
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(colorscheme.general.border_fg))
diff --git a/crates/television/action.rs b/crates/television/action.rs
index b0e9ee5..10cb1c2 100644
--- a/crates/television/action.rs
+++ b/crates/television/action.rs
@@ -93,6 +93,9 @@ pub enum Action {
/// Toggle the help bar.
#[serde(alias = "toggle_help")]
ToggleHelp,
+ /// Toggle the preview panel.
+ #[serde(alias = "toggle_preview")]
+ TogglePreview,
/// Signal an error with the given message.
#[serde(skip)]
Error(String),
diff --git a/crates/television/config/ui.rs b/crates/television/config/ui.rs
index e879204..7d3650c 100644
--- a/crates/television/config/ui.rs
+++ b/crates/television/config/ui.rs
@@ -6,16 +6,17 @@ use television_screen::layout::{InputPosition, PreviewTitlePosition};
use super::themes::DEFAULT_THEME;
-const DEFAULT_UI_SCALE: u16 = 90;
+const DEFAULT_UI_SCALE: u16 = 100;
#[derive(Clone, Debug, Deserialize)]
pub struct UiConfig {
pub use_nerd_font_icons: bool,
pub ui_scale: u16,
pub show_help_bar: bool,
+ pub show_preview_panel: bool,
#[serde(default)]
pub input_bar_position: InputPosition,
- pub preview_title_position: PreviewTitlePosition,
+ pub preview_title_position: Option,
pub theme: String,
}
@@ -24,9 +25,10 @@ impl Default for UiConfig {
Self {
use_nerd_font_icons: false,
ui_scale: DEFAULT_UI_SCALE,
- show_help_bar: true,
- input_bar_position: InputPosition::Bottom,
- preview_title_position: PreviewTitlePosition::Top,
+ show_help_bar: false,
+ show_preview_panel: true,
+ input_bar_position: InputPosition::Top,
+ preview_title_position: None,
theme: String::from(DEFAULT_THEME),
}
}
@@ -47,13 +49,21 @@ impl From for ValueKind {
String::from("show_help_bar"),
ValueKind::Boolean(val.show_help_bar).into(),
);
+ m.insert(
+ String::from("show_preview_panel"),
+ ValueKind::Boolean(val.show_preview_panel).into(),
+ );
m.insert(
String::from("input_position"),
ValueKind::String(val.input_bar_position.to_string()).into(),
);
m.insert(
String::from("preview_title_position"),
- ValueKind::String(val.preview_title_position.to_string()).into(),
+ match val.preview_title_position {
+ Some(pos) => ValueKind::String(pos.to_string()),
+ None => ValueKind::Nil,
+ }
+ .into(),
);
m.insert(String::from("theme"), ValueKind::String(val.theme).into());
ValueKind::Table(m)
diff --git a/crates/television/television.rs b/crates/television/television.rs
index 3113cad..5041c33 100644
--- a/crates/television/television.rs
+++ b/crates/television/television.rs
@@ -23,9 +23,7 @@ use television_screen::keybindings::{
};
use television_screen::layout::{Dimensions, InputPosition, Layout};
use television_screen::mode::Mode;
-use television_screen::preview::{
- draw_preview_content_block, draw_preview_title_block,
-};
+use television_screen::preview::draw_preview_content_block;
use television_screen::remote_control::draw_remote_control;
use television_screen::results::draw_results_list;
use television_screen::spinner::{Spinner, SpinnerState};
@@ -386,6 +384,10 @@ impl Television {
Action::ToggleHelp => {
self.config.ui.show_help_bar = !self.config.ui.show_help_bar;
}
+ Action::TogglePreview => {
+ self.config.ui.show_preview_panel =
+ !self.config.ui.show_preview_panel;
+ }
_ => {}
}
Ok(None)
@@ -405,8 +407,8 @@ impl Television {
area,
!matches!(self.mode, Mode::Channel),
self.config.ui.show_help_bar,
+ self.config.ui.show_preview_panel,
self.config.ui.input_bar_position,
- self.config.ui.preview_title_position,
);
// help bar (metadata, keymaps, logo)
@@ -426,7 +428,10 @@ impl Television {
self.results_area_height =
u32::from(layout.results.height.saturating_sub(2)); // 2 for the borders
- self.preview_pane_height = layout.preview_window.height;
+ self.preview_pane_height = match layout.preview_window {
+ Some(preview) => preview.height,
+ None => 0,
+ };
// results list
let result_count = self.channel.result_count();
@@ -447,6 +452,22 @@ impl Television {
self.config.ui.use_nerd_font_icons,
&mut self.icon_color_cache,
&self.colorscheme,
+ &self
+ .config
+ .keybindings
+ .get(&self.mode)
+ .unwrap()
+ .get(&Action::ToggleHelp)
+ .unwrap()
+ .to_string(),
+ &self
+ .config
+ .keybindings
+ .get(&self.mode)
+ .unwrap()
+ .get(&Action::TogglePreview)
+ .unwrap()
+ .to_string(),
)?;
// input box
@@ -463,38 +484,32 @@ impl Television {
&self.colorscheme,
)?;
- let selected_entry = self
- .get_selected_entry(Some(Mode::Channel))
- .unwrap_or(ENTRY_PLACEHOLDER);
- let preview = self.previewer.preview(&selected_entry);
+ if self.config.ui.show_preview_panel {
+ let selected_entry = self
+ .get_selected_entry(Some(Mode::Channel))
+ .unwrap_or(ENTRY_PLACEHOLDER);
- // preview title
- self.current_preview_total_lines = preview.total_lines();
- draw_preview_title_block(
- f,
- layout.preview_title,
- &preview,
- self.config.ui.use_nerd_font_icons,
- &self.colorscheme,
- )?;
-
- // preview content
- // initialize preview scroll
- self.maybe_init_preview_scroll(
- selected_entry
- .line_number
- .map(|l| u16::try_from(l).unwrap_or(0)),
- layout.preview_window.height,
- );
- draw_preview_content_block(
- f,
- layout.preview_window,
- &selected_entry,
- &preview,
- &self.rendered_preview_cache,
- self.preview_scroll.unwrap_or(0),
- &self.colorscheme,
- );
+ // preview content
+ let preview = self.previewer.preview(&selected_entry);
+ self.current_preview_total_lines = preview.total_lines();
+ // initialize preview scroll
+ self.maybe_init_preview_scroll(
+ selected_entry
+ .line_number
+ .map(|l| u16::try_from(l).unwrap_or(0)),
+ layout.preview_window.unwrap().height,
+ );
+ draw_preview_content_block(
+ f,
+ layout.preview_window.unwrap(),
+ &selected_entry,
+ &preview,
+ &self.rendered_preview_cache,
+ self.preview_scroll.unwrap_or(0),
+ self.config.ui.use_nerd_font_icons,
+ &self.colorscheme,
+ )?;
+ }
// remote control
if matches!(self.mode, Mode::RemoteControl | Mode::SendToChannel) {