diff --git a/Cargo.lock b/Cargo.lock index 244095d..224ba6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2419,7 +2419,7 @@ dependencies = [ [[package]] name = "television" -version = "0.1.4" +version = "0.1.5" dependencies = [ "anyhow", "better-panic", diff --git a/Cargo.toml b/Cargo.toml index 870f5bb..2a49d8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "television" -version = "0.1.4" +version = "0.1.5" edition = "2021" description = "The revolution will be televised." license = "MIT" @@ -81,6 +81,10 @@ opt-level = 3 debug = true lto = false +[profile.profiling] +inherits = "release" +debug = true + [profile.release] opt-level = 3 diff --git a/README.md b/README.md index cbf74dd..6d56e8e 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,19 @@ # 📺 television +``` + _______________ + |,----------. |\ + || |=| | + || || | | + || . _o| | | + |`-----------' |/ + ~~~~~~~~~~~~~~~ + __ __ _ _ + / /____ / /__ _ __(_)__ (_)__ ___ +/ __/ -_) / -_) |/ / (_- use xargs for that - [x] maybe filter out image types etc. for now - [x] return selected entry on exit - [x] piping output to another command @@ -31,6 +31,7 @@ the channel directly? - [x] support for images is implemented but do we really want that in the core? it's quite heavy - [x] shrink entry names that are too long (from the middle) +- [ ] make the preview toggleable ## feature ideas - [ ] some sort of iterative fuzzy file explorer (preview contents of folders @@ -51,4 +52,6 @@ https://github.com/jrmuizel/pdf-extract - [ ] from one set of entries to another? (fuzzy-refine) maybe piping tv with itself? - [ ] add a way of copying the selected entry name/value to the clipboard - +- [ ] have a keybind to send all current entries to stdout ... oorrrrr to another channel?? +- [ ] action menu on the bottom: send to channel, copy to clipboard, send to stdout, ... maybe with tab to navigate +between possible actions (defined per channel, not all channels can pipe to all channels) diff --git a/crates/television/channels.rs b/crates/television/channels.rs index 40abc50..a2a8b2b 100644 --- a/crates/television/channels.rs +++ b/crates/television/channels.rs @@ -64,6 +64,9 @@ pub trait TelevisionChannel: Send { /// Get the total number of entries currently available. fn total_count(&self) -> u32; + + /// Check if the channel is currently running. + fn running(&self) -> bool; } /// The available television channels. diff --git a/crates/television/channels/alias.rs b/crates/television/channels/alias.rs index 96a3904..f34bc21 100644 --- a/crates/television/channels/alias.rs +++ b/crates/television/channels/alias.rs @@ -22,6 +22,7 @@ pub(crate) struct Channel { file_icon: FileIcon, result_count: u32, total_count: u32, + running: bool, } const NUM_THREADS: usize = 1; @@ -103,6 +104,7 @@ impl Channel { file_icon: FileIcon::from(FILE_ICON_STR), result_count: 0, total_count: 0, + running: false, } } @@ -130,6 +132,7 @@ impl TelevisionChannel for Channel { self.result_count = snapshot.matched_item_count(); self.total_count = snapshot.item_count(); } + self.running = status.running; let mut col_indices = Vec::new(); let mut matcher = MATCHER.lock(); let icon = self.file_icon; @@ -200,4 +203,8 @@ impl TelevisionChannel for Channel { fn total_count(&self) -> u32 { self.total_count } + + fn running(&self) -> bool { + self.running + } } diff --git a/crates/television/channels/env.rs b/crates/television/channels/env.rs index c2104c3..d590d0d 100644 --- a/crates/television/channels/env.rs +++ b/crates/television/channels/env.rs @@ -23,6 +23,7 @@ pub(crate) struct Channel { file_icon: FileIcon, result_count: u32, total_count: u32, + running: bool, } const NUM_THREADS: usize = 1; @@ -48,6 +49,7 @@ impl Channel { file_icon: FileIcon::from(FILE_ICON_STR), result_count: 0, total_count: 0, + running: false, } } @@ -83,6 +85,7 @@ impl TelevisionChannel for Channel { self.result_count = snapshot.matched_item_count(); self.total_count = snapshot.item_count(); } + self.running = status.running; let mut col_indices = Vec::new(); let mut matcher = MATCHER.lock(); let icon = self.file_icon; @@ -147,4 +150,8 @@ impl TelevisionChannel for Channel { .with_icon(self.file_icon) }) } + + fn running(&self) -> bool { + self.running + } } diff --git a/crates/television/channels/files.rs b/crates/television/channels/files.rs index 4690f20..141a9d3 100644 --- a/crates/television/channels/files.rs +++ b/crates/television/channels/files.rs @@ -18,6 +18,7 @@ pub(crate) struct Channel { last_pattern: String, result_count: u32, total_count: u32, + running: bool, } impl Channel { @@ -38,6 +39,7 @@ impl Channel { last_pattern: String::new(), result_count: 0, total_count: 0, + running: false, } } @@ -73,6 +75,7 @@ impl TelevisionChannel for Channel { self.result_count = snapshot.matched_item_count(); self.total_count = snapshot.item_count(); } + self.running = status.running; let mut indices = Vec::new(); let mut matcher = MATCHER.lock(); @@ -110,6 +113,10 @@ impl TelevisionChannel for Channel { .with_icon(FileIcon::from(&path)) }) } + + fn running(&self) -> bool { + self.running + } } #[allow(clippy::unused_async)] diff --git a/crates/television/channels/stdin.rs b/crates/television/channels/stdin.rs index 9862951..718331c 100644 --- a/crates/television/channels/stdin.rs +++ b/crates/television/channels/stdin.rs @@ -17,6 +17,7 @@ pub(crate) struct Channel { result_count: u32, total_count: u32, icon: FileIcon, + running: bool, } const NUM_THREADS: usize = 2; @@ -46,6 +47,7 @@ impl Channel { result_count: 0, total_count: 0, icon: FileIcon::from("nu"), + running: false, } } @@ -75,6 +77,7 @@ impl TelevisionChannel for Channel { self.result_count = snapshot.matched_item_count(); self.total_count = snapshot.item_count(); } + self.running = status.running; let mut indices = Vec::new(); let mut matcher = MATCHER.lock(); let icon = self.icon; @@ -138,4 +141,8 @@ impl TelevisionChannel for Channel { fn total_count(&self) -> u32 { self.total_count } + + fn running(&self) -> bool { + self.running + } } diff --git a/crates/television/channels/text.rs b/crates/television/channels/text.rs index c8edbd1..9178951 100644 --- a/crates/television/channels/text.rs +++ b/crates/television/channels/text.rs @@ -44,6 +44,7 @@ pub(crate) struct Channel { last_pattern: String, result_count: u32, total_count: u32, + running: bool, } impl Channel { @@ -59,6 +60,7 @@ impl Channel { last_pattern: String::new(), result_count: 0, total_count: 0, + running: false, } } @@ -94,6 +96,7 @@ impl TelevisionChannel for Channel { self.result_count = snapshot.matched_item_count(); self.total_count = snapshot.item_count(); } + self.running = status.running; let mut indices = Vec::new(); let mut matcher = MATCHER.lock(); @@ -143,8 +146,18 @@ impl TelevisionChannel for Channel { .with_line_number(item.data.line_number) }) } + + fn running(&self) -> bool { + self.running + } } +/// The maximum file size we're willing to search in. +/// +/// This is to prevent taking humongous amounts of memory when searching in +/// a lot of files (e.g. starting tv in $HOME). +const MAX_FILE_SIZE: u64 = 4 * 1024 * 1024; + #[allow(clippy::unused_async)] async fn load_candidates(path: PathBuf, injector: Injector) { let current_dir = std::env::current_dir().unwrap(); @@ -156,6 +169,11 @@ async fn load_candidates(path: PathBuf, injector: Injector) { Box::new(move |result| { if let Ok(entry) = result { if entry.file_type().unwrap().is_file() { + if let Ok(m) = entry.metadata() { + if m.len() > MAX_FILE_SIZE { + return ignore::WalkState::Continue; + } + } // iterate over the lines of the file match File::open(entry.path()) { Ok(file) => { diff --git a/crates/television/cli.rs b/crates/television/cli.rs index bac017e..af4e728 100644 --- a/crates/television/cli.rs +++ b/crates/television/cli.rs @@ -39,6 +39,18 @@ pub(crate) fn version() -> String { "\ {VERSION_MESSAGE} + _______________ + |,----------. |\\ + || |=| | + || || | | + || . _o| | | + |`-----------' |/ + ~~~~~~~~~~~~~~~ + __ __ _ _ + / /____ / /__ _ __(_)__ (_)__ ___ +/ __/ -_) / -_) |/ / (_- PathBuf { } else { PathBuf::from(".").join(".config") }; + info!("Using config directory: {:?}", directory); directory } diff --git a/crates/television/television.rs b/crates/television/television.rs index 78994cc..057c7b7 100644 --- a/crates/television/television.rs +++ b/crates/television/television.rs @@ -15,8 +15,6 @@ use ratatui::{ use std::{collections::HashMap, str::FromStr}; use tokio::sync::mpsc::UnboundedSender; -use crate::entry::{Entry, ENTRY_PLACEHOLDER}; -use crate::previewers::Previewer; use crate::ui::get_border_style; use crate::ui::input::actions::InputActionHandler; use crate::ui::input::Input; @@ -29,6 +27,11 @@ use crate::{ channels::{CliTvChannel, TelevisionChannel}, utils::strings::shrink_with_ellipsis, }; +use crate::{ + entry::{Entry, ENTRY_PLACEHOLDER}, + ui::spinner::Spinner, +}; +use crate::{previewers::Previewer, ui::spinner::SpinnerState}; #[derive(PartialEq, Copy, Clone)] enum Pane { @@ -62,6 +65,8 @@ pub(crate) struct Television { /// benefiting from a cache mechanism. pub(crate) meta_paragraph_cache: HashMap<(String, u16, u16), Paragraph<'static>>, + spinner: Spinner, + spinner_state: SpinnerState, } impl Television { @@ -70,6 +75,9 @@ impl Television { let mut tv_channel = cli_channel.to_channel(); tv_channel.find(EMPTY_STRING); + let spinner = Spinner::default(); + let spinner_state = SpinnerState::from(&spinner); + Self { action_tx: None, config: Config::default(), @@ -86,6 +94,8 @@ impl Television { preview_pane_height: 0, current_preview_total_lines: 0, meta_paragraph_cache: HashMap::new(), + spinner, + spinner_state, } } @@ -408,7 +418,7 @@ impl Television { ) -> Result<()> { let layout = Layout::all_panes_centered(Dimensions::default(), area); //let layout = - // Layout::results_only_centered(Dimensions::new(40, 60), area); + //Layout::results_only_centered(Dimensions::new(40, 60), area); self.results_area_height = u32::from(layout.results.height); if let Some(preview_window) = layout.preview_window { @@ -478,6 +488,8 @@ impl Television { + 1) + 3, ), + // spinner + Constraint::Length(1), ]) .split(input_block_inner); @@ -500,6 +512,14 @@ impl Television { .alignment(Alignment::Left); frame.render_widget(input, inner_input_chunks[1]); + if self.channel.running() { + frame.render_stateful_widget( + self.spinner, + inner_input_chunks[3], + &mut self.spinner_state, + ); + } + let result_count_block = Block::default(); let result_count = Paragraph::new(Span::styled( format!( diff --git a/crates/television/ui.rs b/crates/television/ui.rs index 3166840..81633a6 100644 --- a/crates/television/ui.rs +++ b/crates/television/ui.rs @@ -4,6 +4,7 @@ pub mod input; pub mod layout; pub mod preview; pub mod results; +pub mod spinner; // input //const DEFAULT_INPUT_FG: Color = Color::Rgb(200, 200, 200); diff --git a/crates/television/ui/spinner.rs b/crates/television/ui/spinner.rs new file mode 100644 index 0000000..3b0f32a --- /dev/null +++ b/crates/television/ui/spinner.rs @@ -0,0 +1,71 @@ +use ratatui::{ + buffer::Buffer, layout::Rect, style::Style, widgets::StatefulWidget, +}; + +//const FRAMES: &[char] = &[ +// '⠄', '⠆', '⠇', '⠋', '⠙', '⠸', '⠰', '⠠', '⠰', '⠸', '⠙', '⠋', '⠇', '⠆', +//]; + +const FRAMES: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + +/// A spinner widget. +#[derive(Debug, Clone, Copy)] +pub struct Spinner { + frames: &'static [&'static str], +} + +impl Spinner { + pub fn new(frames: &'static [&str]) -> Spinner { + Spinner { frames } + } + + pub fn frame(&self, index: usize) -> &str { + self.frames[index] + } +} + +impl Default for Spinner { + fn default() -> Spinner { + Spinner::new(FRAMES) + } +} + +#[derive(Debug)] +pub struct SpinnerState { + pub current_frame: usize, + total_frames: usize, +} + +impl SpinnerState { + pub fn new(total_frames: usize) -> SpinnerState { + SpinnerState { + current_frame: 0, + total_frames, + } + } + + fn tick(&mut self) { + self.current_frame = (self.current_frame + 1) % self.total_frames; + } +} + +impl From<&Spinner> for SpinnerState { + fn from(spinner: &Spinner) -> SpinnerState { + SpinnerState::new(spinner.frames.len()) + } +} + +impl StatefulWidget for Spinner { + type State = SpinnerState; + + /// Renders the spinner in the given area. + fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { + buf.set_string( + area.left(), + area.top(), + self.frame(state.current_frame), + Style::default(), + ); + state.tick(); + } +}