mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 12:05:34 +00:00
feat(cli): add a --no-help
flag to allow disabling showing the help panel (#456)
This will disable the help panel and associated toggling actions entirely. This is useful when the help panel is not needed or when the user wants `tv` to run with a minimal interface (e.g. when using it as a file picker for a script or embedding it in a larger application).
This commit is contained in:
parent
b81873738a
commit
5bf3d20c83
@ -30,7 +30,8 @@ fn draw(c: &mut Criterion) {
|
|||||||
]));
|
]));
|
||||||
channel.find("television");
|
channel.find("television");
|
||||||
// Wait for the channel to finish loading
|
// Wait for the channel to finish loading
|
||||||
let mut tv = Television::new(tx, channel, config, None);
|
let mut tv =
|
||||||
|
Television::new(tx, channel, config, None, false, false);
|
||||||
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);
|
||||||
|
@ -6,7 +6,7 @@ use tracing::{debug, trace};
|
|||||||
|
|
||||||
use crate::channels::entry::{Entry, PreviewType};
|
use crate::channels::entry::{Entry, PreviewType};
|
||||||
use crate::channels::{OnAir, TelevisionChannel};
|
use crate::channels::{OnAir, TelevisionChannel};
|
||||||
use crate::config::Config;
|
use crate::config::{default_tick_rate, Config};
|
||||||
use crate::keymap::Keymap;
|
use crate::keymap::Keymap;
|
||||||
use crate::render::UiState;
|
use crate::render::UiState;
|
||||||
use crate::television::{Mode, Television};
|
use crate::television::{Mode, Television};
|
||||||
@ -16,12 +16,47 @@ use crate::{
|
|||||||
render::{render, RenderingTask},
|
render::{render, RenderingTask},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub struct AppOptions {
|
||||||
|
/// Whether the application should automatically select the first entry if there is only one
|
||||||
|
/// entry available.
|
||||||
|
pub select_1: bool,
|
||||||
|
/// Whether the application should disable the remote control feature.
|
||||||
|
pub no_remote: bool,
|
||||||
|
/// Whether the application should disable the help panel feature.
|
||||||
|
pub no_help: bool,
|
||||||
|
pub tick_rate: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AppOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
select_1: false,
|
||||||
|
no_remote: false,
|
||||||
|
no_help: false,
|
||||||
|
tick_rate: default_tick_rate(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppOptions {
|
||||||
|
pub fn new(
|
||||||
|
select_1: bool,
|
||||||
|
no_remote: bool,
|
||||||
|
no_help: bool,
|
||||||
|
tick_rate: f64,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
select_1,
|
||||||
|
no_remote,
|
||||||
|
no_help,
|
||||||
|
tick_rate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The main application struct that holds the state of the application.
|
/// The main application struct that holds the state of the application.
|
||||||
pub struct App {
|
pub struct App {
|
||||||
keymap: Keymap,
|
keymap: Keymap,
|
||||||
// maybe move these two into config instead of passing them
|
|
||||||
// via the cli?
|
|
||||||
tick_rate: f64,
|
|
||||||
/// The television instance that handles channels and entries.
|
/// The television instance that handles channels and entries.
|
||||||
television: Television,
|
television: Television,
|
||||||
/// A flag that indicates whether the application should quit during the next frame.
|
/// A flag that indicates whether the application should quit during the next frame.
|
||||||
@ -53,9 +88,7 @@ pub struct App {
|
|||||||
ui_state_tx: mpsc::UnboundedSender<UiState>,
|
ui_state_tx: mpsc::UnboundedSender<UiState>,
|
||||||
/// Render task handle
|
/// Render task handle
|
||||||
render_task: Option<tokio::task::JoinHandle<Result<()>>>,
|
render_task: Option<tokio::task::JoinHandle<Result<()>>>,
|
||||||
/// Whether the application should automatically select the first entry if there is only one
|
options: AppOptions,
|
||||||
/// entry available.
|
|
||||||
select_1: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The outcome of an action.
|
/// The outcome of an action.
|
||||||
@ -99,14 +132,12 @@ impl App {
|
|||||||
channel: TelevisionChannel,
|
channel: TelevisionChannel,
|
||||||
config: Config,
|
config: Config,
|
||||||
input: Option<String>,
|
input: Option<String>,
|
||||||
select_1: bool,
|
options: AppOptions,
|
||||||
no_remote: bool,
|
|
||||||
) -> 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();
|
||||||
let (_, event_rx) = mpsc::unbounded_channel();
|
let (_, event_rx) = mpsc::unbounded_channel();
|
||||||
let (event_abort_tx, _) = mpsc::unbounded_channel();
|
let (event_abort_tx, _) = mpsc::unbounded_channel();
|
||||||
let tick_rate = config.application.tick_rate;
|
|
||||||
let keymap = Keymap::from(&config.keybindings);
|
let keymap = Keymap::from(&config.keybindings);
|
||||||
|
|
||||||
debug!("{:?}", keymap);
|
debug!("{:?}", keymap);
|
||||||
@ -116,12 +147,12 @@ impl App {
|
|||||||
channel,
|
channel,
|
||||||
config,
|
config,
|
||||||
input,
|
input,
|
||||||
no_remote,
|
options.no_remote,
|
||||||
|
options.no_help,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
keymap,
|
keymap,
|
||||||
tick_rate,
|
|
||||||
television,
|
television,
|
||||||
should_quit: false,
|
should_quit: false,
|
||||||
should_suspend: false,
|
should_suspend: false,
|
||||||
@ -134,7 +165,7 @@ impl App {
|
|||||||
ui_state_rx,
|
ui_state_rx,
|
||||||
ui_state_tx,
|
ui_state_tx,
|
||||||
render_task: None,
|
render_task: None,
|
||||||
select_1,
|
options,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +191,7 @@ impl App {
|
|||||||
// Event loop
|
// Event loop
|
||||||
if !headless {
|
if !headless {
|
||||||
debug!("Starting backend event loop");
|
debug!("Starting backend event loop");
|
||||||
let event_loop = EventLoop::new(self.tick_rate, true);
|
let event_loop = EventLoop::new(self.options.tick_rate, true);
|
||||||
self.event_rx = event_loop.rx;
|
self.event_rx = event_loop.rx;
|
||||||
self.event_abort_tx = event_loop.abort_tx;
|
self.event_abort_tx = event_loop.abort_tx;
|
||||||
}
|
}
|
||||||
@ -210,7 +241,7 @@ impl App {
|
|||||||
|
|
||||||
// If `self.select_1` is true, the channel is not running, and there is
|
// If `self.select_1` is true, the channel is not running, and there is
|
||||||
// only one entry available, automatically select the first entry.
|
// only one entry available, automatically select the first entry.
|
||||||
if self.select_1
|
if self.options.select_1
|
||||||
&& !self.television.channel.running()
|
&& !self.television.channel.running()
|
||||||
&& self.television.channel.total_count() == 1
|
&& self.television.channel.total_count() == 1
|
||||||
{
|
{
|
||||||
@ -406,8 +437,7 @@ mod test {
|
|||||||
TelevisionChannel::Stdin(StdinChannel::new(PreviewType::None)),
|
TelevisionChannel::Stdin(StdinChannel::new(PreviewType::None)),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
None,
|
None,
|
||||||
true,
|
AppOptions::default(),
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
app.television
|
app.television
|
||||||
.results_picker
|
.results_picker
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use clap::{Parser, Subcommand, ValueEnum};
|
use clap::{Parser, Subcommand, ValueEnum};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
|
#[derive(Parser, Debug, Default)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
/// Which channel shall we watch?
|
/// Which channel shall we watch?
|
||||||
@ -112,6 +113,16 @@ pub struct Cli {
|
|||||||
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
||||||
pub no_remote: bool,
|
pub no_remote: bool,
|
||||||
|
|
||||||
|
/// Disable the help panel.
|
||||||
|
///
|
||||||
|
/// This will disable the help panel and associated toggling actions
|
||||||
|
/// entirely. This is useful when the help panel is not needed or
|
||||||
|
/// when the user wants `tv` to run with a minimal interface (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_help: bool,
|
||||||
|
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: Option<Command>,
|
pub command: Option<Command>,
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ use crate::{
|
|||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
|
|
||||||
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PostProcessedCli {
|
pub struct PostProcessedCli {
|
||||||
pub channel: ParsedCliChannel,
|
pub channel: ParsedCliChannel,
|
||||||
@ -31,6 +32,7 @@ pub struct PostProcessedCli {
|
|||||||
pub keybindings: Option<KeyBindings>,
|
pub keybindings: Option<KeyBindings>,
|
||||||
pub select_1: bool,
|
pub select_1: bool,
|
||||||
pub no_remote: bool,
|
pub no_remote: bool,
|
||||||
|
pub no_help: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PostProcessedCli {
|
impl Default for PostProcessedCli {
|
||||||
@ -48,6 +50,7 @@ impl Default for PostProcessedCli {
|
|||||||
keybindings: None,
|
keybindings: None,
|
||||||
select_1: false,
|
select_1: false,
|
||||||
no_remote: false,
|
no_remote: false,
|
||||||
|
no_help: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,6 +117,7 @@ impl From<Cli> for PostProcessedCli {
|
|||||||
keybindings,
|
keybindings,
|
||||||
select_1: cli.select_1,
|
select_1: cli.select_1,
|
||||||
no_remote: cli.no_remote,
|
no_remote: cli.no_remote,
|
||||||
|
no_help: cli.no_help,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -319,17 +323,9 @@ mod tests {
|
|||||||
let cli = Cli {
|
let cli = Cli {
|
||||||
channel: "files".to_string(),
|
channel: "files".to_string(),
|
||||||
preview: Some("bat -n --color=always {}".to_string()),
|
preview: Some("bat -n --color=always {}".to_string()),
|
||||||
no_preview: false,
|
|
||||||
delimiter: ":".to_string(),
|
delimiter: ":".to_string(),
|
||||||
tick_rate: Some(50.0),
|
|
||||||
frame_rate: Some(60.0),
|
|
||||||
keybindings: None,
|
|
||||||
input: None,
|
|
||||||
command: None,
|
|
||||||
working_directory: Some("/home/user".to_string()),
|
working_directory: Some("/home/user".to_string()),
|
||||||
autocomplete_prompt: None,
|
..Default::default()
|
||||||
select_1: false,
|
|
||||||
no_remote: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let post_processed_cli: PostProcessedCli = cli.into();
|
let post_processed_cli: PostProcessedCli = cli.into();
|
||||||
@ -345,8 +341,8 @@ mod tests {
|
|||||||
delimiter: ":".to_string()
|
delimiter: ":".to_string()
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(post_processed_cli.tick_rate, Some(50.0));
|
assert_eq!(post_processed_cli.tick_rate, None);
|
||||||
assert_eq!(post_processed_cli.frame_rate, Some(60.0));
|
assert_eq!(post_processed_cli.frame_rate, None);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
post_processed_cli.working_directory,
|
post_processed_cli.working_directory,
|
||||||
Some("/home/user".to_string())
|
Some("/home/user".to_string())
|
||||||
@ -358,18 +354,8 @@ mod tests {
|
|||||||
fn test_from_cli_no_args() {
|
fn test_from_cli_no_args() {
|
||||||
let cli = Cli {
|
let cli = Cli {
|
||||||
channel: ".".to_string(),
|
channel: ".".to_string(),
|
||||||
preview: None,
|
|
||||||
no_preview: false,
|
|
||||||
delimiter: ":".to_string(),
|
delimiter: ":".to_string(),
|
||||||
tick_rate: Some(50.0),
|
..Default::default()
|
||||||
frame_rate: Some(60.0),
|
|
||||||
keybindings: None,
|
|
||||||
input: None,
|
|
||||||
command: None,
|
|
||||||
working_directory: None,
|
|
||||||
autocomplete_prompt: None,
|
|
||||||
select_1: false,
|
|
||||||
no_remote: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let post_processed_cli: PostProcessedCli = cli.into();
|
let post_processed_cli: PostProcessedCli = cli.into();
|
||||||
@ -390,17 +376,8 @@ mod tests {
|
|||||||
let cli = Cli {
|
let cli = Cli {
|
||||||
channel: "files".to_string(),
|
channel: "files".to_string(),
|
||||||
preview: Some(":files:".to_string()),
|
preview: Some(":files:".to_string()),
|
||||||
no_preview: false,
|
|
||||||
delimiter: ":".to_string(),
|
delimiter: ":".to_string(),
|
||||||
tick_rate: Some(50.0),
|
..Default::default()
|
||||||
frame_rate: Some(60.0),
|
|
||||||
keybindings: None,
|
|
||||||
input: None,
|
|
||||||
command: None,
|
|
||||||
working_directory: None,
|
|
||||||
autocomplete_prompt: None,
|
|
||||||
select_1: false,
|
|
||||||
no_remote: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let post_processed_cli: PostProcessedCli = cli.into();
|
let post_processed_cli: PostProcessedCli = cli.into();
|
||||||
@ -416,17 +393,8 @@ mod tests {
|
|||||||
let cli = Cli {
|
let cli = Cli {
|
||||||
channel: "files".to_string(),
|
channel: "files".to_string(),
|
||||||
preview: Some(":env_var:".to_string()),
|
preview: Some(":env_var:".to_string()),
|
||||||
no_preview: false,
|
|
||||||
delimiter: ":".to_string(),
|
delimiter: ":".to_string(),
|
||||||
tick_rate: Some(50.0),
|
..Default::default()
|
||||||
frame_rate: Some(60.0),
|
|
||||||
keybindings: None,
|
|
||||||
input: None,
|
|
||||||
command: None,
|
|
||||||
working_directory: None,
|
|
||||||
autocomplete_prompt: None,
|
|
||||||
select_1: false,
|
|
||||||
no_remote: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let post_processed_cli: PostProcessedCli = cli.into();
|
let post_processed_cli: PostProcessedCli = cli.into();
|
||||||
@ -442,20 +410,12 @@ mod tests {
|
|||||||
let cli = Cli {
|
let cli = Cli {
|
||||||
channel: "files".to_string(),
|
channel: "files".to_string(),
|
||||||
preview: Some(":env_var:".to_string()),
|
preview: Some(":env_var:".to_string()),
|
||||||
no_preview: false,
|
|
||||||
delimiter: ":".to_string(),
|
delimiter: ":".to_string(),
|
||||||
tick_rate: Some(50.0),
|
|
||||||
frame_rate: Some(60.0),
|
|
||||||
keybindings: Some(
|
keybindings: Some(
|
||||||
"quit=\"esc\";select_next_entry=[\"down\",\"ctrl-j\"]"
|
"quit=\"esc\";select_next_entry=[\"down\",\"ctrl-j\"]"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
),
|
),
|
||||||
input: None,
|
..Default::default()
|
||||||
command: None,
|
|
||||||
working_directory: None,
|
|
||||||
autocomplete_prompt: None,
|
|
||||||
select_1: false,
|
|
||||||
no_remote: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let post_processed_cli: PostProcessedCli = cli.into();
|
let post_processed_cli: PostProcessedCli = cli.into();
|
||||||
|
@ -260,7 +260,7 @@ fn default_frame_rate() -> f64 {
|
|||||||
60.0
|
60.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_tick_rate() -> f64 {
|
pub fn default_tick_rate() -> f64 {
|
||||||
50.0
|
50.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ use television::cli::parse_channel;
|
|||||||
use television::utils::clipboard::CLIPBOARD;
|
use television::utils::clipboard::CLIPBOARD;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
use television::app::App;
|
use television::app::{App, AppOptions};
|
||||||
use television::channels::{
|
use television::channels::{
|
||||||
entry::PreviewType, stdin::Channel as StdinChannel, TelevisionChannel,
|
entry::PreviewType, stdin::Channel as StdinChannel, TelevisionChannel,
|
||||||
};
|
};
|
||||||
@ -65,8 +65,13 @@ async fn main() -> Result<()> {
|
|||||||
CLIPBOARD.with(<_>::default);
|
CLIPBOARD.with(<_>::default);
|
||||||
|
|
||||||
debug!("Creating application...");
|
debug!("Creating application...");
|
||||||
let mut app =
|
let options = AppOptions::new(
|
||||||
App::new(channel, config, args.input, args.select_1, args.no_remote);
|
args.select_1,
|
||||||
|
args.no_remote,
|
||||||
|
args.no_help,
|
||||||
|
config.application.tick_rate,
|
||||||
|
);
|
||||||
|
let mut app = App::new(channel, config, args.input, options);
|
||||||
stdout().flush()?;
|
stdout().flush()?;
|
||||||
debug!("Running application...");
|
debug!("Running application...");
|
||||||
let output = app.run(stdout().is_terminal(), false).await?;
|
let output = app.run(stdout().is_terminal(), false).await?;
|
||||||
|
@ -48,6 +48,7 @@ pub struct Television {
|
|||||||
pub colorscheme: Colorscheme,
|
pub colorscheme: Colorscheme,
|
||||||
pub ticks: u64,
|
pub ticks: u64,
|
||||||
pub ui_state: UiState,
|
pub ui_state: UiState,
|
||||||
|
pub no_help: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Television {
|
impl Television {
|
||||||
@ -55,9 +56,10 @@ impl Television {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
action_tx: UnboundedSender<Action>,
|
action_tx: UnboundedSender<Action>,
|
||||||
mut channel: TelevisionChannel,
|
mut channel: TelevisionChannel,
|
||||||
config: Config,
|
mut config: Config,
|
||||||
input: Option<String>,
|
input: Option<String>,
|
||||||
no_remote: bool,
|
no_remote: bool,
|
||||||
|
no_help: bool,
|
||||||
) -> 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 {
|
||||||
@ -97,6 +99,10 @@ impl Television {
|
|||||||
)))
|
)))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if no_help {
|
||||||
|
config.ui.show_help_bar = false;
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
action_tx,
|
action_tx,
|
||||||
config,
|
config,
|
||||||
@ -114,6 +120,7 @@ impl Television {
|
|||||||
colorscheme,
|
colorscheme,
|
||||||
ticks: 0,
|
ticks: 0,
|
||||||
ui_state: UiState::default(),
|
ui_state: UiState::default(),
|
||||||
|
no_help,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -592,6 +599,9 @@ impl Television {
|
|||||||
self.handle_toggle_send_to_channel();
|
self.handle_toggle_send_to_channel();
|
||||||
}
|
}
|
||||||
Action::ToggleHelp => {
|
Action::ToggleHelp => {
|
||||||
|
if self.no_help {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
self.config.ui.show_help_bar = !self.config.ui.show_help_bar;
|
self.config.ui.show_help_bar = !self.config.ui.show_help_bar;
|
||||||
}
|
}
|
||||||
Action::TogglePreview => {
|
Action::TogglePreview => {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use std::{collections::HashSet, path::PathBuf, time::Duration};
|
use std::{collections::HashSet, path::PathBuf, time::Duration};
|
||||||
|
|
||||||
use television::{
|
use television::{
|
||||||
action::Action, app::App, channels::TelevisionChannel,
|
action::Action,
|
||||||
|
app::{App, AppOptions},
|
||||||
|
channels::TelevisionChannel,
|
||||||
config::default_config_from_file,
|
config::default_config_from_file,
|
||||||
};
|
};
|
||||||
use tokio::{task::JoinHandle, time::timeout};
|
use tokio::{task::JoinHandle, time::timeout};
|
||||||
@ -39,7 +41,9 @@ fn setup_app(
|
|||||||
config.application.tick_rate = 100.0;
|
config.application.tick_rate = 100.0;
|
||||||
let input = None;
|
let input = None;
|
||||||
|
|
||||||
let mut app = App::new(chan, config, input, select_1, false);
|
let options =
|
||||||
|
AppOptions::new(select_1, false, false, config.application.tick_rate);
|
||||||
|
let mut app = App::new(chan, config, input, options);
|
||||||
|
|
||||||
// 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();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user