diff --git a/crates/television/channels.rs b/crates/television/channels.rs index 5bd6302..671965d 100644 --- a/crates/television/channels.rs +++ b/crates/television/channels.rs @@ -1,6 +1,6 @@ use crate::entry::Entry; use color_eyre::eyre::Result; -use television_derive::{CliChannel, Broadcast, UnitChannel}; +use television_derive::{Broadcast, CliChannel, UnitChannel}; mod alias; pub mod channels; @@ -70,7 +70,7 @@ pub trait OnAir: Send { /// Check if the channel is currently running. fn running(&self) -> bool; - + /// Turn off fn shutdown(&self); } diff --git a/crates/television/channels/alias.rs b/crates/television/channels/alias.rs index c0b7519..cb39def 100644 --- a/crates/television/channels/alias.rs +++ b/crates/television/channels/alias.rs @@ -199,9 +199,7 @@ impl OnAir for Channel { self.running } - fn shutdown(&self) { - todo!() - } + fn shutdown(&self) {} } #[allow(clippy::unused_async)] diff --git a/crates/television/channels/channels.rs b/crates/television/channels/channels.rs index 9b77378..2639a67 100644 --- a/crates/television/channels/channels.rs +++ b/crates/television/channels/channels.rs @@ -134,7 +134,5 @@ impl OnAir for SelectionChannel { self.running } - fn shutdown(&self) { - todo!() - } + fn shutdown(&self) {} } diff --git a/crates/television/channels/env.rs b/crates/television/channels/env.rs index 0c945f1..d38eebd 100644 --- a/crates/television/channels/env.rs +++ b/crates/television/channels/env.rs @@ -161,7 +161,5 @@ impl OnAir for Channel { self.running } - fn shutdown(&self) { - todo!() - } + fn shutdown(&self) {} } diff --git a/crates/television/channels/files.rs b/crates/television/channels/files.rs index a01b8e4..27f1a29 100644 --- a/crates/television/channels/files.rs +++ b/crates/television/channels/files.rs @@ -25,6 +25,7 @@ pub struct Channel { result_count: u32, total_count: u32, running: bool, + crawl_handle: tokio::task::JoinHandle<()>, // PERF: cache results (to make deleting characters smoother) but like // a shallow cache (maybe more like a stack actually? so we just pop result sets) } @@ -38,7 +39,7 @@ impl Channel { 1, ); // start loading files in the background - tokio::spawn(load_files( + let crawl_handle = tokio::spawn(load_files( starting_dir.to_path_buf(), matcher.injector(), )); @@ -48,6 +49,7 @@ impl Channel { result_count: 0, total_count: 0, running: false, + crawl_handle, } } @@ -133,7 +135,7 @@ impl OnAir for Channel { } fn shutdown(&self) { - todo!() + self.crawl_handle.abort(); } } diff --git a/crates/television/channels/git_repos.rs b/crates/television/channels/git_repos.rs index e1fb12f..3d953b1 100644 --- a/crates/television/channels/git_repos.rs +++ b/crates/television/channels/git_repos.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use color_eyre::owo_colors::OwoColorize; use devicons::FileIcon; use ignore::{overrides::OverrideBuilder, DirEntry}; @@ -6,7 +5,12 @@ use nucleo::{ pattern::{CaseMatching, Normalization}, Config, Nucleo, }; -use tokio::sync::{oneshot, watch}; +use parking_lot::Mutex; +use std::{collections::HashSet, sync::Arc}; +use tokio::{ + sync::{oneshot, watch}, + task::JoinHandle, +}; use tracing::debug; use crate::{ @@ -25,7 +29,10 @@ pub struct Channel { total_count: u32, running: bool, icon: FileIcon, - crawl_cancellation_tx: watch::Sender, + crawl_handle: JoinHandle<()>, + entry_cache: Arc>>, + // TODO: implement cache validation/invalidation + cache_valid: bool, } impl Channel { @@ -36,12 +43,13 @@ impl Channel { None, 1, ); + let entry_cache = Arc::new(Mutex::new(HashSet::new())); // start loading files in the background - let (tx, rx) = watch::channel(false); - tokio::spawn(crawl_for_repos( + // 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(), - rx, + entry_cache.clone(), )); Channel { matcher, @@ -50,7 +58,9 @@ impl Channel { total_count: 0, running: false, icon: FileIcon::from("git"), - crawl_cancellation_tx: tx, + crawl_handle, + entry_cache, + cache_valid: false, } } @@ -137,7 +147,8 @@ impl OnAir for Channel { } fn shutdown(&self) { - self.crawl_cancellation_tx.send(true).unwrap(); + debug!("Shutting down git repos channel"); + self.crawl_handle.abort(); } } @@ -145,7 +156,7 @@ impl OnAir for Channel { async fn crawl_for_repos( starting_point: std::path::PathBuf, injector: nucleo::Injector, - cancellation_rx: watch::Receiver, + entry_cache: Arc>>, ) { let mut walker_overrides_builder = OverrideBuilder::new(&starting_point); walker_overrides_builder.add(".git").unwrap(); @@ -158,25 +169,31 @@ async fn crawl_for_repos( walker.run(|| { let injector = injector.clone(); - let cancellation_rx = cancellation_rx.clone(); + let entry_cache = entry_cache.clone(); Box::new(move |result| { - if let Ok(true) = cancellation_rx.has_changed() { - debug!("Crawling for git repos cancelled"); - return ignore::WalkState::Quit; - } if let Ok(entry) = result { - if entry.file_type().unwrap().is_dir() - && entry.path().ends_with(".git") - { - debug!("Found git repo: {:?}", entry.path()); - let _ = injector.push(entry, |e, cols| { - cols[0] = e + 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 entry.path().ends_with(".git") { + let parent_path = entry .path() .parent() .unwrap() .to_string_lossy() - .into(); - }); + .to_string(); + debug!("Found git repo: {:?}", parent_path); + let _ = injector.push(entry, |_e, cols| { + cols[0] = parent_path.clone().into(); + }); + entry_cache.lock().insert(parent_path); + return ignore::WalkState::Skip; + } } } ignore::WalkState::Continue diff --git a/crates/television/channels/stdin.rs b/crates/television/channels/stdin.rs index 9a95ca1..e7a6b9a 100644 --- a/crates/television/channels/stdin.rs +++ b/crates/television/channels/stdin.rs @@ -150,7 +150,5 @@ impl OnAir for Channel { self.running } - fn shutdown(&self) { - todo!() - } + fn shutdown(&self) {} } diff --git a/crates/television/channels/text.rs b/crates/television/channels/text.rs index d66f79f..0ea8527 100644 --- a/crates/television/channels/text.rs +++ b/crates/television/channels/text.rs @@ -47,13 +47,14 @@ pub struct Channel { result_count: u32, total_count: u32, running: bool, + crawl_handle: tokio::task::JoinHandle<()>, } impl Channel { pub fn new(working_dir: &Path) -> Self { let matcher = Nucleo::new(Config::DEFAULT, Arc::new(|| {}), None, 1); // start loading files in the background - tokio::spawn(load_candidates( + let crawl_handle = tokio::spawn(load_candidates( working_dir.to_path_buf(), matcher.injector(), )); @@ -63,6 +64,7 @@ impl Channel { result_count: 0, total_count: 0, running: false, + crawl_handle, } } @@ -160,7 +162,7 @@ impl OnAir for Channel { } fn shutdown(&self) { - todo!() + self.crawl_handle.abort(); } } diff --git a/crates/television/television.rs b/crates/television/television.rs index 672f83d..451e88a 100644 --- a/crates/television/television.rs +++ b/crates/television/television.rs @@ -107,13 +107,18 @@ impl Television { self.reset_results_selection(); self.current_pattern = EMPTY_STRING.to_string(); self.input.reset(); + self.channel.shutdown(); self.channel = channel; } + const MAX_BACKUP_RESULTS: u32 = 1000; + fn backup_current_channel(&mut self) { self.last_channel = Some(UnitChannel::from(&self.channel)); - self.last_channel_results = - self.channel.results(self.channel.result_count(), 0); + self.last_channel_results = self.channel.results( + self.channel.result_count().min(Self::MAX_BACKUP_RESULTS), + 0, + ); } fn find(&mut self, pattern: &str) {