mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 03:55:23 +00:00
remote controls
This commit is contained in:
parent
76d32a01c2
commit
8223e073a0
@ -1,63 +1,64 @@
|
||||
/**
|
||||
|
||||
The general idea
|
||||
┌──────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ rendering thread event thread main thread │
|
||||
│ │
|
||||
│ │ │ │ │
|
||||
│ │
|
||||
│ │ │ │ │
|
||||
│ │
|
||||
│ │ │ │ │
|
||||
│ ┌───────┴───────┐ │
|
||||
│ │ │ │ │ │
|
||||
│ │ receive event │ │
|
||||
│ │ │ │ │ │
|
||||
│ └───────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ │
|
||||
│ │ ┌──────────────────┐ ┌──────────┴─────────┐ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │send on `event_rx`├────────────►│ receive `event_rx` │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ └──────────────────┘ └──────────┬─────────┘ │
|
||||
│ │ │
|
||||
│ │ ▼ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ │ map to action │ │
|
||||
│ └──────────┬─────────┘ │
|
||||
│ │ ▼ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ │ send on `action_tx`│ │
|
||||
│ └──────────┬─────────┘ │
|
||||
│ │ │
|
||||
│ │
|
||||
│ │ ┌──────────┴─────────┐ │
|
||||
│ │ receive `action_rx`│ │
|
||||
│ │ └──────────┬─────────┘ │
|
||||
│ ┌───────────┴────────────┐ ▼ │
|
||||
│ │ │ ┌────────────────────┐ │
|
||||
│ │ receive `render_rx` │◄────────────────────────────────────────────────┤ dispatch action │ │
|
||||
│ │ │ └──────────┬─────────┘ │
|
||||
│ └───────────┬────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌────────────────────────┐ ┌────────────────────┐ │
|
||||
│ │ render components │ │ update components │ │
|
||||
│ └────────────────────────┘ └────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
The general idea
|
||||
┌──────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ rendering thread event thread main thread │
|
||||
│ │
|
||||
│ │ │ │ │
|
||||
│ │
|
||||
│ │ │ │ │
|
||||
│ │
|
||||
│ │ │ │ │
|
||||
│ ┌───────┴───────┐ │
|
||||
│ │ │ │ │ │
|
||||
│ │ receive event │ │
|
||||
│ │ │ │ │ │
|
||||
│ └───────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ │
|
||||
│ │ ┌──────────────────┐ ┌──────────┴─────────┐ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │send on `event_rx`├────────────►│ receive `event_rx` │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ └──────────────────┘ └──────────┬─────────┘ │
|
||||
│ │ │
|
||||
│ │ ▼ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ │ map to action │ │
|
||||
│ └──────────┬─────────┘ │
|
||||
│ │ ▼ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ │ send on `action_tx`│ │
|
||||
│ └──────────┬─────────┘ │
|
||||
│ │ │
|
||||
│ │
|
||||
│ │ ┌──────────┴─────────┐ │
|
||||
│ │ receive `action_rx`│ │
|
||||
│ │ └──────────┬─────────┘ │
|
||||
│ ┌───────────┴────────────┐ ▼ │
|
||||
│ │ │ ┌────────────────────┐ │
|
||||
│ │ receive `render_rx` │◄────────────────────────────────────────────────┤ dispatch action │ │
|
||||
│ │ │ └──────────┬─────────┘ │
|
||||
│ └───────────┬────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌────────────────────────┐ ┌────────────────────┐ │
|
||||
│ │ render components │ │ update components │ │
|
||||
│ └────────────────────────┘ └────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
*/
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
use color_eyre::Result;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::channels::{CliTvChannel, TelevisionChannel};
|
||||
use crate::television::Television;
|
||||
use crate::television::{Mode, Television};
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
@ -132,7 +133,7 @@ impl App {
|
||||
frame_rate,
|
||||
is_output_tty,
|
||||
)
|
||||
.await
|
||||
.await
|
||||
});
|
||||
|
||||
// event handling loop
|
||||
@ -221,7 +222,7 @@ impl App {
|
||||
.television
|
||||
.lock()
|
||||
.await
|
||||
.get_selected_entry());
|
||||
.get_selected_entry(Some(Mode::Channel)));
|
||||
}
|
||||
Action::ClearScreen => {
|
||||
self.render_tx.send(RenderingTask::ClearScreen)?;
|
||||
|
@ -85,12 +85,24 @@ pub trait OnAir: Send {
|
||||
/// 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(UnitChannel, CliChannel, Broadcast)]
|
||||
pub enum TelevisionChannel {
|
||||
@ -113,6 +125,7 @@ pub enum TelevisionChannel {
|
||||
/// The standard input channel.
|
||||
///
|
||||
/// This channel allows to search through whatever is passed through stdin.
|
||||
#[exclude_from_cli]
|
||||
Stdin(stdin::Channel),
|
||||
/// The alias channel.
|
||||
///
|
||||
@ -121,6 +134,7 @@ pub enum TelevisionChannel {
|
||||
/// The remote control channel.
|
||||
///
|
||||
/// This channel allows to switch between different channels.
|
||||
#[exclude_from_cli]
|
||||
RemoteControl(remote_control::RemoteControl),
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ use crate::entry::Entry;
|
||||
use crate::fuzzy::MATCHER;
|
||||
use crate::previewers::PreviewType;
|
||||
use crate::utils::indices::sep_name_and_value_indices;
|
||||
use crate::utils::strings::preprocess_line;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Alias {
|
||||
@ -217,8 +218,8 @@ async fn load_aliases(injector: Injector<Alias>) {
|
||||
if let Some(name) = parts.next() {
|
||||
if let Some(value) = parts.next() {
|
||||
return Some(Alias::new(
|
||||
name.to_string(),
|
||||
value.to_string(),
|
||||
preprocess_line(name),
|
||||
preprocess_line(value),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use crate::entry::Entry;
|
||||
use crate::fuzzy::MATCHER;
|
||||
use crate::previewers::PreviewType;
|
||||
use crate::utils::indices::sep_name_and_value_indices;
|
||||
use crate::utils::strings::preprocess_line;
|
||||
|
||||
struct EnvVar {
|
||||
name: String,
|
||||
@ -39,7 +40,7 @@ impl Channel {
|
||||
);
|
||||
let injector = matcher.injector();
|
||||
for (name, value) in std::env::vars() {
|
||||
let _ = injector.push(EnvVar { name, value }, |e, cols| {
|
||||
let _ = injector.push(EnvVar { name: preprocess_line(&name), value: preprocess_line(&value) }, |e, cols| {
|
||||
cols[0] = (e.name.clone() + &e.value).into();
|
||||
});
|
||||
}
|
||||
@ -92,7 +93,7 @@ impl OnAir for Channel {
|
||||
.matched_items(
|
||||
offset
|
||||
..(num_entries + offset)
|
||||
.min(snapshot.matched_item_count()),
|
||||
.min(snapshot.matched_item_count()),
|
||||
)
|
||||
.map(move |item| {
|
||||
snapshot.pattern().column_pattern(0).indices(
|
||||
|
@ -3,20 +3,17 @@ use nucleo::{
|
||||
pattern::{CaseMatching, Normalization},
|
||||
Config, Injector, Nucleo,
|
||||
};
|
||||
use std::{os::unix::ffi::OsStrExt, path::PathBuf, sync::Arc};
|
||||
|
||||
use ignore::DirEntry;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use super::{OnAir, TelevisionChannel};
|
||||
use crate::entry::Entry;
|
||||
use crate::fuzzy::MATCHER;
|
||||
use crate::previewers::PreviewType;
|
||||
use crate::utils::files::{walk_builder, DEFAULT_NUM_THREADS};
|
||||
use crate::{
|
||||
entry::Entry, utils::strings::proportion_of_printable_ascii_characters,
|
||||
};
|
||||
use crate::{fuzzy::MATCHER, utils::strings::PRINTABLE_ASCII_THRESHOLD};
|
||||
use crate::utils::strings::preprocess_line;
|
||||
|
||||
pub struct Channel {
|
||||
matcher: Nucleo<DirEntry>,
|
||||
matcher: Nucleo<String>,
|
||||
last_pattern: String,
|
||||
result_count: u32,
|
||||
total_count: u32,
|
||||
@ -101,7 +98,7 @@ impl OnAir for Channel {
|
||||
.matched_items(
|
||||
offset
|
||||
..(num_entries + offset)
|
||||
.min(snapshot.matched_item_count()),
|
||||
.min(snapshot.matched_item_count()),
|
||||
)
|
||||
.map(move |item| {
|
||||
snapshot.pattern().column_pattern(0).indices(
|
||||
@ -150,7 +147,7 @@ impl OnAir for Channel {
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_async)]
|
||||
async fn load_files(paths: Vec<PathBuf>, injector: Injector<DirEntry>) {
|
||||
async fn load_files(paths: Vec<PathBuf>, injector: Injector<String>) {
|
||||
if paths.is_empty() {
|
||||
return;
|
||||
}
|
||||
@ -168,20 +165,9 @@ async fn load_files(paths: Vec<PathBuf>, injector: Injector<DirEntry>) {
|
||||
Box::new(move |result| {
|
||||
if let Ok(entry) = result {
|
||||
if entry.file_type().unwrap().is_file() {
|
||||
let file_name = entry.file_name();
|
||||
if proportion_of_printable_ascii_characters(
|
||||
file_name.as_bytes(),
|
||||
) < PRINTABLE_ASCII_THRESHOLD
|
||||
{
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
let _ = injector.push(entry, |e, cols| {
|
||||
cols[0] = e
|
||||
.path()
|
||||
.strip_prefix(¤t_dir)
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.into();
|
||||
let file_path = preprocess_line(&*entry.path().strip_prefix(¤t_dir).unwrap().to_string_lossy());
|
||||
let _ = injector.push(file_path, |e, cols| {
|
||||
cols[0] = e.clone().into();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,11 @@
|
||||
use devicons::FileIcon;
|
||||
use ignore::{overrides::OverrideBuilder, DirEntry};
|
||||
use ignore::overrides::OverrideBuilder;
|
||||
use nucleo::{
|
||||
pattern::{CaseMatching, Normalization},
|
||||
Config, Nucleo,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use std::{collections::HashSet, path::PathBuf, sync::Arc};
|
||||
use tokio::{
|
||||
sync::{oneshot, watch},
|
||||
task::JoinHandle,
|
||||
};
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
@ -20,18 +16,16 @@ use crate::{
|
||||
};
|
||||
|
||||
use crate::channels::OnAir;
|
||||
use crate::utils::strings::preprocess_line;
|
||||
|
||||
pub struct Channel {
|
||||
matcher: Nucleo<DirEntry>,
|
||||
matcher: Nucleo<String>,
|
||||
last_pattern: String,
|
||||
result_count: u32,
|
||||
total_count: u32,
|
||||
running: bool,
|
||||
icon: FileIcon,
|
||||
crawl_handle: JoinHandle<()>,
|
||||
git_dirs_cache: Arc<Mutex<HashSet<String>>>,
|
||||
// TODO: implement cache validation/invalidation
|
||||
cache_valid: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
@ -42,15 +36,9 @@ impl Channel {
|
||||
None,
|
||||
1,
|
||||
);
|
||||
let entry_cache = Arc::new(Mutex::new(HashSet::new()));
|
||||
let cache_valid = Arc::new(Mutex::new(false));
|
||||
// start loading files in the background
|
||||
// PERF: store the results somewhere in a cache
|
||||
let crawl_handle = tokio::spawn(crawl_for_repos(
|
||||
std::env::home_dir().expect("Could not get home directory"),
|
||||
matcher.injector(),
|
||||
entry_cache.clone(),
|
||||
cache_valid.clone(),
|
||||
));
|
||||
Channel {
|
||||
matcher,
|
||||
@ -60,8 +48,6 @@ impl Channel {
|
||||
running: false,
|
||||
icon: FileIcon::from("git"),
|
||||
crawl_handle,
|
||||
git_dirs_cache: entry_cache,
|
||||
cache_valid,
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,9 +187,7 @@ fn get_ignored_paths() -> Vec<PathBuf> {
|
||||
#[allow(clippy::unused_async)]
|
||||
async fn crawl_for_repos(
|
||||
starting_point: PathBuf,
|
||||
injector: nucleo::Injector<DirEntry>,
|
||||
entry_cache: Arc<Mutex<HashSet<String>>>,
|
||||
cache_valid: Arc<Mutex<bool>>,
|
||||
injector: nucleo::Injector<String>,
|
||||
) {
|
||||
let mut walker_overrides_builder = OverrideBuilder::new(&starting_point);
|
||||
walker_overrides_builder.add(".git").unwrap();
|
||||
@ -217,29 +201,20 @@ async fn crawl_for_repos(
|
||||
|
||||
walker.run(|| {
|
||||
let injector = injector.clone();
|
||||
let entry_cache = entry_cache.clone();
|
||||
Box::new(move |result| {
|
||||
if let Ok(entry) = result {
|
||||
if entry.file_type().unwrap().is_dir() {
|
||||
// if the dir is already in cache, skip it
|
||||
let path = entry.path().to_string_lossy().to_string();
|
||||
if entry_cache.lock().contains(&path) {
|
||||
return ignore::WalkState::Skip;
|
||||
}
|
||||
// if the entry is a .git directory, add its parent to the list
|
||||
// of git repos and cache it
|
||||
// if the entry is a .git directory, add its parent to the list of git repos
|
||||
if entry.path().ends_with(".git") {
|
||||
let parent_path = entry
|
||||
let parent_path = preprocess_line(&*entry
|
||||
.path()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
.to_string_lossy());
|
||||
debug!("Found git repo: {:?}", parent_path);
|
||||
let _ = injector.push(entry, |_e, cols| {
|
||||
cols[0] = parent_path.clone().into();
|
||||
let _ = injector.push(parent_path, |e, cols| {
|
||||
cols[0] = e.clone().into();
|
||||
});
|
||||
entry_cache.lock().insert(parent_path);
|
||||
return ignore::WalkState::Skip;
|
||||
}
|
||||
}
|
||||
@ -247,6 +222,4 @@ async fn crawl_for_repos(
|
||||
ignore::WalkState::Continue
|
||||
})
|
||||
});
|
||||
|
||||
*cache_valid.lock() = true;
|
||||
}
|
||||
|
@ -4,11 +4,11 @@ use std::{io::BufRead, sync::Arc};
|
||||
use devicons::FileIcon;
|
||||
use nucleo::{Config, Nucleo};
|
||||
|
||||
use super::OnAir;
|
||||
use crate::entry::Entry;
|
||||
use crate::fuzzy::MATCHER;
|
||||
use crate::previewers::PreviewType;
|
||||
|
||||
use super::OnAir;
|
||||
use crate::utils::strings::preprocess_line;
|
||||
|
||||
pub struct Channel {
|
||||
matcher: Nucleo<String>,
|
||||
@ -25,7 +25,7 @@ impl Channel {
|
||||
pub fn new() -> Self {
|
||||
let mut lines = Vec::new();
|
||||
for line in std::io::stdin().lock().lines().map_while(Result::ok) {
|
||||
lines.push(line);
|
||||
lines.push(preprocess_line(&line));
|
||||
}
|
||||
let matcher = Nucleo::new(
|
||||
Config::DEFAULT,
|
||||
|
@ -1,6 +1,7 @@
|
||||
use devicons::FileIcon;
|
||||
|
||||
use crate::previewers::PreviewType;
|
||||
use crate::utils::strings::preprocess_line;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct Entry {
|
||||
@ -17,7 +18,7 @@ pub struct Entry {
|
||||
impl Entry {
|
||||
pub fn new(name: String, preview_type: PreviewType) -> Self {
|
||||
Self {
|
||||
name,
|
||||
name: preprocess_line(&name),
|
||||
display_name: None,
|
||||
value: None,
|
||||
name_match_ranges: None,
|
||||
@ -29,12 +30,12 @@ impl Entry {
|
||||
}
|
||||
|
||||
pub fn with_display_name(mut self, display_name: String) -> Self {
|
||||
self.display_name = Some(display_name);
|
||||
self.display_name = Some(preprocess_line(&display_name));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_value(mut self, value: String) -> Self {
|
||||
self.value = Some(value);
|
||||
self.value = Some(preprocess_line(&value));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ impl Picker {
|
||||
}
|
||||
} else {
|
||||
self.view_offset = total_items.saturating_sub(height - 2);
|
||||
self.select(Some((total_items).saturating_sub(1)));
|
||||
self.select(Some(total_items.saturating_sub(1)));
|
||||
self.relative_select(Some(height - 3));
|
||||
}
|
||||
}
|
||||
|
@ -165,7 +165,9 @@ impl FilePreviewer {
|
||||
entry_c.name
|
||||
);
|
||||
let lines: Vec<String> =
|
||||
reader.lines().map_while(Result::ok).collect();
|
||||
reader.lines().map_while(Result::ok).map(
|
||||
|line| preprocess_line(&line),
|
||||
).collect();
|
||||
|
||||
match syntax::compute_highlights_for_path(
|
||||
&PathBuf::from(&entry_c.name),
|
||||
|
@ -108,8 +108,8 @@ impl Television {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_selected_entry(&mut self) -> Option<Entry> {
|
||||
match self.mode {
|
||||
pub fn get_selected_entry(&mut self, mode: Option<Mode>) -> Option<Entry> {
|
||||
match mode.unwrap_or(self.mode) {
|
||||
Mode::Channel => self.results_picker.selected().and_then(|i| {
|
||||
self.channel.get_result(u32::try_from(i).unwrap())
|
||||
}),
|
||||
@ -298,7 +298,7 @@ impl Television {
|
||||
Mode::SendToChannel => {}
|
||||
},
|
||||
Action::SelectEntry => {
|
||||
if let Some(entry) = self.get_selected_entry() {
|
||||
if let Some(entry) = self.get_selected_entry(None) {
|
||||
match self.mode {
|
||||
Mode::Channel => self
|
||||
.action_tx
|
||||
@ -378,7 +378,7 @@ impl Television {
|
||||
self.draw_input_box(f, &layout)?;
|
||||
|
||||
let selected_entry =
|
||||
self.get_selected_entry().unwrap_or(ENTRY_PLACEHOLDER);
|
||||
self.get_selected_entry(Some(Mode::Channel)).unwrap_or(ENTRY_PLACEHOLDER);
|
||||
let preview = block_on(self.previewer.preview(&selected_entry));
|
||||
|
||||
// top right block: preview title
|
||||
|
@ -10,6 +10,7 @@ pub mod preview;
|
||||
mod remote_control;
|
||||
pub mod results;
|
||||
pub mod spinner;
|
||||
mod mode;
|
||||
// input
|
||||
//const DEFAULT_INPUT_FG: Color = Color::Rgb(200, 200, 200);
|
||||
//const DEFAULT_RESULTS_COUNT_FG: Color = Color::Rgb(150, 150, 150);
|
||||
|
@ -1,17 +1,18 @@
|
||||
use crate::television::Television;
|
||||
use crate::ui::layout::Layout;
|
||||
use crate::ui::logo::build_logo_paragraph;
|
||||
use crate::ui::mode::mode_color;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::prelude::{Color, Style};
|
||||
use ratatui::widgets::{Block, BorderType, Borders, Padding};
|
||||
use ratatui::Frame;
|
||||
|
||||
pub fn draw_logo_block(f: &mut Frame, area: Rect) {
|
||||
pub fn draw_logo_block(f: &mut Frame, area: Rect, color: Color) {
|
||||
let logo_block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(Color::Blue))
|
||||
.style(Style::default().fg(Color::Yellow))
|
||||
.style(Style::default().fg(color))
|
||||
.padding(Padding::horizontal(1));
|
||||
|
||||
let logo_paragraph = build_logo_paragraph().block(logo_block);
|
||||
@ -27,7 +28,7 @@ impl Television {
|
||||
) -> color_eyre::Result<()> {
|
||||
self.draw_metadata_block(f, layout.help_bar_left);
|
||||
self.draw_keymaps_block(f, layout.help_bar_middle)?;
|
||||
draw_logo_block(f, layout.help_bar_right);
|
||||
draw_logo_block(f, layout.help_bar_right, mode_color(self.mode));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ use ratatui::{
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::ui::mode::mode_color;
|
||||
use crate::{
|
||||
action::Action,
|
||||
event::Key,
|
||||
@ -14,7 +15,6 @@ use crate::{
|
||||
};
|
||||
|
||||
const ACTION_COLOR: Color = Color::DarkGray;
|
||||
const KEY_COLOR: Color = Color::LightYellow;
|
||||
|
||||
impl Television {
|
||||
pub fn build_keymap_table<'a>(&self) -> Result<Table<'a>> {
|
||||
@ -29,6 +29,7 @@ impl Television {
|
||||
|
||||
fn build_keymap_table_for_channel<'a>(&self) -> Result<Table<'a>> {
|
||||
let keymap = self.keymap_for_mode()?;
|
||||
let key_color = mode_color(self.mode);
|
||||
|
||||
// Results navigation
|
||||
let prev = keys_for_action(keymap, &Action::SelectPrevEntry);
|
||||
@ -36,6 +37,7 @@ impl Television {
|
||||
let results_row = Row::new(build_cells_for_key_groups(
|
||||
"↕ Results navigation",
|
||||
vec![prev, next],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// Preview navigation
|
||||
@ -46,6 +48,7 @@ impl Television {
|
||||
let preview_row = Row::new(build_cells_for_key_groups(
|
||||
"↕ Preview navigation",
|
||||
vec![up_keys, down_keys],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// Select entry
|
||||
@ -53,6 +56,7 @@ impl Television {
|
||||
let select_entry_row = Row::new(build_cells_for_key_groups(
|
||||
"✓ Select entry",
|
||||
vec![select_entry_keys],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// Send to channel
|
||||
@ -61,21 +65,23 @@ impl Television {
|
||||
let send_to_channel_row = Row::new(build_cells_for_key_groups(
|
||||
"⇉ Send results to",
|
||||
vec![send_to_channel_keys],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// Switch channels
|
||||
let switch_channels_keys =
|
||||
keys_for_action(keymap, &Action::ToggleRemoteControl);
|
||||
let switch_channels_row = Row::new(build_cells_for_key_groups(
|
||||
"⨀ Remote control",
|
||||
"⨀ Toggle Remote control",
|
||||
vec![switch_channels_keys],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// MISC line (quit, help, etc.)
|
||||
// Quit ⏼
|
||||
let quit_keys = keys_for_action(keymap, &Action::Quit);
|
||||
let quit_row =
|
||||
Row::new(build_cells_for_key_groups("⏼ Quit", vec![quit_keys]));
|
||||
Row::new(build_cells_for_key_groups("⏼ Quit", vec![quit_keys], key_color));
|
||||
|
||||
let widths = vec![Constraint::Fill(1), Constraint::Fill(2)];
|
||||
|
||||
@ -96,34 +102,38 @@ impl Television {
|
||||
&self,
|
||||
) -> Result<Table<'a>> {
|
||||
let keymap = self.keymap_for_mode()?;
|
||||
let key_color = mode_color(self.mode);
|
||||
|
||||
// Results navigation
|
||||
let prev = keys_for_action(keymap, &Action::SelectPrevEntry);
|
||||
let next = keys_for_action(keymap, &Action::SelectNextEntry);
|
||||
let results_row = Row::new(build_cells_for_key_groups(
|
||||
"↕ Results",
|
||||
"↕ Browse channels",
|
||||
vec![prev, next],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// Select entry
|
||||
let select_entry_keys = keys_for_action(keymap, &Action::SelectEntry);
|
||||
let select_entry_row = Row::new(build_cells_for_key_groups(
|
||||
"Select entry",
|
||||
"✓ Select channel",
|
||||
vec![select_entry_keys],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// Switch channels
|
||||
let switch_channels_keys =
|
||||
keys_for_action(keymap, &Action::ToggleRemoteControl);
|
||||
let switch_channels_row = Row::new(build_cells_for_key_groups(
|
||||
"Switch channels",
|
||||
"⨀ Toggle Remote control",
|
||||
vec![switch_channels_keys],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// Quit
|
||||
let quit_keys = keys_for_action(keymap, &Action::Quit);
|
||||
let quit_row =
|
||||
Row::new(build_cells_for_key_groups("Quit", vec![quit_keys]));
|
||||
Row::new(build_cells_for_key_groups("⏼ Quit", vec![quit_keys], key_color));
|
||||
|
||||
Ok(Table::new(
|
||||
vec![results_row, select_entry_row, switch_channels_row, quit_row],
|
||||
@ -173,6 +183,7 @@ impl Television {
|
||||
fn build_cells_for_key_groups(
|
||||
group_name: &str,
|
||||
key_groups: Vec<Vec<String>>,
|
||||
key_color: Color,
|
||||
) -> Vec<Cell> {
|
||||
if key_groups.is_empty() || key_groups.iter().all(Vec::is_empty)
|
||||
{
|
||||
@ -190,13 +201,13 @@ fn build_cells_for_key_groups(
|
||||
let key_group_spans: Vec<Span> = non_empty_groups
|
||||
.map(|keys| {
|
||||
let key_group = keys.join(", ");
|
||||
Span::styled(key_group, Style::default().fg(KEY_COLOR))
|
||||
Span::styled(key_group, Style::default().fg(key_color))
|
||||
})
|
||||
.collect();
|
||||
key_group_spans.iter().enumerate().for_each(|(i, span)| {
|
||||
spans.push(span.clone());
|
||||
if i < key_group_spans.len() - 1 {
|
||||
spans.push(Span::styled(" / ", Style::default().fg(KEY_COLOR)));
|
||||
spans.push(Span::styled(" / ", Style::default().fg(key_color)));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -6,93 +6,92 @@ use ratatui::{
|
||||
};
|
||||
|
||||
use crate::television::Television;
|
||||
use crate::ui::mode::mode_color;
|
||||
|
||||
// television 0.1.6
|
||||
// target triple: aarch64-apple-darwin
|
||||
// build: 1.82.0 (2024-10-24)
|
||||
// current_channel: git_repos
|
||||
// current_mode: channel
|
||||
|
||||
const METADATA_FIELD_NAME_COLOR: Color = Color::DarkGray;
|
||||
const METADATA_FIELD_VALUE_COLOR: Color = Color::Gray;
|
||||
|
||||
impl Television {
|
||||
pub fn build_metadata_table<'a>(&self) -> Table<'a> {
|
||||
let version_row = Row::new(vec![
|
||||
Cell::from(Span::styled(
|
||||
"version: ",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
Style::default().fg(Color::LightYellow),
|
||||
Style::default().fg(METADATA_FIELD_VALUE_COLOR),
|
||||
)),
|
||||
]);
|
||||
|
||||
let target_triple_row = Row::new(vec![
|
||||
Cell::from(Span::styled(
|
||||
"target triple: ",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
env!("VERGEN_CARGO_TARGET_TRIPLE"),
|
||||
Style::default().fg(Color::LightYellow),
|
||||
Style::default().fg(METADATA_FIELD_VALUE_COLOR),
|
||||
)),
|
||||
]);
|
||||
|
||||
let build_row = Row::new(vec![
|
||||
Cell::from(Span::styled(
|
||||
"build: ",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
env!("VERGEN_RUSTC_SEMVER"),
|
||||
Style::default().fg(Color::LightYellow),
|
||||
Style::default().fg(METADATA_FIELD_VALUE_COLOR),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
" (",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
env!("VERGEN_BUILD_DATE"),
|
||||
Style::default().fg(Color::LightYellow),
|
||||
Style::default().fg(METADATA_FIELD_VALUE_COLOR),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
")",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
)),
|
||||
]);
|
||||
|
||||
let current_dir_row = Row::new(vec![
|
||||
Cell::from(Span::styled(
|
||||
"current directory: ",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
std::env::current_dir()
|
||||
.expect("Could not get current directory")
|
||||
.display()
|
||||
.to_string(),
|
||||
Style::default().fg(Color::LightYellow),
|
||||
Style::default().fg(METADATA_FIELD_VALUE_COLOR),
|
||||
)),
|
||||
]);
|
||||
|
||||
let current_channel_row = Row::new(vec![
|
||||
Cell::from(Span::styled(
|
||||
"current channel: ",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
self.current_channel().to_string(),
|
||||
Style::default().fg(Color::LightYellow),
|
||||
Style::default().fg(METADATA_FIELD_VALUE_COLOR),
|
||||
)),
|
||||
]);
|
||||
|
||||
let current_mode_row = Row::new(vec![
|
||||
Cell::from(Span::styled(
|
||||
"current mode: ",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
self.mode.to_string(),
|
||||
Style::default().fg(Color::LightYellow),
|
||||
Style::default().fg(mode_color(self.mode)),
|
||||
)),
|
||||
]);
|
||||
|
||||
|
15
crates/television/ui/mode.rs
Normal file
15
crates/television/ui/mode.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use crate::television::Mode;
|
||||
use ratatui::style::Color;
|
||||
|
||||
const CHANNEL_COLOR: Color = Color::LightYellow;
|
||||
const REMOTE_CONTROL_COLOR: Color = Color::LightMagenta;
|
||||
const SEND_TO_CHANNEL_COLOR: Color = Color::LightCyan;
|
||||
|
||||
|
||||
pub fn mode_color(mode: Mode) -> Color {
|
||||
match mode {
|
||||
Mode::Channel => CHANNEL_COLOR,
|
||||
Mode::RemoteControl => REMOTE_CONTROL_COLOR,
|
||||
Mode::SendToChannel => SEND_TO_CHANNEL_COLOR,
|
||||
}
|
||||
}
|
@ -2,7 +2,8 @@ use crate::channels::OnAir;
|
||||
use crate::television::Television;
|
||||
use crate::ui::get_border_style;
|
||||
use crate::ui::logo::build_remote_logo_paragraph;
|
||||
use crate::ui::results::build_results_list;
|
||||
use crate::ui::mode::mode_color;
|
||||
use crate::ui::results::{build_results_list, ResultsListColors};
|
||||
use color_eyre::eyre::Result;
|
||||
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
|
||||
use ratatui::prelude::Style;
|
||||
@ -32,7 +33,7 @@ impl Television {
|
||||
.split(*area);
|
||||
self.draw_rc_channels(f, &layout[0])?;
|
||||
self.draw_rc_input(f, &layout[1])?;
|
||||
draw_rc_logo(f, layout[2]);
|
||||
draw_rc_logo(f, layout[2], mode_color(self.mode));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -56,7 +57,7 @@ impl Television {
|
||||
);
|
||||
|
||||
let channel_list =
|
||||
build_results_list(rc_block, &entries, ListDirection::TopToBottom);
|
||||
build_results_list(rc_block, &entries, ListDirection::TopToBottom, Some(ResultsListColors::default().result_name_fg(mode_color(self.mode))));
|
||||
|
||||
f.render_stateful_widget(
|
||||
channel_list,
|
||||
@ -132,12 +133,9 @@ impl Television {
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_rc_logo(f: &mut Frame, area: Rect) {
|
||||
fn draw_rc_logo(f: &mut Frame, area: Rect, color: Color) {
|
||||
let logo_block = Block::default()
|
||||
// .borders(Borders::ALL)
|
||||
// .border_type(BorderType::Rounded)
|
||||
// .border_style(Style::default().fg(Color::Blue))
|
||||
.style(Style::default().fg(Color::Yellow));
|
||||
.style(Style::default().fg(color));
|
||||
|
||||
let logo_paragraph = build_remote_logo_paragraph()
|
||||
.alignment(Alignment::Center)
|
||||
|
@ -19,14 +19,56 @@ const DEFAULT_RESULT_PREVIEW_FG: Color = Color::Rgb(150, 150, 150);
|
||||
const DEFAULT_RESULT_LINE_NUMBER_FG: Color = Color::Yellow;
|
||||
const DEFAULT_RESULT_SELECTED_BG: Color = Color::Rgb(50, 50, 50);
|
||||
|
||||
pub struct ResultsListColors {
|
||||
pub result_name_fg: Color,
|
||||
pub result_preview_fg: Color,
|
||||
pub result_line_number_fg: Color,
|
||||
pub result_selected_bg: Color,
|
||||
}
|
||||
|
||||
impl Default for ResultsListColors {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
result_name_fg: DEFAULT_RESULT_NAME_FG,
|
||||
result_preview_fg: DEFAULT_RESULT_PREVIEW_FG,
|
||||
result_line_number_fg: DEFAULT_RESULT_LINE_NUMBER_FG,
|
||||
result_selected_bg: DEFAULT_RESULT_SELECTED_BG,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResultsListColors {
|
||||
pub fn result_name_fg(mut self, color: Color) -> Self {
|
||||
self.result_name_fg = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn result_preview_fg(mut self, color: Color) -> Self {
|
||||
self.result_preview_fg = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn result_line_number_fg(mut self, color: Color) -> Self {
|
||||
self.result_line_number_fg = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn result_selected_bg(mut self, color: Color) -> Self {
|
||||
self.result_selected_bg = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_results_list<'a, 'b>(
|
||||
results_block: Block<'b>,
|
||||
entries: &'a [Entry],
|
||||
list_direction: ListDirection,
|
||||
results_list_colors: Option<ResultsListColors>,
|
||||
) -> List<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
let results_list_colors = results_list_colors.unwrap_or_default();
|
||||
List::new(entries.iter().map(|entry| {
|
||||
let mut spans = Vec::new();
|
||||
// optional icon
|
||||
@ -50,7 +92,7 @@ where
|
||||
last_match_end,
|
||||
start,
|
||||
),
|
||||
Style::default().fg(DEFAULT_RESULT_NAME_FG),
|
||||
Style::default().fg(results_list_colors.result_name_fg),
|
||||
));
|
||||
spans.push(Span::styled(
|
||||
slice_at_char_boundaries(&entry.name, start, end),
|
||||
@ -60,19 +102,19 @@ where
|
||||
}
|
||||
spans.push(Span::styled(
|
||||
&entry.name[next_char_boundary(&entry.name, last_match_end)..],
|
||||
Style::default().fg(DEFAULT_RESULT_NAME_FG),
|
||||
Style::default().fg(results_list_colors.result_name_fg),
|
||||
));
|
||||
} else {
|
||||
spans.push(Span::styled(
|
||||
entry.display_name(),
|
||||
Style::default().fg(DEFAULT_RESULT_NAME_FG),
|
||||
Style::default().fg(results_list_colors.result_name_fg),
|
||||
));
|
||||
}
|
||||
// optional line number
|
||||
if let Some(line_number) = entry.line_number {
|
||||
spans.push(Span::styled(
|
||||
format!(":{line_number}"),
|
||||
Style::default().fg(DEFAULT_RESULT_LINE_NUMBER_FG),
|
||||
Style::default().fg(results_list_colors.result_line_number_fg),
|
||||
));
|
||||
}
|
||||
// optional preview
|
||||
@ -92,7 +134,7 @@ where
|
||||
last_match_end,
|
||||
start,
|
||||
),
|
||||
Style::default().fg(DEFAULT_RESULT_PREVIEW_FG),
|
||||
Style::default().fg(results_list_colors.result_preview_fg),
|
||||
));
|
||||
spans.push(Span::styled(
|
||||
slice_at_char_boundaries(preview, start, end),
|
||||
@ -105,22 +147,22 @@ where
|
||||
preview,
|
||||
preview_match_ranges.last().unwrap().1 as usize,
|
||||
)..],
|
||||
Style::default().fg(DEFAULT_RESULT_PREVIEW_FG),
|
||||
Style::default().fg(results_list_colors.result_preview_fg),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
spans.push(Span::styled(
|
||||
preview,
|
||||
Style::default().fg(DEFAULT_RESULT_PREVIEW_FG),
|
||||
Style::default().fg(results_list_colors.result_preview_fg),
|
||||
));
|
||||
}
|
||||
}
|
||||
Line::from(spans)
|
||||
}))
|
||||
.direction(list_direction)
|
||||
.highlight_style(Style::default().bg(DEFAULT_RESULT_SELECTED_BG))
|
||||
.highlight_symbol("> ")
|
||||
.block(results_block)
|
||||
.direction(list_direction)
|
||||
.highlight_style(Style::default().bg(results_list_colors.result_selected_bg))
|
||||
.highlight_symbol("> ")
|
||||
.block(results_block)
|
||||
}
|
||||
|
||||
impl Television {
|
||||
@ -152,6 +194,7 @@ impl Television {
|
||||
results_block,
|
||||
&entries,
|
||||
ListDirection::BottomToTop,
|
||||
None,
|
||||
);
|
||||
|
||||
f.render_stateful_widget(
|
||||
|
@ -1,5 +1,6 @@
|
||||
use lazy_static::lazy_static;
|
||||
use std::fmt::Write;
|
||||
use tracing::debug;
|
||||
|
||||
pub fn next_char_boundary(s: &str, start: usize) -> usize {
|
||||
let mut i = start;
|
||||
@ -53,7 +54,6 @@ lazy_static! {
|
||||
}
|
||||
|
||||
pub const EMPTY_STRING: &str = "";
|
||||
pub const FOUR_SPACES: &str = " ";
|
||||
pub const TAB_WIDTH: usize = 4;
|
||||
|
||||
const SPACE_CHARACTER: char = ' ';
|
||||
@ -78,11 +78,12 @@ pub fn replace_nonprintable(input: &[u8], tab_width: usize) -> String {
|
||||
// space
|
||||
SPACE_CHARACTER => output.push(' '),
|
||||
// tab
|
||||
TAB_CHARACTER => output.push_str(&" ".repeat(tab_width)),
|
||||
// line feed
|
||||
LINE_FEED_CHARACTER => {
|
||||
output.push_str("␊\x0A");
|
||||
TAB_CHARACTER => {
|
||||
output.push_str(&" ".repeat(tab_width));
|
||||
}
|
||||
// line feed
|
||||
LINE_FEED_CHARACTER => {}
|
||||
|
||||
// ASCII control characters from 0x00 to 0x1F
|
||||
// + control characters from \u{007F} to \u{009F}
|
||||
NULL_CHARACTER..=UNIT_SEPARATOR_CHARACTER
|
||||
@ -91,12 +92,15 @@ pub fn replace_nonprintable(input: &[u8], tab_width: usize) -> String {
|
||||
}
|
||||
// don't print BOMs
|
||||
BOM_CHARACTER => {}
|
||||
// unicode characters above 0x0700 seem unstable with ratatui
|
||||
// Unicode characters above 0x0700 seem unstable with ratatui
|
||||
c if c > '\u{0700}' => {
|
||||
output.push(*NULL_SYMBOL);
|
||||
}
|
||||
// everything else
|
||||
c => output.push(c),
|
||||
c => {
|
||||
debug!("char: {:?}", c);
|
||||
output.push(c)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
write!(output, "\\x{:02X}", input[idx]).ok();
|
||||
@ -123,7 +127,7 @@ pub fn proportion_of_printable_ascii_characters(buffer: &[u8]) -> f32 {
|
||||
printable as f32 / buffer.len() as f32
|
||||
}
|
||||
|
||||
const MAX_LINE_LENGTH: usize = 500;
|
||||
const MAX_LINE_LENGTH: usize = 300;
|
||||
|
||||
pub fn preprocess_line(line: &str) -> String {
|
||||
replace_nonprintable(
|
||||
@ -134,8 +138,8 @@ pub fn preprocess_line(line: &str) -> String {
|
||||
line
|
||||
}
|
||||
}
|
||||
.trim_end_matches(['\r', '\n', '\0'])
|
||||
.as_bytes(),
|
||||
.trim_end_matches(['\r', '\n', '\0'])
|
||||
.as_bytes(),
|
||||
TAB_WIDTH,
|
||||
)
|
||||
}
|
||||
@ -169,11 +173,16 @@ mod tests {
|
||||
#[test]
|
||||
fn test_replace_nonprintable_tab() {
|
||||
test_replace_nonprintable("Hello\tWorld!", "Hello World!");
|
||||
test_replace_nonprintable(
|
||||
" -- AND
|
||||
",
|
||||
" -- AND",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_nonprintable_line_feed() {
|
||||
test_replace_nonprintable("Hello\nWorld!", "Hello␊\nWorld!");
|
||||
test_replace_nonprintable("Hello\nWorld!", "HelloWorld!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -22,7 +22,10 @@ use quote::quote;
|
||||
/// ```
|
||||
///
|
||||
/// The `CliChannel` enum is used to select channels from the command line.
|
||||
#[proc_macro_derive(CliChannel)]
|
||||
///
|
||||
/// Any variant that should not be included in the CLI should be annotated with
|
||||
/// `#[exclude_from_cli]`.
|
||||
#[proc_macro_derive(CliChannel, attributes(exclude_from_cli))]
|
||||
pub fn cli_channel_derive(input: TokenStream) -> TokenStream {
|
||||
// Construct a representation of Rust code as a syntax tree
|
||||
// that we can manipulate
|
||||
@ -32,8 +35,9 @@ pub fn cli_channel_derive(input: TokenStream) -> TokenStream {
|
||||
impl_cli_channel(&ast)
|
||||
}
|
||||
|
||||
/// List of variant names that should be ignored when generating the CliTvChannel enum.
|
||||
const VARIANT_BLACKLIST: [&str; 2] = ["Stdin", "RemoteControl"];
|
||||
fn has_exclude_attr(attrs: &[syn::Attribute]) -> bool {
|
||||
attrs.iter().any(|attr| attr.path().is_ident("exclude_from_cli"))
|
||||
}
|
||||
|
||||
fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
|
||||
// check that the struct is an enum
|
||||
@ -52,7 +56,7 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
|
||||
// create the CliTvChannel enum
|
||||
let cli_enum_variants = variants
|
||||
.iter()
|
||||
.filter(|v| !VARIANT_BLACKLIST.contains(&v.ident.to_string().as_str()))
|
||||
.filter(|variant| !has_exclude_attr(&variant.attrs))
|
||||
.map(|variant| {
|
||||
let variant_name = &variant.ident;
|
||||
quote! {
|
||||
@ -74,7 +78,7 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
|
||||
|
||||
// Generate the match arms for the `to_channel` method
|
||||
let arms = variants.iter().filter(
|
||||
|variant| !VARIANT_BLACKLIST.contains(&variant.ident.to_string().as_str()),
|
||||
|variant| !has_exclude_attr(&variant.attrs)
|
||||
).map(|variant| {
|
||||
let variant_name = &variant.ident;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user