mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-01 09:00:16 +00:00
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:
parent
58d73dbeba
commit
2b2654b6aa
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -1424,7 +1424,6 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"signal-hook",
|
||||
"television-derive",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"toml",
|
||||
@ -1434,15 +1433,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "television-derive"
|
||||
version = "0.0.27"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.19.1"
|
||||
|
@ -31,8 +31,6 @@ build = "build.rs"
|
||||
path = "television/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
television-derive = { path = "television-derive", version = "0.0.27" }
|
||||
|
||||
anyhow = "1.0"
|
||||
base64 = "0.22.1"
|
||||
directories = "6.0"
|
||||
|
@ -8,13 +8,12 @@ use ratatui::prelude::{Line, Style};
|
||||
use ratatui::style::Color;
|
||||
use ratatui::widgets::{Block, BorderType, Borders, ListDirection, Padding};
|
||||
use ratatui::Terminal;
|
||||
use television::channels::cable::prototypes::CableChannelPrototypes;
|
||||
use television::channels::cable::prototypes::Cable;
|
||||
use television::{
|
||||
action::Action,
|
||||
channels::{
|
||||
cable::prototypes::CableChannelPrototype,
|
||||
cable::prototypes::ChannelPrototype,
|
||||
entry::{into_ranges, Entry},
|
||||
OnAir,
|
||||
},
|
||||
config::{Config, ConfigEnv},
|
||||
screen::{colors::ResultsColorscheme, results::build_results_list},
|
||||
@ -22,6 +21,7 @@ use television::{
|
||||
};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn draw_results_list(c: &mut Criterion) {
|
||||
// FIXME: there's probably a way to have this as a benchmark asset
|
||||
// possible as a JSON file and to load it for the benchmark using Serde
|
||||
@ -458,6 +458,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub fn draw(c: &mut Criterion) {
|
||||
let width = 250;
|
||||
let height = 80;
|
||||
@ -472,7 +473,7 @@ pub fn draw(c: &mut Criterion) {
|
||||
let backend = TestBackend::new(width, height);
|
||||
let terminal = Terminal::new(backend).unwrap();
|
||||
let (tx, _) = tokio::sync::mpsc::unbounded_channel();
|
||||
let channel = CableChannelPrototype::default();
|
||||
let channel = ChannelPrototype::default();
|
||||
// Wait for the channel to finish loading
|
||||
let mut tv = Television::new(
|
||||
tx,
|
||||
@ -482,7 +483,7 @@ pub fn draw(c: &mut Criterion) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
CableChannelPrototypes::default(),
|
||||
Cable::default(),
|
||||
);
|
||||
tv.find("television");
|
||||
for _ in 0..5 {
|
||||
|
@ -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
|
@ -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()
|
||||
}
|
@ -4,10 +4,8 @@ use anyhow::Result;
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use crate::channels::cable::prototypes::{
|
||||
CableChannelPrototype, CableChannelPrototypes,
|
||||
};
|
||||
use crate::channels::{entry::Entry, OnAir};
|
||||
use crate::channels::cable::prototypes::{Cable, ChannelPrototype};
|
||||
use crate::channels::entry::Entry;
|
||||
use crate::config::{default_tick_rate, Config};
|
||||
use crate::keymap::Keymap;
|
||||
use crate::render::UiState;
|
||||
@ -138,11 +136,11 @@ const ACTION_BUF_SIZE: usize = 8;
|
||||
|
||||
impl App {
|
||||
pub fn new(
|
||||
channel_prototype: CableChannelPrototype,
|
||||
channel_prototype: ChannelPrototype,
|
||||
config: Config,
|
||||
input: Option<String>,
|
||||
options: AppOptions,
|
||||
cable_channels: &CableChannelPrototypes,
|
||||
cable_channels: &Cable,
|
||||
) -> Self {
|
||||
let (action_tx, action_rx) = mpsc::unbounded_channel();
|
||||
let (render_tx, render_rx) = mpsc::unbounded_channel();
|
||||
@ -160,7 +158,7 @@ impl App {
|
||||
options.no_remote,
|
||||
options.no_help,
|
||||
options.exact,
|
||||
CableChannelPrototypes((*cable_channels).clone()),
|
||||
cable_channels.clone(),
|
||||
);
|
||||
|
||||
Self {
|
||||
|
@ -6,17 +6,15 @@ use anyhow::Result;
|
||||
use tracing::{debug, error};
|
||||
|
||||
use crate::{
|
||||
channels::cable::prototypes::{
|
||||
CableChannelPrototype, CableChannelPrototypes,
|
||||
},
|
||||
channels::cable::prototypes::{Cable, ChannelPrototype},
|
||||
config::get_config_dir,
|
||||
};
|
||||
|
||||
/// Just a proxy struct to deserialize prototypes
|
||||
#[derive(Debug, serde::Deserialize, Default)]
|
||||
pub struct SerializedChannelPrototypes {
|
||||
pub struct CableSpec {
|
||||
#[serde(rename = "cable_channel")]
|
||||
pub prototypes: Vec<CableChannelPrototype>,
|
||||
pub prototypes: Vec<ChannelPrototype>,
|
||||
}
|
||||
|
||||
const CABLE_FILE_NAME_SUFFIX: &str = "channels";
|
||||
@ -42,7 +40,7 @@ const DEFAULT_CABLE_CHANNELS: &str =
|
||||
/// ├── my_channels.toml
|
||||
/// └── windows_channels.toml
|
||||
/// ```
|
||||
pub fn load_cable_channels() -> Result<CableChannelPrototypes> {
|
||||
pub fn load_cable() -> Result<Cable> {
|
||||
let config_dir = get_config_dir();
|
||||
|
||||
// list all files in the config directory
|
||||
@ -60,13 +58,13 @@ pub fn load_cable_channels() -> Result<CableChannelPrototypes> {
|
||||
}
|
||||
|
||||
let default_prototypes =
|
||||
toml::from_str::<SerializedChannelPrototypes>(DEFAULT_CABLE_CHANNELS)
|
||||
toml::from_str::<CableSpec>(DEFAULT_CABLE_CHANNELS)
|
||||
.expect("Failed to parse default cable channels");
|
||||
|
||||
let prototypes = file_paths.iter().fold(
|
||||
Vec::<CableChannelPrototype>::new(),
|
||||
Vec::<ChannelPrototype>::new(),
|
||||
|mut acc, p| {
|
||||
match toml::from_str::<SerializedChannelPrototypes>(
|
||||
match toml::from_str::<CableSpec>(
|
||||
&std::fs::read_to_string(p)
|
||||
.expect("Unable to read configuration file"),
|
||||
) {
|
||||
@ -97,7 +95,7 @@ pub fn load_cable_channels() -> Result<CableChannelPrototypes> {
|
||||
{
|
||||
cable_channels.insert(prototype.name.clone(), prototype);
|
||||
}
|
||||
Ok(CableChannelPrototypes(cable_channels))
|
||||
Ok(Cable(cable_channels))
|
||||
}
|
||||
|
||||
fn is_cable_file_format<P>(p: P) -> bool
|
||||
|
@ -2,11 +2,11 @@ use std::collections::HashSet;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::process::Stdio;
|
||||
|
||||
use prototypes::{CableChannelPrototype, DEFAULT_DELIMITER};
|
||||
use prototypes::{ChannelPrototype, DEFAULT_DELIMITER};
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::channels::{entry::Entry, preview::PreviewCommand, OnAir};
|
||||
use crate::channels::{entry::Entry, preview::PreviewCommand};
|
||||
use crate::matcher::Matcher;
|
||||
use crate::matcher::{config::Config, injector::Injector};
|
||||
use crate::utils::command::shell_command;
|
||||
@ -34,8 +34,8 @@ impl Default for Channel {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CableChannelPrototype> for Channel {
|
||||
fn from(prototype: CableChannelPrototype) -> Self {
|
||||
impl From<ChannelPrototype> for Channel {
|
||||
fn from(prototype: ChannelPrototype) -> Self {
|
||||
Self::new(
|
||||
&prototype.name,
|
||||
&prototype.source_command,
|
||||
@ -77,6 +77,59 @@ impl Channel {
|
||||
crawl_handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find(&mut self, pattern: &str) {
|
||||
self.matcher.find(pattern);
|
||||
}
|
||||
|
||||
pub fn results(&mut self, num_entries: u32, offset: u32) -> Vec<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)]
|
||||
@ -123,58 +176,3 @@ async fn load_candidates(
|
||||
}
|
||||
let _ = child.wait();
|
||||
}
|
||||
|
||||
impl OnAir for Channel {
|
||||
fn find(&mut self, pattern: &str) {
|
||||
self.matcher.find(pattern);
|
||||
}
|
||||
|
||||
fn results(&mut self, num_entries: u32, offset: u32) -> Vec<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()
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,7 @@ use std::{
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cable::SerializedChannelPrototypes, channels::preview::PreviewCommand,
|
||||
};
|
||||
use crate::{cable::CableSpec, channels::preview::PreviewCommand};
|
||||
|
||||
/// A prototype for a cable channel.
|
||||
///
|
||||
@ -40,7 +38,7 @@ use crate::{
|
||||
/// preview_command = "cat {}"
|
||||
/// ```
|
||||
#[derive(Clone, Debug, serde::Deserialize, PartialEq)]
|
||||
pub struct CableChannelPrototype {
|
||||
pub struct ChannelPrototype {
|
||||
pub name: String,
|
||||
pub source_command: String,
|
||||
#[serde(default)]
|
||||
@ -54,7 +52,7 @@ pub struct CableChannelPrototype {
|
||||
const STDIN_CHANNEL_NAME: &str = "stdin";
|
||||
const STDIN_SOURCE_COMMAND: &str = "cat";
|
||||
|
||||
impl CableChannelPrototype {
|
||||
impl ChannelPrototype {
|
||||
pub fn new(
|
||||
name: &str,
|
||||
source_command: &str,
|
||||
@ -102,9 +100,9 @@ impl CableChannelPrototype {
|
||||
const DEFAULT_PROTOTYPE_NAME: &str = "files";
|
||||
pub const DEFAULT_DELIMITER: &str = " ";
|
||||
|
||||
impl Default for CableChannelPrototype {
|
||||
impl Default for ChannelPrototype {
|
||||
fn default() -> Self {
|
||||
CableChannelPrototypes::default()
|
||||
Cable::default()
|
||||
.get(DEFAULT_PROTOTYPE_NAME)
|
||||
.cloned()
|
||||
.unwrap()
|
||||
@ -118,7 +116,7 @@ fn default_delimiter() -> Option<String> {
|
||||
Some(DEFAULT_DELIMITER.to_string())
|
||||
}
|
||||
|
||||
impl Display for CableChannelPrototype {
|
||||
impl Display for ChannelPrototype {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.name)
|
||||
}
|
||||
@ -129,13 +127,11 @@ impl Display for CableChannelPrototype {
|
||||
/// This is used to store cable channel prototypes throughout the application
|
||||
/// in a way that facilitates answering questions like "what's the prototype
|
||||
/// for `files`?" or "does this channel exist?".
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct CableChannelPrototypes(
|
||||
pub FxHashMap<String, CableChannelPrototype>,
|
||||
);
|
||||
#[derive(Debug, serde::Deserialize, Clone)]
|
||||
pub struct Cable(pub FxHashMap<String, ChannelPrototype>);
|
||||
|
||||
impl Deref for CableChannelPrototypes {
|
||||
type Target = FxHashMap<String, CableChannelPrototype>;
|
||||
impl Deref for Cable {
|
||||
type Target = FxHashMap<String, ChannelPrototype>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
@ -153,18 +149,16 @@ const DEFAULT_CABLE_CHANNELS_FILE: &str =
|
||||
const DEFAULT_CABLE_CHANNELS_FILE: &str =
|
||||
include_str!("../../../cable/windows-channels.toml");
|
||||
|
||||
impl Default for CableChannelPrototypes {
|
||||
impl Default for Cable {
|
||||
/// Fallback to the default cable channels specification (the template file
|
||||
/// included in the repo).
|
||||
fn default() -> Self {
|
||||
let s = toml::from_str::<SerializedChannelPrototypes>(
|
||||
DEFAULT_CABLE_CHANNELS_FILE,
|
||||
)
|
||||
.expect("Unable to parse default cable channels");
|
||||
let s = toml::from_str::<CableSpec>(DEFAULT_CABLE_CHANNELS_FILE)
|
||||
.expect("Unable to parse default cable channels");
|
||||
let mut prototypes = FxHashMap::default();
|
||||
for prototype in s.prototypes {
|
||||
prototypes.insert(prototype.name.clone(), prototype);
|
||||
}
|
||||
CableChannelPrototypes(prototypes)
|
||||
Cable(prototypes)
|
||||
}
|
||||
}
|
||||
|
@ -1,144 +1,4 @@
|
||||
use crate::channels::entry::Entry;
|
||||
use anyhow::Result;
|
||||
use cable::prototypes::CableChannelPrototype;
|
||||
use rustc_hash::FxHashSet;
|
||||
use television_derive::Broadcast;
|
||||
|
||||
pub mod cable;
|
||||
pub mod entry;
|
||||
pub mod preview;
|
||||
pub mod remote_control;
|
||||
|
||||
/// The interface that all television channels must implement.
|
||||
///
|
||||
/// # Note
|
||||
/// The `OnAir` trait requires the `Send` trait to be implemented as well.
|
||||
/// This is necessary to allow the channels to be used with the tokio
|
||||
/// runtime, which requires `Send` in order to be able to send tasks between
|
||||
/// worker threads safely.
|
||||
///
|
||||
/// # Methods
|
||||
/// - `find`: Find entries that match the given pattern. This method does not
|
||||
/// return anything and instead typically stores the results internally for
|
||||
/// later retrieval allowing to perform the search in the background while
|
||||
/// incrementally polling the results.
|
||||
/// ```ignore
|
||||
/// fn find(&mut self, pattern: &str);
|
||||
/// ```
|
||||
/// - `results`: Get the results of the search (at a given point in time, see
|
||||
/// above). This method returns a specific portion of entries that match the
|
||||
/// search pattern. The `num_entries` parameter specifies the number of
|
||||
/// entries to return and the `offset` parameter specifies the starting index
|
||||
/// of the entries to return.
|
||||
/// ```ignore
|
||||
/// fn results(&mut self, num_entries: u32, offset: u32) -> Vec<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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::{cable::prototypes::DEFAULT_DELIMITER, entry::Entry};
|
||||
use crate::channels::cable::prototypes::CableChannelPrototype;
|
||||
use crate::channels::cable::prototypes::ChannelPrototype;
|
||||
use lazy_regex::{regex, Lazy, Regex};
|
||||
use tracing::debug;
|
||||
|
||||
@ -65,8 +65,8 @@ impl PreviewCommand {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&CableChannelPrototype> for Option<PreviewCommand> {
|
||||
fn from(value: &CableChannelPrototype) -> Self {
|
||||
impl From<&ChannelPrototype> for Option<PreviewCommand> {
|
||||
fn from(value: &ChannelPrototype) -> Self {
|
||||
if let Some(command) = value.preview_command.as_ref() {
|
||||
let delimiter = value
|
||||
.preview_delimiter
|
||||
|
@ -1,32 +1,23 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::channels::cable::prototypes::CableChannelPrototypes;
|
||||
use crate::channels::cable::prototypes::Cable;
|
||||
use crate::channels::entry::Entry;
|
||||
use crate::channels::OnAir;
|
||||
use crate::matcher::{config::Config, Matcher};
|
||||
use anyhow::Result;
|
||||
use devicons::FileIcon;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
|
||||
use super::cable::prototypes::CableChannelPrototype;
|
||||
use super::cable::prototypes::ChannelPrototype;
|
||||
|
||||
pub struct RemoteControl {
|
||||
matcher: Matcher<String>,
|
||||
cable_channels: Option<CableChannelPrototypes>,
|
||||
selected_entries: FxHashSet<Entry>,
|
||||
cable_channels: Option<Cable>,
|
||||
}
|
||||
|
||||
const NUM_THREADS: usize = 1;
|
||||
|
||||
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 injector = matcher.injector();
|
||||
for c in cable_channels
|
||||
.as_ref()
|
||||
.unwrap_or(&CableChannelPrototypes::default())
|
||||
.keys()
|
||||
{
|
||||
for c in cable_channels.as_ref().unwrap_or(&Cable::default()).keys() {
|
||||
let () = injector.push(c.clone(), |e, cols| {
|
||||
cols[0] = e.to_string().into();
|
||||
});
|
||||
@ -34,11 +25,10 @@ impl RemoteControl {
|
||||
RemoteControl {
|
||||
matcher,
|
||||
cable_channels,
|
||||
selected_entries: HashSet::with_hasher(FxBuildHasher),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zap(&self, channel_name: &str) -> Result<CableChannelPrototype> {
|
||||
pub fn zap(&self, channel_name: &str) -> Result<ChannelPrototype> {
|
||||
match self
|
||||
.cable_channels
|
||||
.as_ref()
|
||||
@ -69,12 +59,12 @@ const CABLE_ICON: FileIcon = FileIcon {
|
||||
color: "#000000",
|
||||
};
|
||||
|
||||
impl OnAir for RemoteControl {
|
||||
fn find(&mut self, pattern: &str) {
|
||||
impl RemoteControl {
|
||||
pub fn find(&mut self, pattern: &str) {
|
||||
self.matcher.find(pattern);
|
||||
}
|
||||
|
||||
fn results(&mut self, num_entries: u32, offset: u32) -> Vec<Entry> {
|
||||
pub fn results(&mut self, num_entries: u32, offset: u32) -> Vec<Entry> {
|
||||
self.matcher.tick();
|
||||
self.matcher
|
||||
.results(num_entries, offset)
|
||||
@ -88,35 +78,28 @@ impl OnAir for RemoteControl {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_result(&self, index: u32) -> Option<Entry> {
|
||||
pub fn get_result(&self, index: u32) -> Option<Entry> {
|
||||
self.matcher.get_result(index).map(|item| {
|
||||
let path = item.matched_string;
|
||||
Entry::new(path).with_icon(TV_ICON)
|
||||
})
|
||||
}
|
||||
|
||||
fn selected_entries(&self) -> &FxHashSet<Entry> {
|
||||
&self.selected_entries
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn toggle_selection(&mut self, entry: &Entry) {}
|
||||
|
||||
fn result_count(&self) -> u32 {
|
||||
pub fn result_count(&self) -> u32 {
|
||||
self.matcher.matched_item_count
|
||||
}
|
||||
|
||||
fn total_count(&self) -> u32 {
|
||||
pub fn total_count(&self) -> u32 {
|
||||
self.matcher.total_item_count
|
||||
}
|
||||
|
||||
fn running(&self) -> bool {
|
||||
pub fn running(&self) -> bool {
|
||||
self.matcher.status.running
|
||||
}
|
||||
|
||||
fn shutdown(&self) {}
|
||||
pub fn shutdown(&self) {}
|
||||
|
||||
fn supports_preview(&self) -> bool {
|
||||
pub fn supports_preview(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,7 @@ use std::path::Path;
|
||||
use anyhow::{anyhow, Result};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::channels::cable::prototypes::{
|
||||
CableChannelPrototype, CableChannelPrototypes,
|
||||
};
|
||||
use crate::channels::cable::prototypes::{Cable, ChannelPrototype};
|
||||
use crate::channels::preview::PreviewCommand;
|
||||
use crate::cli::args::{Cli, Command};
|
||||
use crate::config::{KeyBindings, DEFAULT_CHANNEL};
|
||||
@ -20,7 +18,7 @@ pub mod args;
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PostProcessedCli {
|
||||
pub channel: CableChannelPrototype,
|
||||
pub channel: ChannelPrototype,
|
||||
pub preview_command: Option<PreviewCommand>,
|
||||
pub no_preview: bool,
|
||||
pub tick_rate: Option<f64>,
|
||||
@ -41,7 +39,7 @@ pub struct PostProcessedCli {
|
||||
impl Default for PostProcessedCli {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
channel: CableChannelPrototype::default(),
|
||||
channel: ChannelPrototype::default(),
|
||||
preview_command: None,
|
||||
no_preview: false,
|
||||
tick_rate: None,
|
||||
@ -80,10 +78,10 @@ impl From<Cli> for PostProcessedCli {
|
||||
offset_expr: None,
|
||||
});
|
||||
|
||||
let mut channel: CableChannelPrototype;
|
||||
let mut channel: ChannelPrototype;
|
||||
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() {
|
||||
channel = cable_channels
|
||||
.get(DEFAULT_CHANNEL)
|
||||
@ -178,8 +176,8 @@ fn parse_keybindings_literal(
|
||||
|
||||
pub fn parse_channel(
|
||||
channel: &str,
|
||||
cable_channels: &CableChannelPrototypes,
|
||||
) -> Result<CableChannelPrototype> {
|
||||
cable_channels: &Cable,
|
||||
) -> Result<ChannelPrototype> {
|
||||
// try to parse the channel as a cable channel
|
||||
match cable_channels
|
||||
.iter()
|
||||
@ -191,7 +189,7 @@ pub fn parse_channel(
|
||||
}
|
||||
|
||||
pub fn list_channels() {
|
||||
for c in cable::load_cable_channels().unwrap_or_default().keys() {
|
||||
for c in cable::load_cable().unwrap_or_default().keys() {
|
||||
println!("\t{c}");
|
||||
}
|
||||
}
|
||||
@ -223,8 +221,8 @@ pub fn guess_channel_from_prompt(
|
||||
prompt: &str,
|
||||
command_mapping: &FxHashMap<String, String>,
|
||||
fallback_channel: &str,
|
||||
cable_channels: &CableChannelPrototypes,
|
||||
) -> Result<CableChannelPrototype> {
|
||||
cable_channels: &Cable,
|
||||
) -> Result<ChannelPrototype> {
|
||||
debug!("Guessing channel from prompt: {}", prompt);
|
||||
// git checkout -qf
|
||||
// --- -------- --- <---------
|
||||
@ -317,7 +315,7 @@ mod tests {
|
||||
|
||||
let post_processed_cli: PostProcessedCli = cli.into();
|
||||
|
||||
let expected = CableChannelPrototype {
|
||||
let expected = ChannelPrototype {
|
||||
preview_delimiter: Some(":".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
@ -350,10 +348,7 @@ mod tests {
|
||||
|
||||
let post_processed_cli: PostProcessedCli = cli.into();
|
||||
|
||||
assert_eq!(
|
||||
post_processed_cli.channel,
|
||||
CableChannelPrototype::default(),
|
||||
);
|
||||
assert_eq!(post_processed_cli.channel, ChannelPrototype::default(),);
|
||||
assert_eq!(
|
||||
post_processed_cli.working_directory,
|
||||
Some(".".to_string())
|
||||
@ -388,7 +383,7 @@ mod tests {
|
||||
|
||||
/// Returns a tuple containing a command mapping and a fallback channel.
|
||||
fn guess_channel_from_prompt_setup<'a>(
|
||||
) -> (FxHashMap<String, String>, &'a str, CableChannelPrototypes) {
|
||||
) -> (FxHashMap<String, String>, &'a str, Cable) {
|
||||
let mut command_mapping = FxHashMap::default();
|
||||
command_mapping.insert("vim".to_string(), "files".to_string());
|
||||
command_mapping.insert("export".to_string(), "env".to_string());
|
||||
@ -396,7 +391,7 @@ mod tests {
|
||||
(
|
||||
command_mapping,
|
||||
"env",
|
||||
cable::load_cable_channels().unwrap_or_default(),
|
||||
cable::load_cable().unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,7 @@ use std::process::exit;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use television::cable;
|
||||
use television::channels::cable::prototypes::{
|
||||
CableChannelPrototype, CableChannelPrototypes,
|
||||
};
|
||||
use television::channels::cable::prototypes::{Cable, ChannelPrototype};
|
||||
use television::utils::clipboard::CLIPBOARD;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
@ -43,7 +41,7 @@ async fn main() -> Result<()> {
|
||||
let mut config = Config::new(&ConfigEnv::init()?)?;
|
||||
|
||||
debug!("Loading cable channels...");
|
||||
let cable_channels = cable::load_cable_channels().unwrap_or_default();
|
||||
let cable_channels = cable::load_cable().unwrap_or_default();
|
||||
|
||||
// optionally handle subcommands
|
||||
debug!("Handling subcommands...");
|
||||
@ -154,11 +152,11 @@ pub fn determine_channel(
|
||||
args: PostProcessedCli,
|
||||
config: &Config,
|
||||
readable_stdin: bool,
|
||||
cable_channels: &CableChannelPrototypes,
|
||||
) -> Result<CableChannelPrototype> {
|
||||
cable_channels: &Cable,
|
||||
) -> Result<ChannelPrototype> {
|
||||
if readable_stdin {
|
||||
debug!("Using stdin channel");
|
||||
Ok(CableChannelPrototype::stdin(args.preview_command))
|
||||
Ok(ChannelPrototype::stdin(args.preview_command))
|
||||
} else if let Some(prompt) = args.autocomplete_prompt {
|
||||
debug!("Using autocomplete prompt: {:?}", prompt);
|
||||
let channel_prototype = guess_channel_from_prompt(
|
||||
@ -179,8 +177,7 @@ pub fn determine_channel(
|
||||
mod tests {
|
||||
use rustc_hash::FxHashMap;
|
||||
use television::{
|
||||
cable::load_cable_channels,
|
||||
channels::cable::prototypes::CableChannelPrototype,
|
||||
cable::load_cable, channels::cable::prototypes::ChannelPrototype,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@ -189,11 +186,11 @@ mod tests {
|
||||
args: &PostProcessedCli,
|
||||
config: &Config,
|
||||
readable_stdin: bool,
|
||||
expected_channel: &CableChannelPrototype,
|
||||
cable_channels: Option<CableChannelPrototypes>,
|
||||
expected_channel: &ChannelPrototype,
|
||||
cable_channels: Option<Cable>,
|
||||
) {
|
||||
let channels: CableChannelPrototypes = cable_channels
|
||||
.unwrap_or_else(|| load_cable_channels().unwrap_or_default());
|
||||
let channels: Cable =
|
||||
cable_channels.unwrap_or_else(|| load_cable().unwrap_or_default());
|
||||
let channel =
|
||||
determine_channel(args.clone(), config, readable_stdin, &channels)
|
||||
.unwrap();
|
||||
@ -208,7 +205,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
/// Test that the channel is stdin when stdin is readable
|
||||
async fn test_determine_channel_readable_stdin() {
|
||||
let channel = CableChannelPrototype::default();
|
||||
let channel = ChannelPrototype::default();
|
||||
let args = PostProcessedCli {
|
||||
channel,
|
||||
..Default::default()
|
||||
@ -218,9 +215,7 @@ mod tests {
|
||||
&args,
|
||||
&config,
|
||||
true,
|
||||
&CableChannelPrototype::new(
|
||||
"stdin", "cat", false, None, None, None,
|
||||
),
|
||||
&ChannelPrototype::new("stdin", "cat", false, None, None, None),
|
||||
None,
|
||||
);
|
||||
}
|
||||
@ -228,9 +223,8 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_determine_channel_autocomplete_prompt() {
|
||||
let autocomplete_prompt = Some("cd".to_string());
|
||||
let expected_channel = CableChannelPrototype::new(
|
||||
"dirs", "ls {}", false, None, None, None,
|
||||
);
|
||||
let expected_channel =
|
||||
ChannelPrototype::new("dirs", "ls {}", false, None, None, None);
|
||||
let args = PostProcessedCli {
|
||||
autocomplete_prompt,
|
||||
..Default::default()
|
||||
@ -263,7 +257,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_determine_channel_standard_case() {
|
||||
let channel =
|
||||
CableChannelPrototype::new("dirs", "", false, None, None, None);
|
||||
ChannelPrototype::new("dirs", "", false, None, None, None);
|
||||
let args = PostProcessedCli {
|
||||
channel,
|
||||
..Default::default()
|
||||
@ -273,7 +267,7 @@ mod tests {
|
||||
&args,
|
||||
&config,
|
||||
false,
|
||||
&CableChannelPrototype::new("dirs", "", false, None, None, None),
|
||||
&ChannelPrototype::new("dirs", "", false, None, None, None),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::channels::cable::prototypes::CableChannelPrototype;
|
||||
use crate::channels::cable::prototypes::ChannelPrototype;
|
||||
use crate::preview::{Preview, PreviewContent};
|
||||
use crate::utils::command::shell_command;
|
||||
use crate::{
|
||||
@ -130,8 +130,8 @@ pub fn try_preview(
|
||||
in_flight_previews.lock().remove(&entry.name);
|
||||
}
|
||||
|
||||
impl From<&CableChannelPrototype> for Option<Previewer> {
|
||||
fn from(value: &CableChannelPrototype) -> Self {
|
||||
impl From<&ChannelPrototype> for Option<Previewer> {
|
||||
fn from(value: &ChannelPrototype) -> Self {
|
||||
Option::<PreviewCommand>::from(value).map(Previewer::new)
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,12 @@
|
||||
use crate::{
|
||||
action::Action,
|
||||
cable::load_cable_channels,
|
||||
channels::{
|
||||
cable::{
|
||||
prototypes::{CableChannelPrototype, CableChannelPrototypes},
|
||||
prototypes::{Cable, ChannelPrototype},
|
||||
Channel as CableChannel,
|
||||
},
|
||||
entry::Entry,
|
||||
remote_control::RemoteControl,
|
||||
OnAir, TelevisionChannel,
|
||||
},
|
||||
config::{Config, Theme},
|
||||
draw::{ChannelState, Ctx, TvState},
|
||||
@ -48,7 +46,7 @@ pub struct Television {
|
||||
action_tx: UnboundedSender<Action>,
|
||||
pub config: Config,
|
||||
pub channel: CableChannel,
|
||||
pub remote_control: Option<TelevisionChannel>,
|
||||
pub remote_control: Option<RemoteControl>,
|
||||
pub mode: Mode,
|
||||
pub current_pattern: String,
|
||||
pub matching_mode: MatchingMode,
|
||||
@ -70,13 +68,13 @@ impl Television {
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
action_tx: UnboundedSender<Action>,
|
||||
channel_prototype: CableChannelPrototype,
|
||||
channel_prototype: ChannelPrototype,
|
||||
mut config: Config,
|
||||
input: Option<String>,
|
||||
no_remote: bool,
|
||||
no_help: bool,
|
||||
exact: bool,
|
||||
cable_channels: CableChannelPrototypes,
|
||||
cable_channels: Cable,
|
||||
) -> Self {
|
||||
let mut results_picker = Picker::new(input.clone());
|
||||
if config.ui.input_bar_position == InputPosition::Bottom {
|
||||
@ -108,9 +106,7 @@ impl Television {
|
||||
let remote_control = if no_remote {
|
||||
None
|
||||
} else {
|
||||
Some(TelevisionChannel::RemoteControl(RemoteControl::new(Some(
|
||||
cable_channels,
|
||||
))))
|
||||
Some(RemoteControl::new(Some(cable_channels)))
|
||||
};
|
||||
|
||||
if no_help {
|
||||
@ -150,13 +146,6 @@ impl Television {
|
||||
self.ui_state = ui_state;
|
||||
}
|
||||
|
||||
pub fn init_remote_control(&mut self) {
|
||||
let cable_channels = load_cable_channels().unwrap_or_default();
|
||||
self.remote_control = Some(TelevisionChannel::RemoteControl(
|
||||
RemoteControl::new(Some(cable_channels)),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn dump_context(&self) -> Ctx {
|
||||
let channel_state = ChannelState::new(
|
||||
self.channel.name.clone(),
|
||||
@ -189,10 +178,7 @@ impl Television {
|
||||
self.channel.name.clone()
|
||||
}
|
||||
|
||||
pub fn change_channel(
|
||||
&mut self,
|
||||
channel_prototype: CableChannelPrototype,
|
||||
) {
|
||||
pub fn change_channel(&mut self, channel_prototype: ChannelPrototype) {
|
||||
self.preview_state.reset();
|
||||
self.preview_state.enabled =
|
||||
channel_prototype.preview_command.is_some();
|
||||
@ -213,7 +199,9 @@ impl Television {
|
||||
);
|
||||
}
|
||||
Mode::RemoteControl => {
|
||||
self.remote_control.as_mut().unwrap().find(pattern);
|
||||
if let Some(rc) = self.remote_control.as_mut() {
|
||||
rc.find(pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -244,11 +232,9 @@ impl Television {
|
||||
}
|
||||
Mode::RemoteControl => {
|
||||
if let Some(i) = self.rc_picker.selected() {
|
||||
return self
|
||||
.remote_control
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get_result(i.try_into().unwrap());
|
||||
if let Some(rc) = &self.remote_control {
|
||||
return rc.get_result(i.try_into().unwrap());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@ -491,12 +477,10 @@ impl Television {
|
||||
match self.mode {
|
||||
Mode::Channel => {
|
||||
self.mode = Mode::RemoteControl;
|
||||
self.init_remote_control();
|
||||
}
|
||||
Mode::RemoteControl => {
|
||||
// this resets the RC picker
|
||||
self.reset_picker_input();
|
||||
self.init_remote_control();
|
||||
self.remote_control.as_mut().unwrap().find(EMPTY_STRING);
|
||||
self.reset_picker_selection();
|
||||
self.mode = Mode::Channel;
|
||||
@ -528,7 +512,6 @@ impl Television {
|
||||
.remote_control
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
// FIXME: is the TelevisionChannel enum still worth it?
|
||||
.zap(entry.name.as_str())?;
|
||||
// this resets the RC picker
|
||||
self.reset_picker_selection();
|
||||
|
26
tests/app.rs
26
tests/app.rs
@ -3,9 +3,7 @@ use std::{collections::HashSet, path::PathBuf, time::Duration};
|
||||
use television::{
|
||||
action::Action,
|
||||
app::{App, AppOptions},
|
||||
channels::cable::prototypes::{
|
||||
CableChannelPrototype, CableChannelPrototypes,
|
||||
},
|
||||
channels::cable::prototypes::{Cable, ChannelPrototype},
|
||||
config::default_config_from_file,
|
||||
};
|
||||
use tokio::{task::JoinHandle, time::timeout};
|
||||
@ -23,19 +21,19 @@ const DEFAULT_TIMEOUT: Duration = Duration::from_millis(100);
|
||||
/// The app is started in a separate task and can be interacted with by sending
|
||||
/// actions to the action channel.
|
||||
fn setup_app(
|
||||
channel_prototype: Option<CableChannelPrototype>,
|
||||
channel_prototype: Option<ChannelPrototype>,
|
||||
select_1: bool,
|
||||
exact: bool,
|
||||
) -> (
|
||||
JoinHandle<television::app::AppOutput>,
|
||||
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"))
|
||||
.join("tests")
|
||||
.join("target_dir");
|
||||
std::env::set_current_dir(&target_dir).unwrap();
|
||||
CableChannelPrototype::default()
|
||||
ChannelPrototype::default()
|
||||
});
|
||||
let mut config = default_config_from_file().unwrap();
|
||||
// this speeds up the tests
|
||||
@ -49,13 +47,7 @@ fn setup_app(
|
||||
false,
|
||||
config.application.tick_rate,
|
||||
);
|
||||
let mut app = App::new(
|
||||
chan,
|
||||
config,
|
||||
input,
|
||||
options,
|
||||
&CableChannelPrototypes::default(),
|
||||
);
|
||||
let mut app = App::new(chan, config, input, options, &Cable::default());
|
||||
|
||||
// retrieve the app's action channel handle in order to send a quit action
|
||||
let tx = app.action_tx.clone();
|
||||
@ -220,8 +212,8 @@ async fn test_app_exact_search_positive() {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 3)]
|
||||
async fn test_app_exits_when_select_1_and_only_one_result() {
|
||||
let prototype = CableChannelPrototype::new(
|
||||
"cable",
|
||||
let prototype = ChannelPrototype::new(
|
||||
"some_channel",
|
||||
"echo file1.txt",
|
||||
false,
|
||||
None,
|
||||
@ -258,8 +250,8 @@ async fn test_app_exits_when_select_1_and_only_one_result() {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 3)]
|
||||
async fn test_app_does_not_exit_when_select_1_and_more_than_one_result() {
|
||||
let prototype = CableChannelPrototype::new(
|
||||
"cable",
|
||||
let prototype = ChannelPrototype::new(
|
||||
"some_channel",
|
||||
"echo 'file1.txt\nfile2.txt'",
|
||||
false,
|
||||
None,
|
||||
|
Loading…
x
Reference in New Issue
Block a user