refactor: drop TelevisionChannel enum and all associated macros (#498)

This drops the `TelevisionChannel` enum which served as a unified
interface for builtin and cable channels as well as all related macros
and the `television-derive` package.
This simplifies the code quite a lot and will improve overall
maintainability.
This commit is contained in:
Alexandre Pasmantier 2025-05-06 11:18:32 +02:00 committed by GitHub
parent 58d73dbeba
commit 2b2654b6aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 162 additions and 570 deletions

10
Cargo.lock generated
View File

@ -1424,7 +1424,6 @@ dependencies = [
"rustc-hash", "rustc-hash",
"serde", "serde",
"signal-hook", "signal-hook",
"television-derive",
"tempfile", "tempfile",
"tokio", "tokio",
"toml", "toml",
@ -1434,15 +1433,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "television-derive"
version = "0.0.27"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.19.1" version = "3.19.1"

View File

@ -31,8 +31,6 @@ build = "build.rs"
path = "television/lib.rs" path = "television/lib.rs"
[dependencies] [dependencies]
television-derive = { path = "television-derive", version = "0.0.27" }
anyhow = "1.0" anyhow = "1.0"
base64 = "0.22.1" base64 = "0.22.1"
directories = "6.0" directories = "6.0"

View File

@ -8,13 +8,12 @@ use ratatui::prelude::{Line, Style};
use ratatui::style::Color; use ratatui::style::Color;
use ratatui::widgets::{Block, BorderType, Borders, ListDirection, Padding}; use ratatui::widgets::{Block, BorderType, Borders, ListDirection, Padding};
use ratatui::Terminal; use ratatui::Terminal;
use television::channels::cable::prototypes::CableChannelPrototypes; use television::channels::cable::prototypes::Cable;
use television::{ use television::{
action::Action, action::Action,
channels::{ channels::{
cable::prototypes::CableChannelPrototype, cable::prototypes::ChannelPrototype,
entry::{into_ranges, Entry}, entry::{into_ranges, Entry},
OnAir,
}, },
config::{Config, ConfigEnv}, config::{Config, ConfigEnv},
screen::{colors::ResultsColorscheme, results::build_results_list}, screen::{colors::ResultsColorscheme, results::build_results_list},
@ -22,6 +21,7 @@ use television::{
}; };
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
#[allow(clippy::too_many_lines)]
pub fn draw_results_list(c: &mut Criterion) { pub fn draw_results_list(c: &mut Criterion) {
// FIXME: there's probably a way to have this as a benchmark asset // 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 // 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) { pub fn draw(c: &mut Criterion) {
let width = 250; let width = 250;
let height = 80; let height = 80;
@ -472,7 +473,7 @@ pub fn draw(c: &mut Criterion) {
let backend = TestBackend::new(width, height); let backend = TestBackend::new(width, height);
let terminal = Terminal::new(backend).unwrap(); let terminal = Terminal::new(backend).unwrap();
let (tx, _) = tokio::sync::mpsc::unbounded_channel(); let (tx, _) = tokio::sync::mpsc::unbounded_channel();
let channel = CableChannelPrototype::default(); let channel = ChannelPrototype::default();
// Wait for the channel to finish loading // Wait for the channel to finish loading
let mut tv = Television::new( let mut tv = Television::new(
tx, tx,
@ -482,7 +483,7 @@ pub fn draw(c: &mut Criterion) {
false, false,
false, false,
false, false,
CableChannelPrototypes::default(), Cable::default(),
); );
tv.find("television"); tv.find("television");
for _ in 0..5 { for _ in 0..5 {

View File

@ -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 <alex.pasmant@gmail.com>"]
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

View File

@ -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<Entry> {
match self {
#(
#enum_name::#variant_names(ref mut channel) => {
channel.results(num_entries, offset)
}
)*
}
}
fn get_result(&self, index: u32) -> Option<Entry> {
match self {
#(
#enum_name::#variant_names(ref channel) => {
channel.get_result(index)
}
)*
}
}
fn selected_entries(&self) -> &FxHashSet<Entry> {
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()
}

View File

@ -4,10 +4,8 @@ use anyhow::Result;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::{debug, trace}; use tracing::{debug, trace};
use crate::channels::cable::prototypes::{ use crate::channels::cable::prototypes::{Cable, ChannelPrototype};
CableChannelPrototype, CableChannelPrototypes, use crate::channels::entry::Entry;
};
use crate::channels::{entry::Entry, OnAir};
use crate::config::{default_tick_rate, Config}; use crate::config::{default_tick_rate, Config};
use crate::keymap::Keymap; use crate::keymap::Keymap;
use crate::render::UiState; use crate::render::UiState;
@ -138,11 +136,11 @@ const ACTION_BUF_SIZE: usize = 8;
impl App { impl App {
pub fn new( pub fn new(
channel_prototype: CableChannelPrototype, channel_prototype: ChannelPrototype,
config: Config, config: Config,
input: Option<String>, input: Option<String>,
options: AppOptions, options: AppOptions,
cable_channels: &CableChannelPrototypes, cable_channels: &Cable,
) -> Self { ) -> Self {
let (action_tx, action_rx) = mpsc::unbounded_channel(); let (action_tx, action_rx) = mpsc::unbounded_channel();
let (render_tx, render_rx) = mpsc::unbounded_channel(); let (render_tx, render_rx) = mpsc::unbounded_channel();
@ -160,7 +158,7 @@ impl App {
options.no_remote, options.no_remote,
options.no_help, options.no_help,
options.exact, options.exact,
CableChannelPrototypes((*cable_channels).clone()), cable_channels.clone(),
); );
Self { Self {

View File

@ -6,17 +6,15 @@ use anyhow::Result;
use tracing::{debug, error}; use tracing::{debug, error};
use crate::{ use crate::{
channels::cable::prototypes::{ channels::cable::prototypes::{Cable, ChannelPrototype},
CableChannelPrototype, CableChannelPrototypes,
},
config::get_config_dir, config::get_config_dir,
}; };
/// Just a proxy struct to deserialize prototypes /// Just a proxy struct to deserialize prototypes
#[derive(Debug, serde::Deserialize, Default)] #[derive(Debug, serde::Deserialize, Default)]
pub struct SerializedChannelPrototypes { pub struct CableSpec {
#[serde(rename = "cable_channel")] #[serde(rename = "cable_channel")]
pub prototypes: Vec<CableChannelPrototype>, pub prototypes: Vec<ChannelPrototype>,
} }
const CABLE_FILE_NAME_SUFFIX: &str = "channels"; const CABLE_FILE_NAME_SUFFIX: &str = "channels";
@ -42,7 +40,7 @@ const DEFAULT_CABLE_CHANNELS: &str =
/// ├── my_channels.toml /// ├── my_channels.toml
/// └── windows_channels.toml /// └── windows_channels.toml
/// ``` /// ```
pub fn load_cable_channels() -> Result<CableChannelPrototypes> { pub fn load_cable() -> Result<Cable> {
let config_dir = get_config_dir(); let config_dir = get_config_dir();
// list all files in the config directory // list all files in the config directory
@ -60,13 +58,13 @@ pub fn load_cable_channels() -> Result<CableChannelPrototypes> {
} }
let default_prototypes = let default_prototypes =
toml::from_str::<SerializedChannelPrototypes>(DEFAULT_CABLE_CHANNELS) toml::from_str::<CableSpec>(DEFAULT_CABLE_CHANNELS)
.expect("Failed to parse default cable channels"); .expect("Failed to parse default cable channels");
let prototypes = file_paths.iter().fold( let prototypes = file_paths.iter().fold(
Vec::<CableChannelPrototype>::new(), Vec::<ChannelPrototype>::new(),
|mut acc, p| { |mut acc, p| {
match toml::from_str::<SerializedChannelPrototypes>( match toml::from_str::<CableSpec>(
&std::fs::read_to_string(p) &std::fs::read_to_string(p)
.expect("Unable to read configuration file"), .expect("Unable to read configuration file"),
) { ) {
@ -97,7 +95,7 @@ pub fn load_cable_channels() -> Result<CableChannelPrototypes> {
{ {
cable_channels.insert(prototype.name.clone(), prototype); cable_channels.insert(prototype.name.clone(), prototype);
} }
Ok(CableChannelPrototypes(cable_channels)) Ok(Cable(cable_channels))
} }
fn is_cable_file_format<P>(p: P) -> bool fn is_cable_file_format<P>(p: P) -> bool

View File

@ -2,11 +2,11 @@ use std::collections::HashSet;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::process::Stdio; use std::process::Stdio;
use prototypes::{CableChannelPrototype, DEFAULT_DELIMITER}; use prototypes::{ChannelPrototype, DEFAULT_DELIMITER};
use rustc_hash::{FxBuildHasher, FxHashSet}; use rustc_hash::{FxBuildHasher, FxHashSet};
use tracing::debug; 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::Matcher;
use crate::matcher::{config::Config, injector::Injector}; use crate::matcher::{config::Config, injector::Injector};
use crate::utils::command::shell_command; use crate::utils::command::shell_command;
@ -34,8 +34,8 @@ impl Default for Channel {
} }
} }
impl From<CableChannelPrototype> for Channel { impl From<ChannelPrototype> for Channel {
fn from(prototype: CableChannelPrototype) -> Self { fn from(prototype: ChannelPrototype) -> Self {
Self::new( Self::new(
&prototype.name, &prototype.name,
&prototype.source_command, &prototype.source_command,
@ -77,6 +77,59 @@ impl Channel {
crawl_handle, crawl_handle,
} }
} }
pub fn find(&mut self, pattern: &str) {
self.matcher.find(pattern);
}
pub fn results(&mut self, num_entries: u32, offset: u32) -> Vec<Entry> {
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<Entry> {
self.matcher.get_result(index).map(|item| {
let path = item.matched_string;
Entry::new(path)
})
}
pub fn selected_entries(&self) -> &FxHashSet<Entry> {
&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)] #[allow(clippy::unused_async)]
@ -123,58 +176,3 @@ async fn load_candidates(
} }
let _ = child.wait(); 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<Entry> {
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<Entry> {
self.matcher.get_result(index).map(|item| {
let path = item.matched_string;
Entry::new(path)
})
}
fn selected_entries(&self) -> &FxHashSet<Entry> {
&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()
}
}

View File

@ -4,9 +4,7 @@ use std::{
ops::Deref, ops::Deref,
}; };
use crate::{ use crate::{cable::CableSpec, channels::preview::PreviewCommand};
cable::SerializedChannelPrototypes, channels::preview::PreviewCommand,
};
/// A prototype for a cable channel. /// A prototype for a cable channel.
/// ///
@ -40,7 +38,7 @@ use crate::{
/// preview_command = "cat {}" /// preview_command = "cat {}"
/// ``` /// ```
#[derive(Clone, Debug, serde::Deserialize, PartialEq)] #[derive(Clone, Debug, serde::Deserialize, PartialEq)]
pub struct CableChannelPrototype { pub struct ChannelPrototype {
pub name: String, pub name: String,
pub source_command: String, pub source_command: String,
#[serde(default)] #[serde(default)]
@ -54,7 +52,7 @@ pub struct CableChannelPrototype {
const STDIN_CHANNEL_NAME: &str = "stdin"; const STDIN_CHANNEL_NAME: &str = "stdin";
const STDIN_SOURCE_COMMAND: &str = "cat"; const STDIN_SOURCE_COMMAND: &str = "cat";
impl CableChannelPrototype { impl ChannelPrototype {
pub fn new( pub fn new(
name: &str, name: &str,
source_command: &str, source_command: &str,
@ -102,9 +100,9 @@ impl CableChannelPrototype {
const DEFAULT_PROTOTYPE_NAME: &str = "files"; const DEFAULT_PROTOTYPE_NAME: &str = "files";
pub const DEFAULT_DELIMITER: &str = " "; pub const DEFAULT_DELIMITER: &str = " ";
impl Default for CableChannelPrototype { impl Default for ChannelPrototype {
fn default() -> Self { fn default() -> Self {
CableChannelPrototypes::default() Cable::default()
.get(DEFAULT_PROTOTYPE_NAME) .get(DEFAULT_PROTOTYPE_NAME)
.cloned() .cloned()
.unwrap() .unwrap()
@ -118,7 +116,7 @@ fn default_delimiter() -> Option<String> {
Some(DEFAULT_DELIMITER.to_string()) Some(DEFAULT_DELIMITER.to_string())
} }
impl Display for CableChannelPrototype { impl Display for ChannelPrototype {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.name) write!(f, "{}", self.name)
} }
@ -129,13 +127,11 @@ impl Display for CableChannelPrototype {
/// This is used to store cable channel prototypes throughout the application /// This is used to store cable channel prototypes throughout the application
/// in a way that facilitates answering questions like "what's the prototype /// in a way that facilitates answering questions like "what's the prototype
/// for `files`?" or "does this channel exist?". /// for `files`?" or "does this channel exist?".
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize, Clone)]
pub struct CableChannelPrototypes( pub struct Cable(pub FxHashMap<String, ChannelPrototype>);
pub FxHashMap<String, CableChannelPrototype>,
);
impl Deref for CableChannelPrototypes { impl Deref for Cable {
type Target = FxHashMap<String, CableChannelPrototype>; type Target = FxHashMap<String, ChannelPrototype>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
@ -153,18 +149,16 @@ const DEFAULT_CABLE_CHANNELS_FILE: &str =
const DEFAULT_CABLE_CHANNELS_FILE: &str = const DEFAULT_CABLE_CHANNELS_FILE: &str =
include_str!("../../../cable/windows-channels.toml"); include_str!("../../../cable/windows-channels.toml");
impl Default for CableChannelPrototypes { impl Default for Cable {
/// Fallback to the default cable channels specification (the template file /// Fallback to the default cable channels specification (the template file
/// included in the repo). /// included in the repo).
fn default() -> Self { fn default() -> Self {
let s = toml::from_str::<SerializedChannelPrototypes>( let s = toml::from_str::<CableSpec>(DEFAULT_CABLE_CHANNELS_FILE)
DEFAULT_CABLE_CHANNELS_FILE, .expect("Unable to parse default cable channels");
)
.expect("Unable to parse default cable channels");
let mut prototypes = FxHashMap::default(); let mut prototypes = FxHashMap::default();
for prototype in s.prototypes { for prototype in s.prototypes {
prototypes.insert(prototype.name.clone(), prototype); prototypes.insert(prototype.name.clone(), prototype);
} }
CableChannelPrototypes(prototypes) Cable(prototypes)
} }
} }

View File

@ -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 cable;
pub mod entry; pub mod entry;
pub mod preview; pub mod preview;
pub mod remote_control; 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<Entry>;
/// ```
/// - `get_result`: Get a specific result by its index.
/// ```ignore
/// fn get_result(&self, index: u32) -> Option<Entry>;
/// ```
/// - `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<Entry>;
/// Get a specific result by its index.
fn get_result(&self, index: u32) -> Option<Entry>;
/// Get the currently selected entries.
fn selected_entries(&self) -> &FxHashSet<Entry>;
/// 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<CableChannelPrototype> {
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"),
}
}
}

View File

@ -1,7 +1,7 @@
use std::fmt::Display; use std::fmt::Display;
use super::{cable::prototypes::DEFAULT_DELIMITER, entry::Entry}; 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 lazy_regex::{regex, Lazy, Regex};
use tracing::debug; use tracing::debug;
@ -65,8 +65,8 @@ impl PreviewCommand {
} }
} }
impl From<&CableChannelPrototype> for Option<PreviewCommand> { impl From<&ChannelPrototype> for Option<PreviewCommand> {
fn from(value: &CableChannelPrototype) -> Self { fn from(value: &ChannelPrototype) -> Self {
if let Some(command) = value.preview_command.as_ref() { if let Some(command) = value.preview_command.as_ref() {
let delimiter = value let delimiter = value
.preview_delimiter .preview_delimiter

View File

@ -1,32 +1,23 @@
use std::collections::HashSet; use crate::channels::cable::prototypes::Cable;
use crate::channels::cable::prototypes::CableChannelPrototypes;
use crate::channels::entry::Entry; use crate::channels::entry::Entry;
use crate::channels::OnAir;
use crate::matcher::{config::Config, Matcher}; use crate::matcher::{config::Config, Matcher};
use anyhow::Result; use anyhow::Result;
use devicons::FileIcon; use devicons::FileIcon;
use rustc_hash::{FxBuildHasher, FxHashSet};
use super::cable::prototypes::CableChannelPrototype; use super::cable::prototypes::ChannelPrototype;
pub struct RemoteControl { pub struct RemoteControl {
matcher: Matcher<String>, matcher: Matcher<String>,
cable_channels: Option<CableChannelPrototypes>, cable_channels: Option<Cable>,
selected_entries: FxHashSet<Entry>,
} }
const NUM_THREADS: usize = 1; const NUM_THREADS: usize = 1;
impl RemoteControl { impl RemoteControl {
pub fn new(cable_channels: Option<CableChannelPrototypes>) -> Self { pub fn new(cable_channels: Option<Cable>) -> Self {
let matcher = Matcher::new(Config::default().n_threads(NUM_THREADS)); let matcher = Matcher::new(Config::default().n_threads(NUM_THREADS));
let injector = matcher.injector(); let injector = matcher.injector();
for c in cable_channels for c in cable_channels.as_ref().unwrap_or(&Cable::default()).keys() {
.as_ref()
.unwrap_or(&CableChannelPrototypes::default())
.keys()
{
let () = injector.push(c.clone(), |e, cols| { let () = injector.push(c.clone(), |e, cols| {
cols[0] = e.to_string().into(); cols[0] = e.to_string().into();
}); });
@ -34,11 +25,10 @@ impl RemoteControl {
RemoteControl { RemoteControl {
matcher, matcher,
cable_channels, cable_channels,
selected_entries: HashSet::with_hasher(FxBuildHasher),
} }
} }
pub fn zap(&self, channel_name: &str) -> Result<CableChannelPrototype> { pub fn zap(&self, channel_name: &str) -> Result<ChannelPrototype> {
match self match self
.cable_channels .cable_channels
.as_ref() .as_ref()
@ -69,12 +59,12 @@ const CABLE_ICON: FileIcon = FileIcon {
color: "#000000", color: "#000000",
}; };
impl OnAir for RemoteControl { impl RemoteControl {
fn find(&mut self, pattern: &str) { pub fn find(&mut self, pattern: &str) {
self.matcher.find(pattern); self.matcher.find(pattern);
} }
fn results(&mut self, num_entries: u32, offset: u32) -> Vec<Entry> { pub fn results(&mut self, num_entries: u32, offset: u32) -> Vec<Entry> {
self.matcher.tick(); self.matcher.tick();
self.matcher self.matcher
.results(num_entries, offset) .results(num_entries, offset)
@ -88,35 +78,28 @@ impl OnAir for RemoteControl {
.collect() .collect()
} }
fn get_result(&self, index: u32) -> Option<Entry> { pub fn get_result(&self, index: u32) -> Option<Entry> {
self.matcher.get_result(index).map(|item| { self.matcher.get_result(index).map(|item| {
let path = item.matched_string; let path = item.matched_string;
Entry::new(path).with_icon(TV_ICON) Entry::new(path).with_icon(TV_ICON)
}) })
} }
fn selected_entries(&self) -> &FxHashSet<Entry> { pub fn result_count(&self) -> u32 {
&self.selected_entries
}
#[allow(unused_variables)]
fn toggle_selection(&mut self, entry: &Entry) {}
fn result_count(&self) -> u32 {
self.matcher.matched_item_count self.matcher.matched_item_count
} }
fn total_count(&self) -> u32 { pub fn total_count(&self) -> u32 {
self.matcher.total_item_count self.matcher.total_item_count
} }
fn running(&self) -> bool { pub fn running(&self) -> bool {
self.matcher.status.running self.matcher.status.running
} }
fn shutdown(&self) {} pub fn shutdown(&self) {}
fn supports_preview(&self) -> bool { pub fn supports_preview(&self) -> bool {
false false
} }
} }

View File

@ -4,9 +4,7 @@ use std::path::Path;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use tracing::debug; use tracing::debug;
use crate::channels::cable::prototypes::{ use crate::channels::cable::prototypes::{Cable, ChannelPrototype};
CableChannelPrototype, CableChannelPrototypes,
};
use crate::channels::preview::PreviewCommand; use crate::channels::preview::PreviewCommand;
use crate::cli::args::{Cli, Command}; use crate::cli::args::{Cli, Command};
use crate::config::{KeyBindings, DEFAULT_CHANNEL}; use crate::config::{KeyBindings, DEFAULT_CHANNEL};
@ -20,7 +18,7 @@ pub mod args;
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PostProcessedCli { pub struct PostProcessedCli {
pub channel: CableChannelPrototype, pub channel: ChannelPrototype,
pub preview_command: Option<PreviewCommand>, pub preview_command: Option<PreviewCommand>,
pub no_preview: bool, pub no_preview: bool,
pub tick_rate: Option<f64>, pub tick_rate: Option<f64>,
@ -41,7 +39,7 @@ pub struct PostProcessedCli {
impl Default for PostProcessedCli { impl Default for PostProcessedCli {
fn default() -> Self { fn default() -> Self {
Self { Self {
channel: CableChannelPrototype::default(), channel: ChannelPrototype::default(),
preview_command: None, preview_command: None,
no_preview: false, no_preview: false,
tick_rate: None, tick_rate: None,
@ -80,10 +78,10 @@ impl From<Cli> for PostProcessedCli {
offset_expr: None, offset_expr: None,
}); });
let mut channel: CableChannelPrototype; let mut channel: ChannelPrototype;
let working_directory: Option<String>; let working_directory: Option<String>;
let cable_channels = cable::load_cable_channels().unwrap_or_default(); let cable_channels = cable::load_cable().unwrap_or_default();
if cli.channel.is_none() { if cli.channel.is_none() {
channel = cable_channels channel = cable_channels
.get(DEFAULT_CHANNEL) .get(DEFAULT_CHANNEL)
@ -178,8 +176,8 @@ fn parse_keybindings_literal(
pub fn parse_channel( pub fn parse_channel(
channel: &str, channel: &str,
cable_channels: &CableChannelPrototypes, cable_channels: &Cable,
) -> Result<CableChannelPrototype> { ) -> Result<ChannelPrototype> {
// try to parse the channel as a cable channel // try to parse the channel as a cable channel
match cable_channels match cable_channels
.iter() .iter()
@ -191,7 +189,7 @@ pub fn parse_channel(
} }
pub fn list_channels() { 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}"); println!("\t{c}");
} }
} }
@ -223,8 +221,8 @@ pub fn guess_channel_from_prompt(
prompt: &str, prompt: &str,
command_mapping: &FxHashMap<String, String>, command_mapping: &FxHashMap<String, String>,
fallback_channel: &str, fallback_channel: &str,
cable_channels: &CableChannelPrototypes, cable_channels: &Cable,
) -> Result<CableChannelPrototype> { ) -> Result<ChannelPrototype> {
debug!("Guessing channel from prompt: {}", prompt); debug!("Guessing channel from prompt: {}", prompt);
// git checkout -qf // git checkout -qf
// --- -------- --- <--------- // --- -------- --- <---------
@ -317,7 +315,7 @@ mod tests {
let post_processed_cli: PostProcessedCli = cli.into(); let post_processed_cli: PostProcessedCli = cli.into();
let expected = CableChannelPrototype { let expected = ChannelPrototype {
preview_delimiter: Some(":".to_string()), preview_delimiter: Some(":".to_string()),
..Default::default() ..Default::default()
}; };
@ -350,10 +348,7 @@ mod tests {
let post_processed_cli: PostProcessedCli = cli.into(); let post_processed_cli: PostProcessedCli = cli.into();
assert_eq!( assert_eq!(post_processed_cli.channel, ChannelPrototype::default(),);
post_processed_cli.channel,
CableChannelPrototype::default(),
);
assert_eq!( assert_eq!(
post_processed_cli.working_directory, post_processed_cli.working_directory,
Some(".".to_string()) Some(".".to_string())
@ -388,7 +383,7 @@ mod tests {
/// Returns a tuple containing a command mapping and a fallback channel. /// Returns a tuple containing a command mapping and a fallback channel.
fn guess_channel_from_prompt_setup<'a>( fn guess_channel_from_prompt_setup<'a>(
) -> (FxHashMap<String, String>, &'a str, CableChannelPrototypes) { ) -> (FxHashMap<String, String>, &'a str, Cable) {
let mut command_mapping = FxHashMap::default(); let mut command_mapping = FxHashMap::default();
command_mapping.insert("vim".to_string(), "files".to_string()); command_mapping.insert("vim".to_string(), "files".to_string());
command_mapping.insert("export".to_string(), "env".to_string()); command_mapping.insert("export".to_string(), "env".to_string());
@ -396,7 +391,7 @@ mod tests {
( (
command_mapping, command_mapping,
"env", "env",
cable::load_cable_channels().unwrap_or_default(), cable::load_cable().unwrap_or_default(),
) )
} }

View File

@ -6,9 +6,7 @@ use std::process::exit;
use anyhow::Result; use anyhow::Result;
use clap::Parser; use clap::Parser;
use television::cable; use television::cable;
use television::channels::cable::prototypes::{ use television::channels::cable::prototypes::{Cable, ChannelPrototype};
CableChannelPrototype, CableChannelPrototypes,
};
use television::utils::clipboard::CLIPBOARD; use television::utils::clipboard::CLIPBOARD;
use tracing::{debug, error, info}; use tracing::{debug, error, info};
@ -43,7 +41,7 @@ async fn main() -> Result<()> {
let mut config = Config::new(&ConfigEnv::init()?)?; let mut config = Config::new(&ConfigEnv::init()?)?;
debug!("Loading cable channels..."); 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 // optionally handle subcommands
debug!("Handling subcommands..."); debug!("Handling subcommands...");
@ -154,11 +152,11 @@ pub fn determine_channel(
args: PostProcessedCli, args: PostProcessedCli,
config: &Config, config: &Config,
readable_stdin: bool, readable_stdin: bool,
cable_channels: &CableChannelPrototypes, cable_channels: &Cable,
) -> Result<CableChannelPrototype> { ) -> Result<ChannelPrototype> {
if readable_stdin { if readable_stdin {
debug!("Using stdin channel"); debug!("Using stdin channel");
Ok(CableChannelPrototype::stdin(args.preview_command)) Ok(ChannelPrototype::stdin(args.preview_command))
} else if let Some(prompt) = args.autocomplete_prompt { } else if let Some(prompt) = args.autocomplete_prompt {
debug!("Using autocomplete prompt: {:?}", prompt); debug!("Using autocomplete prompt: {:?}", prompt);
let channel_prototype = guess_channel_from_prompt( let channel_prototype = guess_channel_from_prompt(
@ -179,8 +177,7 @@ pub fn determine_channel(
mod tests { mod tests {
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use television::{ use television::{
cable::load_cable_channels, cable::load_cable, channels::cable::prototypes::ChannelPrototype,
channels::cable::prototypes::CableChannelPrototype,
}; };
use super::*; use super::*;
@ -189,11 +186,11 @@ mod tests {
args: &PostProcessedCli, args: &PostProcessedCli,
config: &Config, config: &Config,
readable_stdin: bool, readable_stdin: bool,
expected_channel: &CableChannelPrototype, expected_channel: &ChannelPrototype,
cable_channels: Option<CableChannelPrototypes>, cable_channels: Option<Cable>,
) { ) {
let channels: CableChannelPrototypes = cable_channels let channels: Cable =
.unwrap_or_else(|| load_cable_channels().unwrap_or_default()); cable_channels.unwrap_or_else(|| load_cable().unwrap_or_default());
let channel = let channel =
determine_channel(args.clone(), config, readable_stdin, &channels) determine_channel(args.clone(), config, readable_stdin, &channels)
.unwrap(); .unwrap();
@ -208,7 +205,7 @@ mod tests {
#[tokio::test] #[tokio::test]
/// Test that the channel is stdin when stdin is readable /// Test that the channel is stdin when stdin is readable
async fn test_determine_channel_readable_stdin() { async fn test_determine_channel_readable_stdin() {
let channel = CableChannelPrototype::default(); let channel = ChannelPrototype::default();
let args = PostProcessedCli { let args = PostProcessedCli {
channel, channel,
..Default::default() ..Default::default()
@ -218,9 +215,7 @@ mod tests {
&args, &args,
&config, &config,
true, true,
&CableChannelPrototype::new( &ChannelPrototype::new("stdin", "cat", false, None, None, None),
"stdin", "cat", false, None, None, None,
),
None, None,
); );
} }
@ -228,9 +223,8 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_determine_channel_autocomplete_prompt() { async fn test_determine_channel_autocomplete_prompt() {
let autocomplete_prompt = Some("cd".to_string()); let autocomplete_prompt = Some("cd".to_string());
let expected_channel = CableChannelPrototype::new( let expected_channel =
"dirs", "ls {}", false, None, None, None, ChannelPrototype::new("dirs", "ls {}", false, None, None, None);
);
let args = PostProcessedCli { let args = PostProcessedCli {
autocomplete_prompt, autocomplete_prompt,
..Default::default() ..Default::default()
@ -263,7 +257,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_determine_channel_standard_case() { async fn test_determine_channel_standard_case() {
let channel = let channel =
CableChannelPrototype::new("dirs", "", false, None, None, None); ChannelPrototype::new("dirs", "", false, None, None, None);
let args = PostProcessedCli { let args = PostProcessedCli {
channel, channel,
..Default::default() ..Default::default()
@ -273,7 +267,7 @@ mod tests {
&args, &args,
&config, &config,
false, false,
&CableChannelPrototype::new("dirs", "", false, None, None, None), &ChannelPrototype::new("dirs", "", false, None, None, None),
None, None,
); );
} }

View File

@ -1,4 +1,4 @@
use crate::channels::cable::prototypes::CableChannelPrototype; use crate::channels::cable::prototypes::ChannelPrototype;
use crate::preview::{Preview, PreviewContent}; use crate::preview::{Preview, PreviewContent};
use crate::utils::command::shell_command; use crate::utils::command::shell_command;
use crate::{ use crate::{
@ -130,8 +130,8 @@ pub fn try_preview(
in_flight_previews.lock().remove(&entry.name); in_flight_previews.lock().remove(&entry.name);
} }
impl From<&CableChannelPrototype> for Option<Previewer> { impl From<&ChannelPrototype> for Option<Previewer> {
fn from(value: &CableChannelPrototype) -> Self { fn from(value: &ChannelPrototype) -> Self {
Option::<PreviewCommand>::from(value).map(Previewer::new) Option::<PreviewCommand>::from(value).map(Previewer::new)
} }
} }

View File

@ -1,14 +1,12 @@
use crate::{ use crate::{
action::Action, action::Action,
cable::load_cable_channels,
channels::{ channels::{
cable::{ cable::{
prototypes::{CableChannelPrototype, CableChannelPrototypes}, prototypes::{Cable, ChannelPrototype},
Channel as CableChannel, Channel as CableChannel,
}, },
entry::Entry, entry::Entry,
remote_control::RemoteControl, remote_control::RemoteControl,
OnAir, TelevisionChannel,
}, },
config::{Config, Theme}, config::{Config, Theme},
draw::{ChannelState, Ctx, TvState}, draw::{ChannelState, Ctx, TvState},
@ -48,7 +46,7 @@ pub struct Television {
action_tx: UnboundedSender<Action>, action_tx: UnboundedSender<Action>,
pub config: Config, pub config: Config,
pub channel: CableChannel, pub channel: CableChannel,
pub remote_control: Option<TelevisionChannel>, pub remote_control: Option<RemoteControl>,
pub mode: Mode, pub mode: Mode,
pub current_pattern: String, pub current_pattern: String,
pub matching_mode: MatchingMode, pub matching_mode: MatchingMode,
@ -70,13 +68,13 @@ impl Television {
#[must_use] #[must_use]
pub fn new( pub fn new(
action_tx: UnboundedSender<Action>, action_tx: UnboundedSender<Action>,
channel_prototype: CableChannelPrototype, channel_prototype: ChannelPrototype,
mut config: Config, mut config: Config,
input: Option<String>, input: Option<String>,
no_remote: bool, no_remote: bool,
no_help: bool, no_help: bool,
exact: bool, exact: bool,
cable_channels: CableChannelPrototypes, cable_channels: Cable,
) -> Self { ) -> Self {
let mut results_picker = Picker::new(input.clone()); let mut results_picker = Picker::new(input.clone());
if config.ui.input_bar_position == InputPosition::Bottom { if config.ui.input_bar_position == InputPosition::Bottom {
@ -108,9 +106,7 @@ impl Television {
let remote_control = if no_remote { let remote_control = if no_remote {
None None
} else { } else {
Some(TelevisionChannel::RemoteControl(RemoteControl::new(Some( Some(RemoteControl::new(Some(cable_channels)))
cable_channels,
))))
}; };
if no_help { if no_help {
@ -150,13 +146,6 @@ impl Television {
self.ui_state = ui_state; 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 { pub fn dump_context(&self) -> Ctx {
let channel_state = ChannelState::new( let channel_state = ChannelState::new(
self.channel.name.clone(), self.channel.name.clone(),
@ -189,10 +178,7 @@ impl Television {
self.channel.name.clone() self.channel.name.clone()
} }
pub fn change_channel( pub fn change_channel(&mut self, channel_prototype: ChannelPrototype) {
&mut self,
channel_prototype: CableChannelPrototype,
) {
self.preview_state.reset(); self.preview_state.reset();
self.preview_state.enabled = self.preview_state.enabled =
channel_prototype.preview_command.is_some(); channel_prototype.preview_command.is_some();
@ -213,7 +199,9 @@ impl Television {
); );
} }
Mode::RemoteControl => { 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 => { Mode::RemoteControl => {
if let Some(i) = self.rc_picker.selected() { if let Some(i) = self.rc_picker.selected() {
return self if let Some(rc) = &self.remote_control {
.remote_control return rc.get_result(i.try_into().unwrap());
.as_ref() }
.unwrap()
.get_result(i.try_into().unwrap());
} }
None None
} }
@ -491,12 +477,10 @@ impl Television {
match self.mode { match self.mode {
Mode::Channel => { Mode::Channel => {
self.mode = Mode::RemoteControl; self.mode = Mode::RemoteControl;
self.init_remote_control();
} }
Mode::RemoteControl => { Mode::RemoteControl => {
// this resets the RC picker // this resets the RC picker
self.reset_picker_input(); self.reset_picker_input();
self.init_remote_control();
self.remote_control.as_mut().unwrap().find(EMPTY_STRING); self.remote_control.as_mut().unwrap().find(EMPTY_STRING);
self.reset_picker_selection(); self.reset_picker_selection();
self.mode = Mode::Channel; self.mode = Mode::Channel;
@ -528,7 +512,6 @@ impl Television {
.remote_control .remote_control
.as_ref() .as_ref()
.unwrap() .unwrap()
// FIXME: is the TelevisionChannel enum still worth it?
.zap(entry.name.as_str())?; .zap(entry.name.as_str())?;
// this resets the RC picker // this resets the RC picker
self.reset_picker_selection(); self.reset_picker_selection();

View File

@ -3,9 +3,7 @@ use std::{collections::HashSet, path::PathBuf, time::Duration};
use television::{ use television::{
action::Action, action::Action,
app::{App, AppOptions}, app::{App, AppOptions},
channels::cable::prototypes::{ channels::cable::prototypes::{Cable, ChannelPrototype},
CableChannelPrototype, CableChannelPrototypes,
},
config::default_config_from_file, config::default_config_from_file,
}; };
use tokio::{task::JoinHandle, time::timeout}; 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 /// The app is started in a separate task and can be interacted with by sending
/// actions to the action channel. /// actions to the action channel.
fn setup_app( fn setup_app(
channel_prototype: Option<CableChannelPrototype>, channel_prototype: Option<ChannelPrototype>,
select_1: bool, select_1: bool,
exact: bool, exact: bool,
) -> ( ) -> (
JoinHandle<television::app::AppOutput>, JoinHandle<television::app::AppOutput>,
tokio::sync::mpsc::UnboundedSender<Action>, tokio::sync::mpsc::UnboundedSender<Action>,
) { ) {
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")) let target_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests") .join("tests")
.join("target_dir"); .join("target_dir");
std::env::set_current_dir(&target_dir).unwrap(); std::env::set_current_dir(&target_dir).unwrap();
CableChannelPrototype::default() ChannelPrototype::default()
}); });
let mut config = default_config_from_file().unwrap(); let mut config = default_config_from_file().unwrap();
// this speeds up the tests // this speeds up the tests
@ -49,13 +47,7 @@ fn setup_app(
false, false,
config.application.tick_rate, config.application.tick_rate,
); );
let mut app = App::new( let mut app = App::new(chan, config, input, options, &Cable::default());
chan,
config,
input,
options,
&CableChannelPrototypes::default(),
);
// retrieve the app's action channel handle in order to send a quit action // retrieve the app's action channel handle in order to send a quit action
let tx = app.action_tx.clone(); 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)] #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
async fn test_app_exits_when_select_1_and_only_one_result() { async fn test_app_exits_when_select_1_and_only_one_result() {
let prototype = CableChannelPrototype::new( let prototype = ChannelPrototype::new(
"cable", "some_channel",
"echo file1.txt", "echo file1.txt",
false, false,
None, 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)] #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
async fn test_app_does_not_exit_when_select_1_and_more_than_one_result() { async fn test_app_does_not_exit_when_select_1_and_more_than_one_result() {
let prototype = CableChannelPrototype::new( let prototype = ChannelPrototype::new(
"cable", "some_channel",
"echo 'file1.txt\nfile2.txt'", "echo 'file1.txt\nfile2.txt'",
false, false,
None, None,