From 09c45b5f635e19a5b7f3f813a2f6ac90b106a287 Mon Sep 17 00:00:00 2001 From: Alexandre Pasmantier Date: Thu, 1 May 2025 17:19:29 +0200 Subject: [PATCH] refactor(preview): simplify channel previews and remove intermediate struct --- benches/main/ui.rs | 22 +++++---- television/app.rs | 5 +- television/channels/cable.rs | 46 ++++++----------- television/channels/cable/preview.rs | 34 ------------- television/channels/cable/prototypes.rs | 7 +++ television/channels/entry.rs | 43 ++-------------- television/channels/mod.rs | 1 + television/channels/preview.rs | 63 ++++++++++++++++++++++++ television/channels/remote_control.rs | 2 +- television/channels/stdin.rs | 2 +- television/cli/mod.rs | 31 +++++------- television/main.rs | 32 ++++++------ television/preview/mod.rs | 2 +- television/preview/previewers/command.rs | 10 ++-- television/screen/results.rs | 2 +- television/television.rs | 5 +- 16 files changed, 145 insertions(+), 162 deletions(-) delete mode 100644 television/channels/cable/preview.rs create mode 100644 television/channels/preview.rs diff --git a/benches/main/ui.rs b/benches/main/ui.rs index 8ef118b..5be7f17 100644 --- a/benches/main/ui.rs +++ b/benches/main/ui.rs @@ -8,16 +8,18 @@ use ratatui::prelude::{Line, Style}; use ratatui::style::Color; use ratatui::widgets::{Block, BorderType, Borders, ListDirection, Padding}; use ratatui::Terminal; -use television::action::Action; -use television::channels::cable::prototypes::CableChannelPrototype; -use television::channels::entry::into_ranges; -use television::channels::entry::{Entry, PreviewType}; -use television::channels::OnAir; -use television::channels::TelevisionChannel; -use television::config::{Config, ConfigEnv}; -use television::screen::colors::ResultsColorscheme; -use television::screen::results::build_results_list; -use television::television::Television; +use television::{ + action::Action, + channels::{ + cable::prototypes::CableChannelPrototype, + entry::{into_ranges, Entry}, + preview::PreviewType, + OnAir, TelevisionChannel, + }, + config::{Config, ConfigEnv}, + screen::{colors::ResultsColorscheme, results::build_results_list}, + television::Television, +}; use tokio::runtime::Runtime; pub fn draw_results_list(c: &mut Criterion) { diff --git a/television/app.rs b/television/app.rs index 2a0119a..b7554c2 100644 --- a/television/app.rs +++ b/television/app.rs @@ -4,8 +4,9 @@ use anyhow::Result; use tokio::sync::mpsc; use tracing::{debug, trace}; -use crate::channels::entry::{Entry, PreviewType}; -use crate::channels::{OnAir, TelevisionChannel}; +use crate::channels::{ + entry::Entry, preview::PreviewType, OnAir, TelevisionChannel, +}; use crate::config::{default_tick_rate, Config}; use crate::keymap::Keymap; use crate::render::UiState; diff --git a/television/channels/cable.rs b/television/channels/cable.rs index 4479098..a90e9b1 100644 --- a/television/channels/cable.rs +++ b/television/channels/cable.rs @@ -2,18 +2,20 @@ use std::collections::HashSet; use std::io::{BufRead, BufReader}; use std::process::Stdio; -use preview::{parse_preview_kind, PreviewKind}; use prototypes::{CableChannelPrototype, DEFAULT_DELIMITER}; use rustc_hash::{FxBuildHasher, FxHashSet}; use tracing::debug; -use crate::channels::entry::{Entry, PreviewCommand, PreviewType}; -use crate::channels::OnAir; +use crate::channels::preview::parse_preview_type; +use crate::channels::{ + entry::Entry, + preview::{PreviewCommand, PreviewType}, + OnAir, +}; use crate::matcher::Matcher; use crate::matcher::{config::Config, injector::Injector}; use crate::utils::command::shell_command; -pub mod preview; pub mod prototypes; #[allow(dead_code)] @@ -21,7 +23,7 @@ pub struct Channel { pub name: String, matcher: Matcher, entries_command: String, - preview_kind: PreviewKind, + preview_type: PreviewType, selected_entries: FxHashSet, crawl_handle: tokio::task::JoinHandle<()>, } @@ -72,17 +74,17 @@ impl Channel { )); let preview_kind = match preview_command { Some(command) => { - parse_preview_kind(&command).unwrap_or_else(|_| { + parse_preview_type(&command).unwrap_or_else(|_| { panic!("Invalid preview command: {command}") }) } - None => PreviewKind::None, + None => PreviewType::None, }; debug!("Preview kind: {:?}", preview_kind); Self { matcher, entries_command: entries_command.to_string(), - preview_kind, + preview_type: preview_kind, name: name.to_string(), selected_entries: HashSet::with_hasher(FxBuildHasher), crawl_handle, @@ -147,19 +149,8 @@ impl OnAir for Channel { .into_iter() .map(|item| { let path = item.matched_string; - Entry::new( - path, - match &self.preview_kind { - PreviewKind::Command(ref preview_command) => { - PreviewType::Command(preview_command.clone()) - } - PreviewKind::Builtin(preview_type) => { - preview_type.clone() - } - PreviewKind::None => PreviewType::None, - }, - ) - .with_name_match_indices(&item.match_indices) + Entry::new(path, self.preview_type.clone()) + .with_name_match_indices(&item.match_indices) }) .collect() } @@ -167,16 +158,7 @@ impl OnAir for Channel { fn get_result(&self, index: u32) -> Option { self.matcher.get_result(index).map(|item| { let path = item.matched_string; - Entry::new( - path, - match &self.preview_kind { - PreviewKind::Command(ref preview_command) => { - PreviewType::Command(preview_command.clone()) - } - PreviewKind::Builtin(preview_type) => preview_type.clone(), - PreviewKind::None => PreviewType::None, - }, - ) + Entry::new(path, self.preview_type.clone()) }) } @@ -207,6 +189,6 @@ impl OnAir for Channel { fn shutdown(&self) {} fn supports_preview(&self) -> bool { - self.preview_kind != PreviewKind::None + self.preview_type != PreviewType::None } } diff --git a/television/channels/cable/preview.rs b/television/channels/cable/preview.rs deleted file mode 100644 index fb897c3..0000000 --- a/television/channels/cable/preview.rs +++ /dev/null @@ -1,34 +0,0 @@ -use anyhow::Result; -use regex::Regex; -use tracing::debug; - -use crate::channels::entry::{PreviewCommand, PreviewType}; - -#[derive(Debug, Clone, PartialEq)] -pub enum PreviewKind { - Command(PreviewCommand), - Builtin(PreviewType), - None, -} - -/// Parses the preview command to determine if it is a built-in (i.e. ":files:") or custom command. -/// -/// # Example: -/// ``` -/// use television::channels::entry::{PreviewCommand, PreviewType}; -/// use television::channels::cable::preview::{parse_preview_kind, PreviewKind}; -/// -/// let command = PreviewCommand::new("cat {0}", ":"); -/// let preview_kind = parse_preview_kind(&command).unwrap(); -/// assert_eq!(preview_kind, PreviewKind::Command(command)); -/// ``` -pub fn parse_preview_kind(command: &PreviewCommand) -> Result { - debug!("Parsing preview kind for command: {:?}", command); - let re = Regex::new(r"^\:(\w+)\:$").unwrap(); - if let Some(captures) = re.captures(&command.command) { - let preview_type = PreviewType::try_from(&captures[1])?; - Ok(PreviewKind::Builtin(preview_type)) - } else { - Ok(PreviewKind::Command(command.clone())) - } -} diff --git a/television/channels/cable/prototypes.rs b/television/channels/cable/prototypes.rs index fb3eb6d..e5f139b 100644 --- a/television/channels/cable/prototypes.rs +++ b/television/channels/cable/prototypes.rs @@ -25,6 +25,9 @@ use crate::cable::SerializedChannelPrototypes; /// - `preview_delimiter`: The delimiter to use to split an entry into /// multiple parts that can then be referenced in the preview command (e.g. /// `{1} + {2}`). +/// - `preview_offset`: a litteral expression that will be interpreted later on +/// in order to determine the vertical offset at which the preview should be +/// displayed. /// /// # Example /// The default files channel might look something like this: @@ -43,6 +46,7 @@ pub struct CableChannelPrototype { pub preview_command: Option, #[serde(default = "default_delimiter")] pub preview_delimiter: Option, + pub preview_offset: Option, } impl CableChannelPrototype { @@ -52,6 +56,7 @@ impl CableChannelPrototype { interactive: bool, preview_command: Option, preview_delimiter: Option, + preview_offset: Option, ) -> Self { Self { name: name.to_string(), @@ -59,6 +64,7 @@ impl CableChannelPrototype { interactive, preview_command, preview_delimiter, + preview_offset, } } } @@ -76,6 +82,7 @@ impl Default for CableChannelPrototype { interactive: false, preview_command: Some(DEFAULT_PREVIEW_COMMAND.to_string()), preview_delimiter: Some(DEFAULT_DELIMITER.to_string()), + preview_offset: None, } } } diff --git a/television/channels/entry.rs b/television/channels/entry.rs index da253be..9f95ad8 100644 --- a/television/channels/entry.rs +++ b/television/channels/entry.rs @@ -1,10 +1,8 @@ -use std::{ - fmt::Display, - hash::{Hash, Hasher}, -}; +use std::hash::{Hash, Hasher}; use devicons::FileIcon; -use strum::EnumString; + +use crate::channels::preview::PreviewType; // NOTE: having an enum for entry types would be nice since it would allow // having a nicer implementation for transitions between channels. This would @@ -87,7 +85,7 @@ impl Entry { /// /// Additional fields can be set using the builder pattern. /// ``` - /// use television::channels::entry::{Entry, PreviewType}; + /// use television::channels::{entry::Entry, preview::PreviewType}; /// use devicons::FileIcon; /// /// let entry = Entry::new("name".to_string(), PreviewType::EnvVar) @@ -161,39 +159,6 @@ pub const ENTRY_PLACEHOLDER: Entry = Entry { preview_type: PreviewType::EnvVar, }; -#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] -pub struct PreviewCommand { - pub command: String, - pub delimiter: String, -} - -impl PreviewCommand { - pub fn new(command: &str, delimiter: &str) -> Self { - Self { - command: command.to_string(), - delimiter: delimiter.to_string(), - } - } -} - -impl Display for PreviewCommand { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash, Default, EnumString)] -#[strum(serialize_all = "snake_case")] -pub enum PreviewType { - Basic, - EnvVar, - Files, - #[strum(disabled)] - Command(PreviewCommand), - #[default] - None, -} - #[cfg(test)] mod tests { use super::*; diff --git a/television/channels/mod.rs b/television/channels/mod.rs index 0604278..7eec13f 100644 --- a/television/channels/mod.rs +++ b/television/channels/mod.rs @@ -5,6 +5,7 @@ use television_derive::Broadcast; pub mod cable; pub mod entry; +pub mod preview; pub mod remote_control; pub mod stdin; diff --git a/television/channels/preview.rs b/television/channels/preview.rs new file mode 100644 index 0000000..bc1c303 --- /dev/null +++ b/television/channels/preview.rs @@ -0,0 +1,63 @@ +use std::fmt::Display; + +use anyhow::Result; +use regex::Regex; +use strum::EnumString; +use tracing::debug; + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] +pub struct PreviewCommand { + pub command: String, + pub delimiter: String, +} + +impl PreviewCommand { + pub fn new(command: &str, delimiter: &str) -> Self { + Self { + command: command.to_string(), + delimiter: delimiter.to_string(), + } + } +} + +impl Display for PreviewCommand { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Default, EnumString)] +#[strum(serialize_all = "snake_case")] +pub enum PreviewType { + Basic, + EnvVar, + Files, + #[strum(disabled)] + Command(PreviewCommand), + #[default] + None, +} + +/// Parses the preview command to determine the preview type. +/// +/// This checks if the command matches the builtin pattern `:{preview_type}:` +/// and then falls back to the command type if it doesn't. +/// +/// # Example: +/// ``` +/// use television::channels::preview::{parse_preview_type, PreviewCommand, PreviewType}; +/// +/// let command = PreviewCommand::new("cat {0}", ":"); +/// let preview_type = parse_preview_type(&command).unwrap(); +/// assert_eq!(preview_type, PreviewType::Command(command)); +/// ``` +pub fn parse_preview_type(command: &PreviewCommand) -> Result { + debug!("Parsing preview kind for command: {:?}", command); + let re = Regex::new(r"^\:(\w+)\:$").unwrap(); + if let Some(captures) = re.captures(&command.command) { + let preview_type = PreviewType::try_from(&captures[1])?; + Ok(preview_type) + } else { + Ok(PreviewType::Command(command.clone())) + } +} diff --git a/television/channels/remote_control.rs b/television/channels/remote_control.rs index c91cfd1..01cdd4b 100644 --- a/television/channels/remote_control.rs +++ b/television/channels/remote_control.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use crate::channels::cable::prototypes::CableChannels; -use crate::channels::entry::{Entry, PreviewType}; +use crate::channels::{entry::Entry, preview::PreviewType}; use crate::channels::{OnAir, TelevisionChannel}; use crate::matcher::{config::Config, Matcher}; use anyhow::Result; diff --git a/television/channels/stdin.rs b/television/channels/stdin.rs index cecde34..cf97b9b 100644 --- a/television/channels/stdin.rs +++ b/television/channels/stdin.rs @@ -8,7 +8,7 @@ use rustc_hash::{FxBuildHasher, FxHashSet}; use tracing::debug; use super::OnAir; -use crate::channels::entry::{Entry, PreviewType}; +use crate::channels::{entry::Entry, preview::PreviewType}; use crate::matcher::{config::Config, injector::Injector, Matcher}; pub struct Channel { diff --git a/television/cli/mod.rs b/television/cli/mod.rs index 7e8cbe6..d4ee1b2 100644 --- a/television/cli/mod.rs +++ b/television/cli/mod.rs @@ -4,12 +4,11 @@ use std::path::Path; use anyhow::{anyhow, Result}; use tracing::debug; -use crate::channels::cable::{ - preview::parse_preview_kind, preview::PreviewKind, - prototypes::CableChannels, +use crate::channels::cable::prototypes::{ + CableChannelPrototype, CableChannels, }; -use crate::channels::{ - cable::prototypes::CableChannelPrototype, entry::PreviewCommand, +use crate::channels::preview::{ + parse_preview_type, PreviewCommand, PreviewType, }; use crate::cli::args::{Cli, Command}; use crate::config::{KeyBindings, DEFAULT_CHANNEL}; @@ -24,7 +23,7 @@ pub mod args; #[derive(Debug, Clone)] pub struct PostProcessedCli { pub channel: CableChannelPrototype, - pub preview_kind: PreviewKind, + pub preview_kind: PreviewType, pub no_preview: bool, pub tick_rate: Option, pub frame_rate: Option, @@ -44,7 +43,7 @@ impl Default for PostProcessedCli { fn default() -> Self { Self { channel: CableChannelPrototype::default(), - preview_kind: PreviewKind::None, + preview_kind: PreviewType::None, no_preview: false, tick_rate: None, frame_rate: None, @@ -80,8 +79,8 @@ impl From for PostProcessedCli { command: preview, delimiter: cli.delimiter.clone(), }) - .map_or(PreviewKind::None, |preview_command| { - parse_preview_kind(&preview_command) + .map_or(PreviewType::None, |preview_command| { + parse_preview_type(&preview_command) .map_err(|e| { cli_parsing_error_exit(&e.to_string()); }) @@ -302,7 +301,7 @@ Data directory: {data_dir_path}" #[cfg(test)] mod tests { use crate::{ - action::Action, channels::entry::PreviewType, config::Binding, + action::Action, channels::preview::PreviewType, config::Binding, event::Key, }; @@ -327,7 +326,7 @@ mod tests { ); assert_eq!( post_processed_cli.preview_kind, - PreviewKind::Command(PreviewCommand { + PreviewType::Command(PreviewCommand { command: "bat -n --color=always {}".to_string(), delimiter: ":".to_string() }) @@ -373,10 +372,7 @@ mod tests { let post_processed_cli: PostProcessedCli = cli.into(); - assert_eq!( - post_processed_cli.preview_kind, - PreviewKind::Builtin(PreviewType::Files) - ); + assert_eq!(post_processed_cli.preview_kind, PreviewType::Files); } #[test] @@ -390,10 +386,7 @@ mod tests { let post_processed_cli: PostProcessedCli = cli.into(); - assert_eq!( - post_processed_cli.preview_kind, - PreviewKind::Builtin(PreviewType::EnvVar) - ); + assert_eq!(post_processed_cli.preview_kind, PreviewType::EnvVar); } #[test] diff --git a/television/main.rs b/television/main.rs index 8bbf3d9..1d011ed 100644 --- a/television/main.rs +++ b/television/main.rs @@ -7,15 +7,13 @@ use anyhow::Result; use clap::Parser; use crossterm::terminal::enable_raw_mode; use television::cable; -use television::channels::cable::{ - preview::PreviewKind, prototypes::CableChannels, -}; +use television::channels::cable::prototypes::CableChannels; use television::utils::clipboard::CLIPBOARD; use tracing::{debug, error, info}; use television::app::{App, AppOptions}; use television::channels::{ - entry::PreviewType, stdin::Channel as StdinChannel, TelevisionChannel, + stdin::Channel as StdinChannel, TelevisionChannel, }; use television::cli::{ args::{Cli, Command}, @@ -163,13 +161,7 @@ pub fn determine_channel( if readable_stdin { debug!("Using stdin channel"); Ok(TelevisionChannel::Stdin(StdinChannel::new( - match &args.preview_kind { - PreviewKind::Command(ref preview_command) => { - PreviewType::Command(preview_command.clone()) - } - PreviewKind::Builtin(preview_type) => preview_type.clone(), - PreviewKind::None => PreviewType::None, - }, + args.preview_kind, ))) } else if let Some(prompt) = args.autocomplete_prompt { debug!("Using autocomplete prompt: {:?}", prompt); @@ -192,7 +184,9 @@ mod tests { use rustc_hash::FxHashMap; use television::{ cable::load_cable_channels, - channels::cable::prototypes::CableChannelPrototype, + channels::{ + cable::prototypes::CableChannelPrototype, preview::PreviewType, + }, }; use super::*; @@ -241,8 +235,10 @@ mod tests { async fn test_determine_channel_autocomplete_prompt() { let autocomplete_prompt = Some("cd".to_string()); let expected_channel = TelevisionChannel::Cable( - CableChannelPrototype::new("dirs", "ls {}", false, None, None) - .into(), + CableChannelPrototype::new( + "dirs", "ls {}", false, None, None, None, + ) + .into(), ); let args = PostProcessedCli { autocomplete_prompt, @@ -276,7 +272,7 @@ mod tests { #[tokio::test] async fn test_determine_channel_standard_case() { let channel = - CableChannelPrototype::new("dirs", "", false, None, None); + CableChannelPrototype::new("dirs", "", false, None, None, None); let args = PostProcessedCli { channel, ..Default::default() @@ -287,8 +283,10 @@ mod tests { &config, false, &TelevisionChannel::Cable( - CableChannelPrototype::new("dirs", "", false, None, None) - .into(), + CableChannelPrototype::new( + "dirs", "", false, None, None, None, + ) + .into(), ), None, ); diff --git a/television/preview/mod.rs b/television/preview/mod.rs index c2571ff..9b66517 100644 --- a/television/preview/mod.rs +++ b/television/preview/mod.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::channels::entry::{Entry, PreviewType}; +use crate::channels::{entry::Entry, preview::PreviewType}; use devicons::FileIcon; use ratatui::layout::Rect; diff --git a/television/preview/previewers/command.rs b/television/preview/previewers/command.rs index 61894b2..d67c99f 100644 --- a/television/preview/previewers/command.rs +++ b/television/preview/previewers/command.rs @@ -1,7 +1,9 @@ -use crate::channels::entry::{Entry, PreviewCommand}; -use crate::preview::cache::PreviewCache; use crate::preview::{Preview, PreviewContent}; use crate::utils::command::shell_command; +use crate::{ + channels::{entry::Entry, preview::PreviewCommand}, + preview::cache::PreviewCache, +}; use parking_lot::Mutex; use regex::Regex; use rustc_hash::FxHashSet; @@ -127,7 +129,7 @@ impl CommandPreviewer { /// /// # Example /// ``` -/// use television::channels::entry::{PreviewCommand, PreviewType, Entry}; +/// use television::channels::{preview::{PreviewCommand, PreviewType}, entry::Entry}; /// use television::preview::previewers::command::format_command; /// /// let command = PreviewCommand { @@ -209,7 +211,7 @@ pub fn try_preview( #[cfg(test)] mod tests { use super::*; - use crate::channels::entry::{Entry, PreviewType}; + use crate::channels::{entry::Entry, preview::PreviewType}; #[test] fn test_format_command() { diff --git a/television/screen/results.rs b/television/screen/results.rs index 4ab92a1..c7847eb 100644 --- a/television/screen/results.rs +++ b/television/screen/results.rs @@ -294,7 +294,7 @@ pub fn draw_results_list( #[cfg(test)] mod tests { - use crate::channels::entry::PreviewType; + use crate::channels::preview::PreviewType; use super::*; diff --git a/television/television.rs b/television/television.rs index 7aa3356..0bac525 100644 --- a/television/television.rs +++ b/television/television.rs @@ -1,6 +1,9 @@ use crate::action::Action; use crate::cable::load_cable_channels; -use crate::channels::entry::{Entry, PreviewType, ENTRY_PLACEHOLDER}; +use crate::channels::{ + entry::{Entry, ENTRY_PLACEHOLDER}, + preview::PreviewType, +}; use crate::channels::{ remote_control::RemoteControl, OnAir, TelevisionChannel, };