wip: a checkpoint with stdin as a regular channel which compiles

This commit is contained in:
Alexandre Pasmantier 2025-05-04 01:50:39 +02:00
parent 1bf67b537f
commit 11db510eb3
8 changed files with 81 additions and 92 deletions

View File

@ -14,7 +14,7 @@ use television::{
channels::{ channels::{
cable::prototypes::CableChannelPrototype, cable::prototypes::CableChannelPrototype,
entry::{into_ranges, Entry}, entry::{into_ranges, Entry},
OnAir, TelevisionChannel, OnAir,
}, },
config::{Config, ConfigEnv}, config::{Config, ConfigEnv},
screen::{colors::ResultsColorscheme, results::build_results_list}, screen::{colors::ResultsColorscheme, results::build_results_list},
@ -472,10 +472,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 mut channel = TelevisionChannel::Cable( let channel = CableChannelPrototype::default();
CableChannelPrototype::default().into(),
);
channel.find("television");
// 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,
@ -487,6 +484,7 @@ pub fn draw(c: &mut Criterion) {
false, false,
CableChannelPrototypes::default(), CableChannelPrototypes::default(),
); );
tv.find("television");
for _ in 0..5 { for _ in 0..5 {
// tick the matcher // tick the matcher
let _ = tv.channel.results(10, 0); let _ = tv.channel.results(10, 0);

View File

@ -4,8 +4,10 @@ use anyhow::Result;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::{debug, trace}; use tracing::{debug, trace};
use crate::channels::cable::prototypes::CableChannelPrototypes; use crate::channels::cable::prototypes::{
use crate::channels::{entry::Entry, OnAir, TelevisionChannel}; CableChannelPrototype, CableChannelPrototypes,
};
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;
@ -97,8 +99,6 @@ pub struct App {
/// Render task handle /// Render task handle
render_task: Option<tokio::task::JoinHandle<Result<()>>>, render_task: Option<tokio::task::JoinHandle<Result<()>>>,
options: AppOptions, options: AppOptions,
/// The cable channels that are available.
cable_channels: CableChannelPrototypes,
} }
/// The outcome of an action. /// The outcome of an action.
@ -138,7 +138,7 @@ const ACTION_BUF_SIZE: usize = 8;
impl App { impl App {
pub fn new( pub fn new(
channel: TelevisionChannel, channel_prototype: CableChannelPrototype,
config: Config, config: Config,
input: Option<String>, input: Option<String>,
options: AppOptions, options: AppOptions,
@ -154,7 +154,7 @@ impl App {
let (ui_state_tx, ui_state_rx) = mpsc::unbounded_channel(); let (ui_state_tx, ui_state_rx) = mpsc::unbounded_channel();
let television = Television::new( let television = Television::new(
action_tx.clone(), action_tx.clone(),
channel, channel_prototype,
config, config,
input, input,
options.no_remote, options.no_remote,
@ -178,7 +178,6 @@ impl App {
ui_state_tx, ui_state_tx,
render_task: None, render_task: None,
options, options,
cable_channels,
} }
} }
@ -443,14 +442,13 @@ impl App {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::channels::cable::Channel as CableChannel;
#[test] #[test]
fn test_maybe_select_1() { fn test_maybe_select_1() {
let mut app = App::new( let mut app = App::new(
TelevisionChannel::Cable(CableChannel::new( CableChannelPrototype::new(
"random", "cat", false, None, "random", "cat", false, None, None, None,
)), ),
Config::default(), Config::default(),
None, None,
AppOptions::default(), AppOptions::default(),

View File

@ -1,5 +1,6 @@
use crate::channels::entry::Entry; use crate::channels::entry::Entry;
use anyhow::Result; use anyhow::Result;
use cable::prototypes::CableChannelPrototype;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use television_derive::Broadcast; use television_derive::Broadcast;
@ -130,7 +131,7 @@ pub enum TelevisionChannel {
} }
impl TelevisionChannel { impl TelevisionChannel {
pub fn zap(&self, channel_name: &str) -> Result<TelevisionChannel> { pub fn zap(&self, channel_name: &str) -> Result<CableChannelPrototype> {
match self { match self {
TelevisionChannel::RemoteControl(remote_control) => { TelevisionChannel::RemoteControl(remote_control) => {
remote_control.zap(channel_name) remote_control.zap(channel_name)

View File

@ -1,6 +1,6 @@
use std::fmt::Display; use std::fmt::Display;
use super::entry::Entry; use super::{cable::prototypes::DEFAULT_DELIMITER, entry::Entry};
use crate::channels::cable::prototypes::CableChannelPrototype; use crate::channels::cable::prototypes::CableChannelPrototype;
use lazy_regex::{regex, Lazy, Regex}; use lazy_regex::{regex, Lazy, Regex};
@ -62,26 +62,23 @@ impl PreviewCommand {
} }
} }
impl TryFrom<&CableChannelPrototype> for PreviewCommand { impl From<&CableChannelPrototype> for Option<PreviewCommand> {
type Error = anyhow::Error; fn from(value: &CableChannelPrototype) -> Self {
if let Some(command) = value.preview_command.as_ref() {
let delimiter = value
.preview_delimiter
.as_ref()
.map_or(DEFAULT_DELIMITER, |v| v);
fn try_from(value: &CableChannelPrototype) -> Result<Self, Self::Error> { let offset_expr = value.preview_offset.clone();
let command = value
.preview_command
.as_ref()
.expect("Preview command is not set.");
let delimiter = value // FIXME: handle offset here (side note: we don't want to reparse the offset
.preview_delimiter // expression for each entry, so maybe just parse it once and try to store it
.as_ref() // as some sort of function we can call later on
.expect("Preview delimiter is not set"); Some(PreviewCommand::new(command, delimiter, offset_expr))
} else {
let offset_expr = value.preview_offset.clone(); None
}
// FIXME: handle offset here (side note: we don't want to reparse the offset
// expression for each entry, so maybe just parse it once and try to store it
// as some sort of function we can call later on
Ok(PreviewCommand::new(command, delimiter, offset_expr))
} }
} }

View File

@ -2,13 +2,13 @@ use std::collections::HashSet;
use crate::channels::cable::prototypes::CableChannelPrototypes; use crate::channels::cable::prototypes::CableChannelPrototypes;
use crate::channels::entry::Entry; use crate::channels::entry::Entry;
use crate::channels::{OnAir, TelevisionChannel}; 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 rustc_hash::{FxBuildHasher, FxHashSet};
use super::cable; use super::cable::prototypes::CableChannelPrototype;
pub struct RemoteControl { pub struct RemoteControl {
matcher: Matcher<String>, matcher: Matcher<String>,
@ -38,15 +38,13 @@ impl RemoteControl {
} }
} }
pub fn zap(&self, channel_name: &str) -> Result<TelevisionChannel> { pub fn zap(&self, channel_name: &str) -> Result<CableChannelPrototype> {
match self match self
.cable_channels .cable_channels
.as_ref() .as_ref()
.and_then(|channels| channels.get(channel_name).cloned()) .and_then(|channels| channels.get(channel_name).cloned())
{ {
Some(prototype) => { Some(prototype) => Ok(prototype),
Ok(TelevisionChannel::Cable(cable::Channel::from(prototype)))
}
None => Err(anyhow::anyhow!( None => Err(anyhow::anyhow!(
"No channel or cable channel prototype found for {}", "No channel or cable channel prototype found for {}",
channel_name channel_name

View File

@ -14,9 +14,6 @@ use television::utils::clipboard::CLIPBOARD;
use tracing::{debug, error, info}; use tracing::{debug, error, info};
use television::app::{App, AppOptions}; use television::app::{App, AppOptions};
use television::channels::{
stdin::Channel as StdinChannel, TelevisionChannel,
};
use television::cli::{ use television::cli::{
args::{Cli, Command}, args::{Cli, Command},
guess_channel_from_prompt, list_channels, PostProcessedCli, guess_channel_from_prompt, list_channels, PostProcessedCli,
@ -164,22 +161,22 @@ pub fn determine_channel(
) -> Result<CableChannelPrototype> { ) -> Result<CableChannelPrototype> {
if readable_stdin { if readable_stdin {
debug!("Using stdin channel"); debug!("Using stdin channel");
Ok(TelevisionChannel::Stdin(StdinChannel::new( Ok(CableChannelPrototype::new(
args.preview_kind, "STDIN", "cat", false, None, None, None,
))) ))
} 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 = guess_channel_from_prompt( let channel_prototype = guess_channel_from_prompt(
&prompt, &prompt,
&config.shell_integration.commands, &config.shell_integration.commands,
&config.shell_integration.fallback_channel, &config.shell_integration.fallback_channel,
cable_channels, cable_channels,
)?; )?;
debug!("Using guessed channel: {:?}", channel); debug!("Using guessed channel: {:?}", channel_prototype);
Ok(TelevisionChannel::Cable(channel.into())) Ok(channel_prototype)
} else { } else {
debug!("Using {:?} channel", args.channel); debug!("Using {:?} channel", args.channel);
Ok(TelevisionChannel::Cable(args.channel.into())) Ok(args.channel)
} }
} }
@ -188,9 +185,7 @@ mod tests {
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use television::{ use television::{
cable::load_cable_channels, cable::load_cable_channels,
channels::{ channels::cable::prototypes::CableChannelPrototype,
cable::prototypes::CableChannelPrototype, preview::PreviewType,
},
}; };
use super::*; use super::*;
@ -199,7 +194,7 @@ mod tests {
args: &PostProcessedCli, args: &PostProcessedCli,
config: &Config, config: &Config,
readable_stdin: bool, readable_stdin: bool,
expected_channel: &TelevisionChannel, expected_channel: &CableChannelPrototype,
cable_channels: Option<CableChannelPrototypes>, cable_channels: Option<CableChannelPrototypes>,
) { ) {
let channels: CableChannelPrototypes = cable_channels let channels: CableChannelPrototypes = cable_channels
@ -209,11 +204,9 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
channel.name(), channel.name, expected_channel.name,
expected_channel.name(),
"Expected {:?} but got {:?}", "Expected {:?} but got {:?}",
expected_channel.name(), expected_channel.name, channel.name
channel.name()
); );
} }
@ -230,7 +223,9 @@ mod tests {
&args, &args,
&config, &config,
true, true,
&TelevisionChannel::Stdin(StdinChannel::new(PreviewType::None)), &CableChannelPrototype::new(
"STDIN", "cat", false, None, None, None,
),
None, None,
); );
} }
@ -238,11 +233,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 = TelevisionChannel::Cable( let expected_channel = CableChannelPrototype::new(
CableChannelPrototype::new( "dirs", "ls {}", false, None, None, None,
"dirs", "ls {}", false, None, None, None,
)
.into(),
); );
let args = PostProcessedCli { let args = PostProcessedCli {
autocomplete_prompt, autocomplete_prompt,
@ -286,12 +278,7 @@ mod tests {
&args, &args,
&config, &config,
false, false,
&TelevisionChannel::Cable( &CableChannelPrototype::new("dirs", "", false, None, None, None),
CableChannelPrototype::new(
"dirs", "", false, None, None, None,
)
.into(),
),
None, None,
); );
} }

View File

@ -1,3 +1,4 @@
use crate::channels::cable::prototypes::CableChannelPrototype;
use crate::preview::{Preview, PreviewContent}; use crate::preview::{Preview, PreviewContent};
use crate::utils::cache::RingSet; use crate::utils::cache::RingSet;
use crate::utils::command::shell_command; use crate::utils::command::shell_command;
@ -150,3 +151,13 @@ pub fn try_preview(
concurrent_tasks.fetch_sub(1, Ordering::Relaxed); concurrent_tasks.fetch_sub(1, Ordering::Relaxed);
in_flight_previews.lock().remove(&entry.name); in_flight_previews.lock().remove(&entry.name);
} }
impl From<&CableChannelPrototype> for Option<Previewer> {
fn from(value: &CableChannelPrototype) -> Self {
if let Some(preview_command) = value.into() {
Some(Previewer::new(preview_command))
} else {
None
}
}
}

View File

@ -2,7 +2,10 @@ use crate::{
action::Action, action::Action,
cable::load_cable_channels, cable::load_cable_channels,
channels::{ channels::{
cable::{prototypes::CableChannelPrototypes, Channel as CableChannel}, cable::{
prototypes::{CableChannelPrototype, CableChannelPrototypes},
Channel as CableChannel,
},
entry::{Entry, ENTRY_PLACEHOLDER}, entry::{Entry, ENTRY_PLACEHOLDER},
remote_control::RemoteControl, remote_control::RemoteControl,
// stdin::Channel as StdinChannel, // stdin::Channel as StdinChannel,
@ -46,7 +49,7 @@ pub enum MatchingMode {
pub struct Television { pub struct Television {
action_tx: UnboundedSender<Action>, action_tx: UnboundedSender<Action>,
pub config: Config, pub config: Config,
pub channel: TelevisionChannel, pub channel: CableChannel,
pub remote_control: Option<TelevisionChannel>, pub remote_control: Option<TelevisionChannel>,
pub mode: Mode, pub mode: Mode,
pub current_pattern: String, pub current_pattern: String,
@ -68,7 +71,7 @@ impl Television {
#[must_use] #[must_use]
pub fn new( pub fn new(
action_tx: UnboundedSender<Action>, action_tx: UnboundedSender<Action>,
mut channel: TelevisionChannel, channel_prototype: CableChannelPrototype,
mut config: Config, mut config: Config,
input: Option<String>, input: Option<String>,
no_remote: bool, no_remote: bool,
@ -81,17 +84,8 @@ impl Television {
results_picker = results_picker.inverted(); results_picker = results_picker.inverted();
} }
let previewer = match &channel { let previewer: Option<Previewer> = (&channel_prototype).into();
// TelevisionChannel::Stdin(StdinChannel { let mut channel: CableChannel = channel_prototype.into();
// preview_command: command,
// ..
// }) |
TelevisionChannel::Cable(CableChannel {
preview_command: command,
..
}) => command.clone().map(Previewer::new),
_ => None,
};
let app_metadata = AppMetadata::new( let app_metadata = AppMetadata::new(
env!("CARGO_PKG_VERSION").to_string(), env!("CARGO_PKG_VERSION").to_string(),
@ -166,7 +160,7 @@ impl Television {
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(), self.channel.name.clone(),
self.channel.selected_entries().clone(), self.channel.selected_entries().clone(),
self.channel.total_count(), self.channel.total_count(),
self.channel.running(), self.channel.running(),
@ -193,20 +187,24 @@ impl Television {
} }
pub fn current_channel(&self) -> String { pub fn current_channel(&self) -> String {
self.channel.name() self.channel.name.clone()
} }
pub fn change_channel(&mut self, channel: TelevisionChannel) { pub fn change_channel(
&mut self,
channel_prototype: CableChannelPrototype,
) {
self.preview_state.reset(); self.preview_state.reset();
self.preview_state.enabled = channel.supports_preview(); self.preview_state.enabled =
channel_prototype.preview_command.is_some();
self.reset_picker_selection(); self.reset_picker_selection();
self.reset_picker_input(); self.reset_picker_input();
self.current_pattern = EMPTY_STRING.to_string(); self.current_pattern = EMPTY_STRING.to_string();
self.channel.shutdown(); self.channel.shutdown();
self.channel = channel; self.channel = channel_prototype.into();
} }
fn find(&mut self, pattern: &str) { pub fn find(&mut self, pattern: &str) {
match self.mode { match self.mode {
Mode::Channel => { Mode::Channel => {
self.channel.find( self.channel.find(
@ -530,6 +528,7 @@ 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();