diff --git a/Cargo.lock b/Cargo.lock index 25e1004..b42d79e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1424,7 +1424,6 @@ dependencies = [ "rustc-hash", "serde", "signal-hook", - "television-derive", "tempfile", "tokio", "toml", @@ -1434,15 +1433,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "television-derive" -version = "0.0.27" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tempfile" version = "3.19.1" diff --git a/Cargo.toml b/Cargo.toml index 6fc61f3..525df69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,8 +31,6 @@ build = "build.rs" path = "television/lib.rs" [dependencies] -television-derive = { path = "television-derive", version = "0.0.27" } - anyhow = "1.0" base64 = "0.22.1" directories = "6.0" diff --git a/benches/main/ui.rs b/benches/main/ui.rs index 73e081f..2d5ce7c 100644 --- a/benches/main/ui.rs +++ b/benches/main/ui.rs @@ -8,13 +8,12 @@ use ratatui::prelude::{Line, Style}; use ratatui::style::Color; use ratatui::widgets::{Block, BorderType, Borders, ListDirection, Padding}; use ratatui::Terminal; -use television::channels::cable::prototypes::CableChannelPrototypes; +use television::channels::cable::prototypes::Cable; use television::{ action::Action, channels::{ - cable::prototypes::CableChannelPrototype, + cable::prototypes::ChannelPrototype, entry::{into_ranges, Entry}, - OnAir, }, config::{Config, ConfigEnv}, screen::{colors::ResultsColorscheme, results::build_results_list}, @@ -22,6 +21,7 @@ use television::{ }; use tokio::runtime::Runtime; +#[allow(clippy::too_many_lines)] pub fn draw_results_list(c: &mut Criterion) { // FIXME: there's probably a way to have this as a benchmark asset // possible as a JSON file and to load it for the benchmark using Serde @@ -458,6 +458,7 @@ pub fn draw_results_list(c: &mut Criterion) { }); } +#[allow(clippy::missing_panics_doc)] pub fn draw(c: &mut Criterion) { let width = 250; let height = 80; @@ -472,7 +473,7 @@ pub fn draw(c: &mut Criterion) { let backend = TestBackend::new(width, height); let terminal = Terminal::new(backend).unwrap(); let (tx, _) = tokio::sync::mpsc::unbounded_channel(); - let channel = CableChannelPrototype::default(); + let channel = ChannelPrototype::default(); // Wait for the channel to finish loading let mut tv = Television::new( tx, @@ -482,7 +483,7 @@ pub fn draw(c: &mut Criterion) { false, false, false, - CableChannelPrototypes::default(), + Cable::default(), ); tv.find("television"); for _ in 0..5 { diff --git a/television-derive/Cargo.toml b/television-derive/Cargo.toml deleted file mode 100644 index afa6943..0000000 --- a/television-derive/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "television-derive" -version = "0.0.27" -edition = "2021" -description = "The revolution will be televised." -license = "MIT" -authors = ["Alexandre Pasmantier "] -repository = "https://github.com/alexpasmantier/television" -homepage = "https://github.com/alexpasmantier/television" -keywords = ["search", "fuzzy", "preview", "tui", "terminal"] -categories = [ - "command-line-utilities", - "command-line-interface", - "concurrency", - "development-tools", -] -rust-version = "1.83" - -[dependencies] -proc-macro2 = "1.0.93" -quote = "1.0.38" -syn = "2.0.96" - - -[lib] -proc-macro = true diff --git a/television-derive/src/lib.rs b/television-derive/src/lib.rs deleted file mode 100644 index 29af41f..0000000 --- a/television-derive/src/lib.rs +++ /dev/null @@ -1,166 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; - -/// This macro generates the `OnAir` trait implementation for the given enum. -/// -/// The `OnAir` trait is used to interact with the different television channels -/// and forwards the method calls to the corresponding channel variants. -/// -/// Example: -/// ```ignore -/// use television-derive::Broadcast; -/// use television::channels::{TelevisionChannel, OnAir}; -/// use television::channels::{files, text}; -/// -/// #[derive(Broadcast)] -/// enum TelevisionChannel { -/// Files(files::Channel), -/// Text(text::Channel), -/// } -/// -/// let mut channel = TelevisionChannel::Files(files::Channel::default()); -/// -/// // Use the `OnAir` trait methods directly on TelevisionChannel -/// channel.find("pattern"); -/// let results = channel.results(10, 0); -/// let result = channel.get_result(0); -/// let result_count = channel.result_count(); -/// let total_count = channel.total_count(); -/// let running = channel.running(); -/// channel.shutdown(); -/// ``` -#[proc_macro_derive(Broadcast)] -pub fn tv_channel_derive(input: TokenStream) -> TokenStream { - // Construct a representation of Rust code as a syntax tree - // that we can manipulate - let ast = syn::parse(input).unwrap(); - - // Build the trait implementation - impl_tv_channel(&ast) -} - -fn impl_tv_channel(ast: &syn::DeriveInput) -> TokenStream { - // Ensure the struct is an enum - let variants = if let syn::Data::Enum(data_enum) = &ast.data { - &data_enum.variants - } else { - panic!("#[derive(OnAir)] is only defined for enums"); - }; - - // Ensure the enum has at least one variant - assert!( - !variants.is_empty(), - "#[derive(OnAir)] requires at least one variant" - ); - - let enum_name = &ast.ident; - - let variant_names: Vec<_> = variants.iter().map(|v| &v.ident).collect(); - - // Generate the trait implementation for the TelevisionChannel trait - let trait_impl = quote! { - impl OnAir for #enum_name { - fn find(&mut self, pattern: &str) { - match self { - #( - #enum_name::#variant_names(ref mut channel) => { - channel.find(pattern); - } - )* - } - } - - fn results(&mut self, num_entries: u32, offset: u32) -> Vec { - match self { - #( - #enum_name::#variant_names(ref mut channel) => { - channel.results(num_entries, offset) - } - )* - } - } - - fn get_result(&self, index: u32) -> Option { - match self { - #( - #enum_name::#variant_names(ref channel) => { - channel.get_result(index) - } - )* - } - } - - fn selected_entries(&self) -> &FxHashSet { - match self { - #( - #enum_name::#variant_names(ref channel) => { - channel.selected_entries() - } - )* - } - } - - fn toggle_selection(&mut self, entry: &Entry) { - match self { - #( - #enum_name::#variant_names(ref mut channel) => { - channel.toggle_selection(entry) - } - )* - } - } - - fn result_count(&self) -> u32 { - match self { - #( - #enum_name::#variant_names(ref channel) => { - channel.result_count() - } - )* - } - } - - fn total_count(&self) -> u32 { - match self { - #( - #enum_name::#variant_names(ref channel) => { - channel.total_count() - } - )* - } - } - - fn running(&self) -> bool { - match self { - #( - #enum_name::#variant_names(ref channel) => { - channel.running() - } - )* - } - } - - fn shutdown(&self) { - match self { - #( - #enum_name::#variant_names(ref channel) => { - channel.shutdown() - } - )* - } - } - - fn supports_preview(&self) -> bool { - match self { - #( - #enum_name::#variant_names(ref channel) => { - channel.supports_preview() - } - )* - } - } - } - }; - - trait_impl.into() -} diff --git a/television/app.rs b/television/app.rs index e9f7def..f0fbee4 100644 --- a/television/app.rs +++ b/television/app.rs @@ -4,10 +4,8 @@ use anyhow::Result; use tokio::sync::mpsc; use tracing::{debug, trace}; -use crate::channels::cable::prototypes::{ - CableChannelPrototype, CableChannelPrototypes, -}; -use crate::channels::{entry::Entry, OnAir}; +use crate::channels::cable::prototypes::{Cable, ChannelPrototype}; +use crate::channels::entry::Entry; use crate::config::{default_tick_rate, Config}; use crate::keymap::Keymap; use crate::render::UiState; @@ -138,11 +136,11 @@ const ACTION_BUF_SIZE: usize = 8; impl App { pub fn new( - channel_prototype: CableChannelPrototype, + channel_prototype: ChannelPrototype, config: Config, input: Option, options: AppOptions, - cable_channels: &CableChannelPrototypes, + cable_channels: &Cable, ) -> Self { let (action_tx, action_rx) = mpsc::unbounded_channel(); let (render_tx, render_rx) = mpsc::unbounded_channel(); @@ -160,7 +158,7 @@ impl App { options.no_remote, options.no_help, options.exact, - CableChannelPrototypes((*cable_channels).clone()), + cable_channels.clone(), ); Self { diff --git a/television/cable.rs b/television/cable.rs index cb3de75..b615107 100644 --- a/television/cable.rs +++ b/television/cable.rs @@ -6,17 +6,15 @@ use anyhow::Result; use tracing::{debug, error}; use crate::{ - channels::cable::prototypes::{ - CableChannelPrototype, CableChannelPrototypes, - }, + channels::cable::prototypes::{Cable, ChannelPrototype}, config::get_config_dir, }; /// Just a proxy struct to deserialize prototypes #[derive(Debug, serde::Deserialize, Default)] -pub struct SerializedChannelPrototypes { +pub struct CableSpec { #[serde(rename = "cable_channel")] - pub prototypes: Vec, + pub prototypes: Vec, } const CABLE_FILE_NAME_SUFFIX: &str = "channels"; @@ -42,7 +40,7 @@ const DEFAULT_CABLE_CHANNELS: &str = /// ├── my_channels.toml /// └── windows_channels.toml /// ``` -pub fn load_cable_channels() -> Result { +pub fn load_cable() -> Result { let config_dir = get_config_dir(); // list all files in the config directory @@ -60,13 +58,13 @@ pub fn load_cable_channels() -> Result { } let default_prototypes = - toml::from_str::(DEFAULT_CABLE_CHANNELS) + toml::from_str::(DEFAULT_CABLE_CHANNELS) .expect("Failed to parse default cable channels"); let prototypes = file_paths.iter().fold( - Vec::::new(), + Vec::::new(), |mut acc, p| { - match toml::from_str::( + match toml::from_str::( &std::fs::read_to_string(p) .expect("Unable to read configuration file"), ) { @@ -97,7 +95,7 @@ pub fn load_cable_channels() -> Result { { cable_channels.insert(prototype.name.clone(), prototype); } - Ok(CableChannelPrototypes(cable_channels)) + Ok(Cable(cable_channels)) } fn is_cable_file_format

(p: P) -> bool diff --git a/television/channels/cable.rs b/television/channels/cable.rs index ddb50d0..4eeb1af 100644 --- a/television/channels/cable.rs +++ b/television/channels/cable.rs @@ -2,11 +2,11 @@ use std::collections::HashSet; use std::io::{BufRead, BufReader}; use std::process::Stdio; -use prototypes::{CableChannelPrototype, DEFAULT_DELIMITER}; +use prototypes::{ChannelPrototype, DEFAULT_DELIMITER}; use rustc_hash::{FxBuildHasher, FxHashSet}; use tracing::debug; -use crate::channels::{entry::Entry, preview::PreviewCommand, OnAir}; +use crate::channels::{entry::Entry, preview::PreviewCommand}; use crate::matcher::Matcher; use crate::matcher::{config::Config, injector::Injector}; use crate::utils::command::shell_command; @@ -34,8 +34,8 @@ impl Default for Channel { } } -impl From for Channel { - fn from(prototype: CableChannelPrototype) -> Self { +impl From for Channel { + fn from(prototype: ChannelPrototype) -> Self { Self::new( &prototype.name, &prototype.source_command, @@ -77,6 +77,59 @@ impl Channel { crawl_handle, } } + + pub fn find(&mut self, pattern: &str) { + self.matcher.find(pattern); + } + + pub fn results(&mut self, num_entries: u32, offset: u32) -> Vec { + self.matcher.tick(); + self.matcher + .results(num_entries, offset) + .into_iter() + .map(|item| { + let path = item.matched_string; + Entry::new(path).with_name_match_indices(&item.match_indices) + }) + .collect() + } + + pub fn get_result(&self, index: u32) -> Option { + self.matcher.get_result(index).map(|item| { + let path = item.matched_string; + Entry::new(path) + }) + } + + pub fn selected_entries(&self) -> &FxHashSet { + &self.selected_entries + } + + pub fn toggle_selection(&mut self, entry: &Entry) { + if self.selected_entries.contains(entry) { + self.selected_entries.remove(entry); + } else { + self.selected_entries.insert(entry.clone()); + } + } + + pub fn result_count(&self) -> u32 { + self.matcher.matched_item_count + } + + pub fn total_count(&self) -> u32 { + self.matcher.total_item_count + } + + pub fn running(&self) -> bool { + self.matcher.status.running || !self.crawl_handle.is_finished() + } + + pub fn shutdown(&self) {} + + pub fn supports_preview(&self) -> bool { + self.preview_command.is_some() + } } #[allow(clippy::unused_async)] @@ -123,58 +176,3 @@ async fn load_candidates( } let _ = child.wait(); } - -impl OnAir for Channel { - fn find(&mut self, pattern: &str) { - self.matcher.find(pattern); - } - - fn results(&mut self, num_entries: u32, offset: u32) -> Vec { - self.matcher.tick(); - self.matcher - .results(num_entries, offset) - .into_iter() - .map(|item| { - let path = item.matched_string; - Entry::new(path).with_name_match_indices(&item.match_indices) - }) - .collect() - } - - fn get_result(&self, index: u32) -> Option { - self.matcher.get_result(index).map(|item| { - let path = item.matched_string; - Entry::new(path) - }) - } - - fn selected_entries(&self) -> &FxHashSet { - &self.selected_entries - } - - fn toggle_selection(&mut self, entry: &Entry) { - if self.selected_entries.contains(entry) { - self.selected_entries.remove(entry); - } else { - self.selected_entries.insert(entry.clone()); - } - } - - fn result_count(&self) -> u32 { - self.matcher.matched_item_count - } - - fn total_count(&self) -> u32 { - self.matcher.total_item_count - } - - fn running(&self) -> bool { - self.matcher.status.running || !self.crawl_handle.is_finished() - } - - fn shutdown(&self) {} - - fn supports_preview(&self) -> bool { - self.preview_command.is_some() - } -} diff --git a/television/channels/cable/prototypes.rs b/television/channels/cable/prototypes.rs index b3bd108..2568626 100644 --- a/television/channels/cable/prototypes.rs +++ b/television/channels/cable/prototypes.rs @@ -4,9 +4,7 @@ use std::{ ops::Deref, }; -use crate::{ - cable::SerializedChannelPrototypes, channels::preview::PreviewCommand, -}; +use crate::{cable::CableSpec, channels::preview::PreviewCommand}; /// A prototype for a cable channel. /// @@ -40,7 +38,7 @@ use crate::{ /// preview_command = "cat {}" /// ``` #[derive(Clone, Debug, serde::Deserialize, PartialEq)] -pub struct CableChannelPrototype { +pub struct ChannelPrototype { pub name: String, pub source_command: String, #[serde(default)] @@ -54,7 +52,7 @@ pub struct CableChannelPrototype { const STDIN_CHANNEL_NAME: &str = "stdin"; const STDIN_SOURCE_COMMAND: &str = "cat"; -impl CableChannelPrototype { +impl ChannelPrototype { pub fn new( name: &str, source_command: &str, @@ -102,9 +100,9 @@ impl CableChannelPrototype { const DEFAULT_PROTOTYPE_NAME: &str = "files"; pub const DEFAULT_DELIMITER: &str = " "; -impl Default for CableChannelPrototype { +impl Default for ChannelPrototype { fn default() -> Self { - CableChannelPrototypes::default() + Cable::default() .get(DEFAULT_PROTOTYPE_NAME) .cloned() .unwrap() @@ -118,7 +116,7 @@ fn default_delimiter() -> Option { Some(DEFAULT_DELIMITER.to_string()) } -impl Display for CableChannelPrototype { +impl Display for ChannelPrototype { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}", self.name) } @@ -129,13 +127,11 @@ impl Display for CableChannelPrototype { /// This is used to store cable channel prototypes throughout the application /// in a way that facilitates answering questions like "what's the prototype /// for `files`?" or "does this channel exist?". -#[derive(Debug, serde::Deserialize)] -pub struct CableChannelPrototypes( - pub FxHashMap, -); +#[derive(Debug, serde::Deserialize, Clone)] +pub struct Cable(pub FxHashMap); -impl Deref for CableChannelPrototypes { - type Target = FxHashMap; +impl Deref for Cable { + type Target = FxHashMap; fn deref(&self) -> &Self::Target { &self.0 @@ -153,18 +149,16 @@ const DEFAULT_CABLE_CHANNELS_FILE: &str = const DEFAULT_CABLE_CHANNELS_FILE: &str = include_str!("../../../cable/windows-channels.toml"); -impl Default for CableChannelPrototypes { +impl Default for Cable { /// Fallback to the default cable channels specification (the template file /// included in the repo). fn default() -> Self { - let s = toml::from_str::( - DEFAULT_CABLE_CHANNELS_FILE, - ) - .expect("Unable to parse default cable channels"); + let s = toml::from_str::(DEFAULT_CABLE_CHANNELS_FILE) + .expect("Unable to parse default cable channels"); let mut prototypes = FxHashMap::default(); for prototype in s.prototypes { prototypes.insert(prototype.name.clone(), prototype); } - CableChannelPrototypes(prototypes) + Cable(prototypes) } } diff --git a/television/channels/mod.rs b/television/channels/mod.rs index a49887a..48981b8 100644 --- a/television/channels/mod.rs +++ b/television/channels/mod.rs @@ -1,144 +1,4 @@ -use crate::channels::entry::Entry; -use anyhow::Result; -use cable::prototypes::CableChannelPrototype; -use rustc_hash::FxHashSet; -use television_derive::Broadcast; - pub mod cable; pub mod entry; pub mod preview; pub mod remote_control; - -/// The interface that all television channels must implement. -/// -/// # Note -/// The `OnAir` trait requires the `Send` trait to be implemented as well. -/// This is necessary to allow the channels to be used with the tokio -/// runtime, which requires `Send` in order to be able to send tasks between -/// worker threads safely. -/// -/// # Methods -/// - `find`: Find entries that match the given pattern. This method does not -/// return anything and instead typically stores the results internally for -/// later retrieval allowing to perform the search in the background while -/// incrementally polling the results. -/// ```ignore -/// fn find(&mut self, pattern: &str); -/// ``` -/// - `results`: Get the results of the search (at a given point in time, see -/// above). This method returns a specific portion of entries that match the -/// search pattern. The `num_entries` parameter specifies the number of -/// entries to return and the `offset` parameter specifies the starting index -/// of the entries to return. -/// ```ignore -/// fn results(&mut self, num_entries: u32, offset: u32) -> Vec; -/// ``` -/// - `get_result`: Get a specific result by its index. -/// ```ignore -/// fn get_result(&self, index: u32) -> Option; -/// ``` -/// - `result_count`: Get the number of results currently available. -/// ```ignore -/// fn result_count(&self) -> u32; -/// ``` -/// - `total_count`: Get the total number of entries currently available (e.g. -/// the haystack). -/// ```ignore -/// fn total_count(&self) -> u32; -/// ``` -/// -pub trait OnAir: Send { - /// Find entries that match the given pattern. - /// - /// This method does not return anything and instead typically stores the - /// results internally for later retrieval allowing to perform the search - /// in the background while incrementally polling the results with - /// `results`. - fn find(&mut self, pattern: &str); - - /// Get the results of the search (that are currently available). - fn results(&mut self, num_entries: u32, offset: u32) -> Vec; - - /// Get a specific result by its index. - fn get_result(&self, index: u32) -> Option; - - /// Get the currently selected entries. - fn selected_entries(&self) -> &FxHashSet; - - /// Toggles selection for the entry under the cursor. - fn toggle_selection(&mut self, entry: &Entry); - - /// Get the number of results currently available. - fn result_count(&self) -> u32; - - /// Get the total number of entries currently available. - fn total_count(&self) -> u32; - - /// Check if the channel is currently running. - fn running(&self) -> bool; - - /// Turn off - fn shutdown(&self); - - /// Whether this channel supports previewing entries. - fn supports_preview(&self) -> bool; -} - -/// The available television channels. -/// -/// Each channel is represented by a variant of the enum and should implement -/// the `OnAir` trait. -/// -/// # Important -/// When adding a new channel, make sure to add a new variant to this enum and -/// implement the `OnAir` trait for it. -/// -/// # Derive -/// ## `CliChannel` -/// The `CliChannel` derive macro generates the necessary glue code to -/// automatically create the corresponding `CliTvChannel` enum with unit -/// variants that can be used to select the channel from the command line. -/// It also generates the necessary glue code to automatically create a channel -/// instance from the selected CLI enum variant. -/// -/// ## `Broadcast` -/// The `Broadcast` derive macro generates the necessary glue code to -/// automatically forward method calls to the corresponding channel variant. -/// This allows to use the `OnAir` trait methods directly on the `TelevisionChannel` -/// enum. In a more straightforward way, it implements the `OnAir` trait for the -/// `TelevisionChannel` enum. -/// -/// ## `UnitChannel` -/// This macro generates an enum with unit variants that can be used instead -/// of carrying the actual channel instances around. It also generates the necessary -/// glue code to automatically create a channel instance from the selected enum variant. -#[allow(dead_code, clippy::module_name_repetitions)] -#[derive(Broadcast)] -pub enum TelevisionChannel { - /// The remote control channel. - /// - /// This channel allows to switch between different channels. - RemoteControl(remote_control::RemoteControl), - /// A custom channel. - /// - /// This channel allows to search through custom data. - Cable(cable::Channel), -} - -impl TelevisionChannel { - pub fn zap(&self, channel_name: &str) -> Result { - match self { - TelevisionChannel::RemoteControl(remote_control) => { - remote_control.zap(channel_name) - } - TelevisionChannel::Cable(_) => unreachable!(), - } - } - - pub fn name(&self) -> String { - match self { - TelevisionChannel::Cable(channel) => channel.name.clone(), - TelevisionChannel::RemoteControl(_) => String::from("Remote"), - } - } -} diff --git a/television/channels/preview.rs b/television/channels/preview.rs index 70b4fe9..5b270c9 100644 --- a/television/channels/preview.rs +++ b/television/channels/preview.rs @@ -1,7 +1,7 @@ use std::fmt::Display; use super::{cable::prototypes::DEFAULT_DELIMITER, entry::Entry}; -use crate::channels::cable::prototypes::CableChannelPrototype; +use crate::channels::cable::prototypes::ChannelPrototype; use lazy_regex::{regex, Lazy, Regex}; use tracing::debug; @@ -65,8 +65,8 @@ impl PreviewCommand { } } -impl From<&CableChannelPrototype> for Option { - fn from(value: &CableChannelPrototype) -> Self { +impl From<&ChannelPrototype> for Option { + fn from(value: &ChannelPrototype) -> Self { if let Some(command) = value.preview_command.as_ref() { let delimiter = value .preview_delimiter diff --git a/television/channels/remote_control.rs b/television/channels/remote_control.rs index 5cf95f0..0bcbeb7 100644 --- a/television/channels/remote_control.rs +++ b/television/channels/remote_control.rs @@ -1,32 +1,23 @@ -use std::collections::HashSet; - -use crate::channels::cable::prototypes::CableChannelPrototypes; +use crate::channels::cable::prototypes::Cable; use crate::channels::entry::Entry; -use crate::channels::OnAir; use crate::matcher::{config::Config, Matcher}; use anyhow::Result; use devicons::FileIcon; -use rustc_hash::{FxBuildHasher, FxHashSet}; -use super::cable::prototypes::CableChannelPrototype; +use super::cable::prototypes::ChannelPrototype; pub struct RemoteControl { matcher: Matcher, - cable_channels: Option, - selected_entries: FxHashSet, + cable_channels: Option, } const NUM_THREADS: usize = 1; impl RemoteControl { - pub fn new(cable_channels: Option) -> Self { + pub fn new(cable_channels: Option) -> Self { let matcher = Matcher::new(Config::default().n_threads(NUM_THREADS)); let injector = matcher.injector(); - for c in cable_channels - .as_ref() - .unwrap_or(&CableChannelPrototypes::default()) - .keys() - { + for c in cable_channels.as_ref().unwrap_or(&Cable::default()).keys() { let () = injector.push(c.clone(), |e, cols| { cols[0] = e.to_string().into(); }); @@ -34,11 +25,10 @@ impl RemoteControl { RemoteControl { matcher, cable_channels, - selected_entries: HashSet::with_hasher(FxBuildHasher), } } - pub fn zap(&self, channel_name: &str) -> Result { + pub fn zap(&self, channel_name: &str) -> Result { match self .cable_channels .as_ref() @@ -69,12 +59,12 @@ const CABLE_ICON: FileIcon = FileIcon { color: "#000000", }; -impl OnAir for RemoteControl { - fn find(&mut self, pattern: &str) { +impl RemoteControl { + pub fn find(&mut self, pattern: &str) { self.matcher.find(pattern); } - fn results(&mut self, num_entries: u32, offset: u32) -> Vec { + pub fn results(&mut self, num_entries: u32, offset: u32) -> Vec { self.matcher.tick(); self.matcher .results(num_entries, offset) @@ -88,35 +78,28 @@ impl OnAir for RemoteControl { .collect() } - fn get_result(&self, index: u32) -> Option { + pub fn get_result(&self, index: u32) -> Option { self.matcher.get_result(index).map(|item| { let path = item.matched_string; Entry::new(path).with_icon(TV_ICON) }) } - fn selected_entries(&self) -> &FxHashSet { - &self.selected_entries - } - - #[allow(unused_variables)] - fn toggle_selection(&mut self, entry: &Entry) {} - - fn result_count(&self) -> u32 { + pub fn result_count(&self) -> u32 { self.matcher.matched_item_count } - fn total_count(&self) -> u32 { + pub fn total_count(&self) -> u32 { self.matcher.total_item_count } - fn running(&self) -> bool { + pub fn running(&self) -> bool { self.matcher.status.running } - fn shutdown(&self) {} + pub fn shutdown(&self) {} - fn supports_preview(&self) -> bool { + pub fn supports_preview(&self) -> bool { false } } diff --git a/television/cli/mod.rs b/television/cli/mod.rs index 5c5ae82..8307d14 100644 --- a/television/cli/mod.rs +++ b/television/cli/mod.rs @@ -4,9 +4,7 @@ use std::path::Path; use anyhow::{anyhow, Result}; use tracing::debug; -use crate::channels::cable::prototypes::{ - CableChannelPrototype, CableChannelPrototypes, -}; +use crate::channels::cable::prototypes::{Cable, ChannelPrototype}; use crate::channels::preview::PreviewCommand; use crate::cli::args::{Cli, Command}; use crate::config::{KeyBindings, DEFAULT_CHANNEL}; @@ -20,7 +18,7 @@ pub mod args; #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub struct PostProcessedCli { - pub channel: CableChannelPrototype, + pub channel: ChannelPrototype, pub preview_command: Option, pub no_preview: bool, pub tick_rate: Option, @@ -41,7 +39,7 @@ pub struct PostProcessedCli { impl Default for PostProcessedCli { fn default() -> Self { Self { - channel: CableChannelPrototype::default(), + channel: ChannelPrototype::default(), preview_command: None, no_preview: false, tick_rate: None, @@ -80,10 +78,10 @@ impl From for PostProcessedCli { offset_expr: None, }); - let mut channel: CableChannelPrototype; + let mut channel: ChannelPrototype; let working_directory: Option; - let cable_channels = cable::load_cable_channels().unwrap_or_default(); + let cable_channels = cable::load_cable().unwrap_or_default(); if cli.channel.is_none() { channel = cable_channels .get(DEFAULT_CHANNEL) @@ -178,8 +176,8 @@ fn parse_keybindings_literal( pub fn parse_channel( channel: &str, - cable_channels: &CableChannelPrototypes, -) -> Result { + cable_channels: &Cable, +) -> Result { // try to parse the channel as a cable channel match cable_channels .iter() @@ -191,7 +189,7 @@ pub fn parse_channel( } pub fn list_channels() { - for c in cable::load_cable_channels().unwrap_or_default().keys() { + for c in cable::load_cable().unwrap_or_default().keys() { println!("\t{c}"); } } @@ -223,8 +221,8 @@ pub fn guess_channel_from_prompt( prompt: &str, command_mapping: &FxHashMap, fallback_channel: &str, - cable_channels: &CableChannelPrototypes, -) -> Result { + cable_channels: &Cable, +) -> Result { debug!("Guessing channel from prompt: {}", prompt); // git checkout -qf // --- -------- --- <--------- @@ -317,7 +315,7 @@ mod tests { let post_processed_cli: PostProcessedCli = cli.into(); - let expected = CableChannelPrototype { + let expected = ChannelPrototype { preview_delimiter: Some(":".to_string()), ..Default::default() }; @@ -350,10 +348,7 @@ mod tests { let post_processed_cli: PostProcessedCli = cli.into(); - assert_eq!( - post_processed_cli.channel, - CableChannelPrototype::default(), - ); + assert_eq!(post_processed_cli.channel, ChannelPrototype::default(),); assert_eq!( post_processed_cli.working_directory, Some(".".to_string()) @@ -388,7 +383,7 @@ mod tests { /// Returns a tuple containing a command mapping and a fallback channel. fn guess_channel_from_prompt_setup<'a>( - ) -> (FxHashMap, &'a str, CableChannelPrototypes) { + ) -> (FxHashMap, &'a str, Cable) { let mut command_mapping = FxHashMap::default(); command_mapping.insert("vim".to_string(), "files".to_string()); command_mapping.insert("export".to_string(), "env".to_string()); @@ -396,7 +391,7 @@ mod tests { ( command_mapping, "env", - cable::load_cable_channels().unwrap_or_default(), + cable::load_cable().unwrap_or_default(), ) } diff --git a/television/main.rs b/television/main.rs index 3809814..d032eff 100644 --- a/television/main.rs +++ b/television/main.rs @@ -6,9 +6,7 @@ use std::process::exit; use anyhow::Result; use clap::Parser; use television::cable; -use television::channels::cable::prototypes::{ - CableChannelPrototype, CableChannelPrototypes, -}; +use television::channels::cable::prototypes::{Cable, ChannelPrototype}; use television::utils::clipboard::CLIPBOARD; use tracing::{debug, error, info}; @@ -43,7 +41,7 @@ async fn main() -> Result<()> { let mut config = Config::new(&ConfigEnv::init()?)?; debug!("Loading cable channels..."); - let cable_channels = cable::load_cable_channels().unwrap_or_default(); + let cable_channels = cable::load_cable().unwrap_or_default(); // optionally handle subcommands debug!("Handling subcommands..."); @@ -154,11 +152,11 @@ pub fn determine_channel( args: PostProcessedCli, config: &Config, readable_stdin: bool, - cable_channels: &CableChannelPrototypes, -) -> Result { + cable_channels: &Cable, +) -> Result { if readable_stdin { debug!("Using stdin channel"); - Ok(CableChannelPrototype::stdin(args.preview_command)) + Ok(ChannelPrototype::stdin(args.preview_command)) } else if let Some(prompt) = args.autocomplete_prompt { debug!("Using autocomplete prompt: {:?}", prompt); let channel_prototype = guess_channel_from_prompt( @@ -179,8 +177,7 @@ pub fn determine_channel( mod tests { use rustc_hash::FxHashMap; use television::{ - cable::load_cable_channels, - channels::cable::prototypes::CableChannelPrototype, + cable::load_cable, channels::cable::prototypes::ChannelPrototype, }; use super::*; @@ -189,11 +186,11 @@ mod tests { args: &PostProcessedCli, config: &Config, readable_stdin: bool, - expected_channel: &CableChannelPrototype, - cable_channels: Option, + expected_channel: &ChannelPrototype, + cable_channels: Option, ) { - let channels: CableChannelPrototypes = cable_channels - .unwrap_or_else(|| load_cable_channels().unwrap_or_default()); + let channels: Cable = + cable_channels.unwrap_or_else(|| load_cable().unwrap_or_default()); let channel = determine_channel(args.clone(), config, readable_stdin, &channels) .unwrap(); @@ -208,7 +205,7 @@ mod tests { #[tokio::test] /// Test that the channel is stdin when stdin is readable async fn test_determine_channel_readable_stdin() { - let channel = CableChannelPrototype::default(); + let channel = ChannelPrototype::default(); let args = PostProcessedCli { channel, ..Default::default() @@ -218,9 +215,7 @@ mod tests { &args, &config, true, - &CableChannelPrototype::new( - "stdin", "cat", false, None, None, None, - ), + &ChannelPrototype::new("stdin", "cat", false, None, None, None), None, ); } @@ -228,9 +223,8 @@ mod tests { #[tokio::test] async fn test_determine_channel_autocomplete_prompt() { let autocomplete_prompt = Some("cd".to_string()); - let expected_channel = CableChannelPrototype::new( - "dirs", "ls {}", false, None, None, None, - ); + let expected_channel = + ChannelPrototype::new("dirs", "ls {}", false, None, None, None); let args = PostProcessedCli { autocomplete_prompt, ..Default::default() @@ -263,7 +257,7 @@ mod tests { #[tokio::test] async fn test_determine_channel_standard_case() { let channel = - CableChannelPrototype::new("dirs", "", false, None, None, None); + ChannelPrototype::new("dirs", "", false, None, None, None); let args = PostProcessedCli { channel, ..Default::default() @@ -273,7 +267,7 @@ mod tests { &args, &config, false, - &CableChannelPrototype::new("dirs", "", false, None, None, None), + &ChannelPrototype::new("dirs", "", false, None, None, None), None, ); } diff --git a/television/preview/previewer.rs b/television/preview/previewer.rs index 1ab5c36..e9576d0 100644 --- a/television/preview/previewer.rs +++ b/television/preview/previewer.rs @@ -1,4 +1,4 @@ -use crate::channels::cable::prototypes::CableChannelPrototype; +use crate::channels::cable::prototypes::ChannelPrototype; use crate::preview::{Preview, PreviewContent}; use crate::utils::command::shell_command; use crate::{ @@ -130,8 +130,8 @@ pub fn try_preview( in_flight_previews.lock().remove(&entry.name); } -impl From<&CableChannelPrototype> for Option { - fn from(value: &CableChannelPrototype) -> Self { +impl From<&ChannelPrototype> for Option { + fn from(value: &ChannelPrototype) -> Self { Option::::from(value).map(Previewer::new) } } diff --git a/television/television.rs b/television/television.rs index 2db4a32..cb52db8 100644 --- a/television/television.rs +++ b/television/television.rs @@ -1,14 +1,12 @@ use crate::{ action::Action, - cable::load_cable_channels, channels::{ cable::{ - prototypes::{CableChannelPrototype, CableChannelPrototypes}, + prototypes::{Cable, ChannelPrototype}, Channel as CableChannel, }, entry::Entry, remote_control::RemoteControl, - OnAir, TelevisionChannel, }, config::{Config, Theme}, draw::{ChannelState, Ctx, TvState}, @@ -48,7 +46,7 @@ pub struct Television { action_tx: UnboundedSender, pub config: Config, pub channel: CableChannel, - pub remote_control: Option, + pub remote_control: Option, pub mode: Mode, pub current_pattern: String, pub matching_mode: MatchingMode, @@ -70,13 +68,13 @@ impl Television { #[must_use] pub fn new( action_tx: UnboundedSender, - channel_prototype: CableChannelPrototype, + channel_prototype: ChannelPrototype, mut config: Config, input: Option, no_remote: bool, no_help: bool, exact: bool, - cable_channels: CableChannelPrototypes, + cable_channels: Cable, ) -> Self { let mut results_picker = Picker::new(input.clone()); if config.ui.input_bar_position == InputPosition::Bottom { @@ -108,9 +106,7 @@ impl Television { let remote_control = if no_remote { None } else { - Some(TelevisionChannel::RemoteControl(RemoteControl::new(Some( - cable_channels, - )))) + Some(RemoteControl::new(Some(cable_channels))) }; if no_help { @@ -150,13 +146,6 @@ impl Television { self.ui_state = ui_state; } - pub fn init_remote_control(&mut self) { - let cable_channels = load_cable_channels().unwrap_or_default(); - self.remote_control = Some(TelevisionChannel::RemoteControl( - RemoteControl::new(Some(cable_channels)), - )); - } - pub fn dump_context(&self) -> Ctx { let channel_state = ChannelState::new( self.channel.name.clone(), @@ -189,10 +178,7 @@ impl Television { self.channel.name.clone() } - pub fn change_channel( - &mut self, - channel_prototype: CableChannelPrototype, - ) { + pub fn change_channel(&mut self, channel_prototype: ChannelPrototype) { self.preview_state.reset(); self.preview_state.enabled = channel_prototype.preview_command.is_some(); @@ -213,7 +199,9 @@ impl Television { ); } Mode::RemoteControl => { - self.remote_control.as_mut().unwrap().find(pattern); + if let Some(rc) = self.remote_control.as_mut() { + rc.find(pattern); + } } } } @@ -244,11 +232,9 @@ impl Television { } Mode::RemoteControl => { if let Some(i) = self.rc_picker.selected() { - return self - .remote_control - .as_ref() - .unwrap() - .get_result(i.try_into().unwrap()); + if let Some(rc) = &self.remote_control { + return rc.get_result(i.try_into().unwrap()); + } } None } @@ -491,12 +477,10 @@ impl Television { match self.mode { Mode::Channel => { self.mode = Mode::RemoteControl; - self.init_remote_control(); } Mode::RemoteControl => { // this resets the RC picker self.reset_picker_input(); - self.init_remote_control(); self.remote_control.as_mut().unwrap().find(EMPTY_STRING); self.reset_picker_selection(); self.mode = Mode::Channel; @@ -528,7 +512,6 @@ impl Television { .remote_control .as_ref() .unwrap() - // FIXME: is the TelevisionChannel enum still worth it? .zap(entry.name.as_str())?; // this resets the RC picker self.reset_picker_selection(); diff --git a/tests/app.rs b/tests/app.rs index c04c736..e96170e 100644 --- a/tests/app.rs +++ b/tests/app.rs @@ -3,9 +3,7 @@ use std::{collections::HashSet, path::PathBuf, time::Duration}; use television::{ action::Action, app::{App, AppOptions}, - channels::cable::prototypes::{ - CableChannelPrototype, CableChannelPrototypes, - }, + channels::cable::prototypes::{Cable, ChannelPrototype}, config::default_config_from_file, }; use tokio::{task::JoinHandle, time::timeout}; @@ -23,19 +21,19 @@ const DEFAULT_TIMEOUT: Duration = Duration::from_millis(100); /// The app is started in a separate task and can be interacted with by sending /// actions to the action channel. fn setup_app( - channel_prototype: Option, + channel_prototype: Option, select_1: bool, exact: bool, ) -> ( JoinHandle, tokio::sync::mpsc::UnboundedSender, ) { - let chan: CableChannelPrototype = channel_prototype.unwrap_or_else(|| { + let chan: ChannelPrototype = channel_prototype.unwrap_or_else(|| { let target_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests") .join("target_dir"); std::env::set_current_dir(&target_dir).unwrap(); - CableChannelPrototype::default() + ChannelPrototype::default() }); let mut config = default_config_from_file().unwrap(); // this speeds up the tests @@ -49,13 +47,7 @@ fn setup_app( false, config.application.tick_rate, ); - let mut app = App::new( - chan, - config, - input, - options, - &CableChannelPrototypes::default(), - ); + let mut app = App::new(chan, config, input, options, &Cable::default()); // retrieve the app's action channel handle in order to send a quit action let tx = app.action_tx.clone(); @@ -220,8 +212,8 @@ async fn test_app_exact_search_positive() { #[tokio::test(flavor = "multi_thread", worker_threads = 3)] async fn test_app_exits_when_select_1_and_only_one_result() { - let prototype = CableChannelPrototype::new( - "cable", + let prototype = ChannelPrototype::new( + "some_channel", "echo file1.txt", false, None, @@ -258,8 +250,8 @@ async fn test_app_exits_when_select_1_and_only_one_result() { #[tokio::test(flavor = "multi_thread", worker_threads = 3)] async fn test_app_does_not_exit_when_select_1_and_more_than_one_result() { - let prototype = CableChannelPrototype::new( - "cable", + let prototype = ChannelPrototype::new( + "some_channel", "echo 'file1.txt\nfile2.txt'", false, None,