diff --git a/television/app.rs b/television/app.rs index f1f8909..b18a84b 100644 --- a/television/app.rs +++ b/television/app.rs @@ -100,6 +100,7 @@ impl App { config: Config, input: Option, select_1: bool, + no_remote: bool, ) -> Self { let (action_tx, action_rx) = mpsc::unbounded_channel(); let (render_tx, render_rx) = mpsc::unbounded_channel(); @@ -110,8 +111,13 @@ impl App { debug!("{:?}", keymap); let (ui_state_tx, ui_state_rx) = mpsc::unbounded_channel(); - let television = - Television::new(action_tx.clone(), channel, config, input); + let television = Television::new( + action_tx.clone(), + channel, + config, + input, + no_remote, + ); Self { keymap, @@ -401,6 +407,7 @@ mod test { Config::default(), None, true, + false, ); app.television .results_picker diff --git a/television/cli/args.rs b/television/cli/args.rs index 5206fa5..2ffb11b 100644 --- a/television/cli/args.rs +++ b/television/cli/args.rs @@ -102,6 +102,16 @@ pub struct Cli { #[arg(long, default_value = "false", verbatim_doc_comment)] pub select_1: bool, + /// Disable the remote control. + /// + /// This will disable the remote control panel and associated actions + /// entirely. This is useful when the remote control is not needed or + /// when the user wants `tv` to run in single-channel mode (e.g. when + /// using it as a file picker for a script or embedding it in a larger + /// application). + #[arg(long, default_value = "false", verbatim_doc_comment)] + pub no_remote: bool, + #[command(subcommand)] pub command: Option, } diff --git a/television/cli/mod.rs b/television/cli/mod.rs index 0eb1edd..d897ab2 100644 --- a/television/cli/mod.rs +++ b/television/cli/mod.rs @@ -30,6 +30,7 @@ pub struct PostProcessedCli { pub autocomplete_prompt: Option, pub keybindings: Option, pub select_1: bool, + pub no_remote: bool, } impl Default for PostProcessedCli { @@ -46,6 +47,7 @@ impl Default for PostProcessedCli { autocomplete_prompt: None, keybindings: None, select_1: false, + no_remote: false, } } } @@ -111,6 +113,7 @@ impl From for PostProcessedCli { autocomplete_prompt: cli.autocomplete_prompt, keybindings, select_1: cli.select_1, + no_remote: cli.no_remote, } } } @@ -326,6 +329,7 @@ mod tests { working_directory: Some("/home/user".to_string()), autocomplete_prompt: None, select_1: false, + no_remote: false, }; let post_processed_cli: PostProcessedCli = cli.into(); @@ -365,6 +369,7 @@ mod tests { working_directory: None, autocomplete_prompt: None, select_1: false, + no_remote: false, }; let post_processed_cli: PostProcessedCli = cli.into(); @@ -395,6 +400,7 @@ mod tests { working_directory: None, autocomplete_prompt: None, select_1: false, + no_remote: false, }; let post_processed_cli: PostProcessedCli = cli.into(); @@ -420,6 +426,7 @@ mod tests { working_directory: None, autocomplete_prompt: None, select_1: false, + no_remote: false, }; let post_processed_cli: PostProcessedCli = cli.into(); @@ -448,6 +455,7 @@ mod tests { working_directory: None, autocomplete_prompt: None, select_1: false, + no_remote: false, }; let post_processed_cli: PostProcessedCli = cli.into(); diff --git a/television/main.rs b/television/main.rs index 1c02fdd..e6c6120 100644 --- a/television/main.rs +++ b/television/main.rs @@ -65,7 +65,8 @@ async fn main() -> Result<()> { CLIPBOARD.with(<_>::default); debug!("Creating application..."); - let mut app = App::new(channel, config, args.input, args.select_1); + let mut app = + App::new(channel, config, args.input, args.select_1, args.no_remote); stdout().flush()?; debug!("Running application..."); let output = app.run(stdout().is_terminal(), false).await?; diff --git a/television/television.rs b/television/television.rs index d528ecc..c7333e0 100644 --- a/television/television.rs +++ b/television/television.rs @@ -35,7 +35,7 @@ pub struct Television { action_tx: UnboundedSender, pub config: Config, pub channel: TelevisionChannel, - pub remote_control: TelevisionChannel, + pub remote_control: Option, pub mode: Mode, pub current_pattern: String, pub results_picker: Picker, @@ -57,6 +57,7 @@ impl Television { mut channel: TelevisionChannel, config: Config, input: Option, + no_remote: bool, ) -> Self { let mut results_picker = Picker::new(input.clone()); if config.ui.input_bar_position == InputPosition::Bottom { @@ -87,13 +88,20 @@ impl Television { None, ); + let remote_control = if no_remote { + None + } else { + Some(TelevisionChannel::RemoteControl(RemoteControl::new( + builtin_channels, + Some(cable_channels), + ))) + }; + Self { action_tx, config, channel, - remote_control: TelevisionChannel::RemoteControl( - RemoteControl::new(builtin_channels, Some(cable_channels)), - ), + remote_control, mode: Mode::Channel, current_pattern: EMPTY_STRING.to_string(), results_picker, @@ -118,9 +126,9 @@ impl Television { let builtin_channels = load_builtin_channels(Some( &cable_channels.keys().collect::>(), )); - self.remote_control = TelevisionChannel::RemoteControl( + self.remote_control = Some(TelevisionChannel::RemoteControl( RemoteControl::new(builtin_channels, Some(cable_channels)), - ); + )); } pub fn dump_context(&self) -> Ctx { @@ -170,7 +178,7 @@ impl Television { self.channel.find(pattern); } Mode::RemoteControl | Mode::SendToChannel => { - self.remote_control.find(pattern); + self.remote_control.as_mut().unwrap().find(pattern); } } } @@ -188,6 +196,8 @@ impl Television { if let Some(i) = self.rc_picker.selected() { return self .remote_control + .as_ref() + .unwrap() .get_result(i.try_into().unwrap()); } None @@ -217,9 +227,10 @@ impl Television { Mode::Channel => { (self.channel.result_count(), &mut self.results_picker) } - Mode::RemoteControl | Mode::SendToChannel => { - (self.remote_control.total_count(), &mut self.rc_picker) - } + Mode::RemoteControl | Mode::SendToChannel => ( + self.remote_control.as_ref().unwrap().total_count(), + &mut self.rc_picker, + ), }; if result_count == 0 { return; @@ -236,9 +247,10 @@ impl Television { Mode::Channel => { (self.channel.result_count(), &mut self.results_picker) } - Mode::RemoteControl | Mode::SendToChannel => { - (self.remote_control.total_count(), &mut self.rc_picker) - } + Mode::RemoteControl | Mode::SendToChannel => ( + self.remote_control.as_ref().unwrap().total_count(), + &mut self.rc_picker, + ), }; if result_count == 0 { return; @@ -368,18 +380,20 @@ impl Television { pub fn update_rc_picker_state(&mut self) { if self.rc_picker.selected().is_none() - && self.remote_control.result_count() > 0 + && self.remote_control.as_ref().unwrap().result_count() > 0 { self.rc_picker.select(Some(0)); self.rc_picker.relative_select(Some(0)); } - self.rc_picker.entries = self.remote_control.results( - // this'll be more than the actual rc height but it's fine - self.ui_state.layout.results.height.into(), - u32::try_from(self.rc_picker.offset()).unwrap(), - ); - self.rc_picker.total_items = self.remote_control.total_count(); + self.rc_picker.entries = + self.remote_control.as_mut().unwrap().results( + // this'll be more than the actual rc height but it's fine + self.ui_state.layout.results.height.into(), + u32::try_from(self.rc_picker.offset()).unwrap(), + ); + self.rc_picker.total_items = + self.remote_control.as_ref().unwrap().total_count(); } pub fn handle_input_action(&mut self, action: &Action) { @@ -408,6 +422,9 @@ impl Television { } pub fn handle_toggle_rc(&mut self) { + if self.remote_control.is_none() { + return; + } match self.mode { Mode::Channel => { self.mode = Mode::RemoteControl; @@ -417,7 +434,7 @@ impl Television { // this resets the RC picker self.reset_picker_input(); self.init_remote_control(); - self.remote_control.find(EMPTY_STRING); + self.remote_control.as_mut().unwrap().find(EMPTY_STRING); self.reset_picker_selection(); self.mode = Mode::Channel; } @@ -426,16 +443,19 @@ impl Television { } pub fn handle_toggle_send_to_channel(&mut self) { + if self.remote_control.is_none() { + return; + } match self.mode { Mode::Channel | Mode::RemoteControl => { self.mode = Mode::SendToChannel; - self.remote_control = TelevisionChannel::RemoteControl( + self.remote_control = Some(TelevisionChannel::RemoteControl( RemoteControl::with_transitions_from(&self.channel), - ); + )); } Mode::SendToChannel => { self.reset_picker_input(); - self.remote_control.find(EMPTY_STRING); + self.remote_control.as_mut().unwrap().find(EMPTY_STRING); self.reset_picker_selection(); self.mode = Mode::Channel; } @@ -462,12 +482,15 @@ impl Television { } Mode::RemoteControl => { if let Some(entry) = self.get_selected_entry(None) { - let new_channel = - self.remote_control.zap(entry.name.as_str())?; + let new_channel = self + .remote_control + .as_ref() + .unwrap() + .zap(entry.name.as_str())?; // this resets the RC picker self.reset_picker_selection(); self.reset_picker_input(); - self.remote_control.find(EMPTY_STRING); + self.remote_control.as_mut().unwrap().find(EMPTY_STRING); self.mode = Mode::Channel; self.change_channel(new_channel); } @@ -479,7 +502,7 @@ impl Television { .transition_to(entry.name.as_str().try_into()?); self.reset_picker_selection(); self.reset_picker_input(); - self.remote_control.find(EMPTY_STRING); + self.remote_control.as_mut().unwrap().find(EMPTY_STRING); self.mode = Mode::Channel; self.change_channel(new_channel); } @@ -589,7 +612,9 @@ impl Television { self.update_results_picker_state(); - self.update_rc_picker_state(); + if self.remote_control.is_some() { + self.update_rc_picker_state(); + } let selected_entry = self .get_selected_entry(Some(Mode::Channel)) diff --git a/tests/app.rs b/tests/app.rs index f087215..5b5f631 100644 --- a/tests/app.rs +++ b/tests/app.rs @@ -39,7 +39,7 @@ fn setup_app( config.application.tick_rate = 100.0; let input = None; - let mut app = App::new(chan, config, input, select_1); + let mut app = App::new(chan, config, input, select_1, false); // retrieve the app's action channel handle in order to send a quit action let tx = app.action_tx.clone();