diff --git a/src/components/channels.rs b/src/components/channels.rs index dd1f0f4..09710a6 100644 --- a/src/components/channels.rs +++ b/src/components/channels.rs @@ -7,7 +7,7 @@ use crate::cli::UnitTvChannel; use crate::components::finders::Entry; use crate::components::previewers; use crate::components::{ - finders::{EnvVarFinder, FileFinder}, + finders::{EnvVarFinder, FileFinder, NucleoEnvVarFinder}, previewers::{EnvVarPreviewer, FilePreviewer}, }; @@ -58,7 +58,7 @@ impl TvChannel { pub fn get_tv_channel(channel: UnitTvChannel) -> TvChannel { match channel { UnitTvChannel::ENV => TvChannel::Env(EnvChannel { - finder: EnvVarFinder::new(), + finder: NucleoEnvVarFinder::new(), }), UnitTvChannel::FILES => TvChannel::Files(FilesChannel { finder: FileFinder::new(), @@ -68,7 +68,7 @@ pub fn get_tv_channel(channel: UnitTvChannel) -> TvChannel { } pub struct EnvChannel { - pub finder: EnvVarFinder, + pub finder: NucleoEnvVarFinder, } pub struct FilesChannel { diff --git a/src/components/finders.rs b/src/components/finders.rs index f04964f..f92bf0f 100644 --- a/src/components/finders.rs +++ b/src/components/finders.rs @@ -1,10 +1,12 @@ use devicons::FileIcon; mod env; +mod env_nucleo; mod files; // finder types pub use env::EnvVarFinder; +pub use env_nucleo::NucleoEnvVarFinder; pub use files::FileFinder; use super::previewers::PreviewType; diff --git a/src/components/finders/env_nucleo.rs b/src/components/finders/env_nucleo.rs new file mode 100644 index 0000000..c113657 --- /dev/null +++ b/src/components/finders/env_nucleo.rs @@ -0,0 +1,157 @@ +use devicons::FileIcon; +use nucleo::{ + pattern::{CaseMatching, Normalization}, + Config, Nucleo, +}; +use std::sync::Arc; + +use tracing::info; + +use crate::components::{ + finders::{Entry, Finder}, + fuzzy::MATCHER, + previewers::PreviewType, +}; + +struct EnvVar { + name: String, + value: String, +} + +pub struct NucleoEnvVarFinder { + matcher: Nucleo, + last_pattern: String, + file_icon: FileIcon, + result_count: u32, + total_count: u32, +} + +const NUM_THREADS: usize = 1; +const FILE_ICON_STR: &str = "config"; + +impl NucleoEnvVarFinder { + pub fn new() -> Self { + let matcher = Nucleo::new(Config::DEFAULT, Arc::new(|| {}), Some(NUM_THREADS), 2); + let injector = matcher.injector(); + for (name, value) in std::env::vars() { + let _ = injector.push(EnvVar { name, value }, |e, cols| { + cols[0] = e.name.clone().into(); + cols[1] = e.value.clone().into(); + }); + } + NucleoEnvVarFinder { + matcher, + last_pattern: String::new(), + file_icon: FileIcon::from(FILE_ICON_STR), + result_count: 0, + total_count: 0, + } + } + + const MATCHER_TICK_TIMEOUT: u64 = 10; +} + +impl Finder for NucleoEnvVarFinder { + fn find(&mut self, pattern: &str) { + if pattern != self.last_pattern { + self.matcher.pattern.reparse( + 0, + pattern, + CaseMatching::Smart, + Normalization::Smart, + if pattern.starts_with(&self.last_pattern) { + true + } else { + false + }, + ); + self.last_pattern = pattern.to_string(); + } + } + + fn result_count(&self) -> u32 { + self.result_count + } + + fn total_count(&self) -> u32 { + self.total_count + } + + // FIXME: find a trick to match name + value but not orthogonally like nucleo does + fn results(&mut self, num_entries: u32, offset: u32) -> impl Iterator { + let status = self.matcher.tick(Self::MATCHER_TICK_TIMEOUT); + let snapshot = self.matcher.snapshot(); + if status.changed { + self.result_count = snapshot.matched_item_count(); + self.total_count = snapshot.item_count(); + } + let mut name_indices = Vec::new(); + let mut value_indices = Vec::new(); + let mut matcher = MATCHER.lock(); + let icon = self.file_icon.clone(); + let mut should_add_name_indices = false; + let mut should_add_value_indices = false; + + snapshot + .matched_items(offset..(num_entries + offset).min(snapshot.matched_item_count())) + .map(move |item| { + snapshot.pattern().column_pattern(0).indices( + item.matcher_columns[0].slice(..), + &mut matcher, + &mut name_indices, + ); + name_indices.sort_unstable(); + name_indices.dedup(); + should_add_name_indices = !name_indices.is_empty(); + let name_indices = name_indices.drain(..); + + snapshot.pattern().column_pattern(1).indices( + item.matcher_columns[1].slice(..), + &mut matcher, + &mut value_indices, + ); + value_indices.sort_unstable(); + value_indices.dedup(); + should_add_value_indices = !value_indices.is_empty(); + info!("value_indices: {:?}", value_indices); + let value_indices = value_indices.drain(..); + + Entry { + name: item.matcher_columns[0].to_string(), + display_name: None, + preview: Some(item.matcher_columns[1].to_string()), + name_match_ranges: if should_add_name_indices { + Some(name_indices.map(|i| (i, i + 1)).collect()) + } else { + None + }, + preview_match_ranges: if should_add_value_indices { + Some(value_indices.map(|i| (i, i + 1)).collect()) + } else { + None + }, + icon: Some(icon), + line_number: None, + preview_type: PreviewType::Files, + } + }) + } + + fn get_result(&self, index: u32) -> Option { + let snapshot = self.matcher.snapshot(); + snapshot.get_matched_item(index).and_then(|item| { + let name = item.matcher_columns[0].to_string(); + let value = item.matcher_columns[1].to_string(); + Some(Entry { + name, + display_name: None, + preview: Some(value), + name_match_ranges: None, + preview_match_ranges: None, + icon: None, + line_number: None, + preview_type: PreviewType::EnvVar, + }) + }) + } +} diff --git a/src/components/finders/files.rs b/src/components/finders/files.rs index 594604e..2c23060 100644 --- a/src/components/finders/files.rs +++ b/src/components/finders/files.rs @@ -29,14 +29,14 @@ pub struct FileFinder { impl FileFinder { pub fn new() -> Self { - let high_matcher = Nucleo::new(Config::DEFAULT.match_paths(), Arc::new(|| {}), None, 1); + let matcher = Nucleo::new(Config::DEFAULT.match_paths(), Arc::new(|| {}), None, 1); // start loading files in the background tokio::spawn(load_files( std::env::current_dir().unwrap(), - high_matcher.injector(), + matcher.injector(), )); FileFinder { - matcher: high_matcher, + matcher, last_pattern: String::new(), result_count: 0, total_count: 0, diff --git a/src/components/previewers/files.rs b/src/components/previewers/files.rs index bb8d47f..0370213 100644 --- a/src/components/previewers/files.rs +++ b/src/components/previewers/files.rs @@ -4,6 +4,7 @@ use image::Rgb; use ratatui_image::picker::Picker; use std::collections::HashMap; use std::io::BufRead; +use std::path::Path; use std::sync::Arc; use tokio::sync::Mutex; @@ -43,13 +44,12 @@ impl FilePreviewer { const MAX_FILE_SIZE: u64 = 4 * 1024 * 1024; pub async fn preview(&mut self, entry: &finders::Entry) -> Arc { - let mut entry = entry.clone(); - entry.name = entry.name.replace(" ", "\\ "); + let path = Path::new(&entry.name); // check if we have that preview in the cache if let Some(preview) = self.cache.lock().await.get(&entry.name) { return preview.clone(); } - if let Ok(metadata) = std::fs::metadata(&entry.name) { + if let Ok(metadata) = std::fs::metadata(path) { if metadata.len() > Self::MAX_FILE_SIZE { return Arc::new(file_too_large(&entry.name)); } @@ -57,66 +57,68 @@ impl FilePreviewer { warn!("Error getting metadata for {:?}", entry); } let cache = self.cache.clone(); + let is_image = match infer::get_from_path(path) { + Ok(Some(t)) => t.mime_type().contains("image"), + _ => false, + }; // images - if let Ok(Some(t)) = infer::get_from_path(&entry.name) { - if t.mime_type().contains("image") { - // insert a loading preview into the cache - self.cache.lock().await.insert( - entry.name.clone(), - Arc::new(Preview { - title: entry.name.clone(), - content: PreviewContent::Loading, + if is_image { + // insert a loading preview into the cache + self.cache.lock().await.insert( + entry.name.clone(), + Arc::new(Preview { + title: entry.name.clone(), + content: PreviewContent::Loading, + target_line: 0, + }), + ); + // load the image in the background + let picker = self.image_picker.clone(); + let entry_c = entry.clone(); + tokio::spawn(async move { + info!("Loading image: {:?}", entry_c.name); + if let Ok(dyn_image) = ImageReader::open(entry_c.name.clone()).unwrap().decode() { + let image = picker.lock().await.new_resize_protocol(dyn_image); + let preview = Arc::new(Preview { + title: entry_c.name.clone(), + content: PreviewContent::Image(image), target_line: 0, - }), - ); - // load the image in the background - let picker = self.image_picker.clone(); - let entry_c = entry.clone(); - tokio::spawn(async move { - info!("Loading image: {:?}", entry_c.name); - if let Ok(dyn_image) = ImageReader::open(entry_c.name.clone()).unwrap().decode() - { - let image = picker.lock().await.new_resize_protocol(dyn_image); - let preview = Arc::new(Preview { - title: entry_c.name.clone(), - content: PreviewContent::Image(image), - target_line: 0, - }); - cache - .lock() - .await - .insert(entry_c.name.clone(), preview.clone()); + }); + cache + .lock() + .await + .insert(entry_c.name.clone(), preview.clone()); + } + }); + } else { + // text + let cache = self.cache.clone(); + let syntax_set = self.syntax_set.clone(); + let syntax_theme = self.syntax_theme.clone(); + let entry_c = entry.clone(); + tokio::spawn(async move { + debug!("Computing highlights for {:?}", entry_c.name); + let preview = match compute_highlights(&entry_c.name, &syntax_set, &syntax_theme) { + Ok(highlighted_lines) => Preview { + title: entry_c.name.clone(), + content: PreviewContent::HighlightedText(highlighted_lines), + target_line: entry_c.line_number.unwrap_or(0) as u16, + }, + Err(e) => { + warn!("Error computing highlights: {:?}", e); + preview_not_supported(&entry_c.name) } - }); - } + }; + cache + .lock() + .await + .insert(entry_c.name.clone(), Arc::new(preview.clone())); + }); } - // text - let cache = self.cache.clone(); - let syntax_set = self.syntax_set.clone(); - let syntax_theme = self.syntax_theme.clone(); - let entry_c = entry.clone(); - tokio::spawn(async move { - debug!("Computing highlights for {:?}", entry_c.name); - let preview = match compute_highlights(&entry_c.name, &syntax_set, &syntax_theme) { - Ok(highlighted_lines) => Preview { - title: entry_c.name.clone(), - content: PreviewContent::HighlightedText(highlighted_lines), - target_line: entry.line_number.unwrap_or(0) as u16, - }, - Err(e) => { - warn!("Error computing highlights: {:?}", e); - preview_not_supported(&entry_c.name) - } - }; - cache - .lock() - .await - .insert(entry_c.name.clone(), Arc::new(preview.clone())); - }); return Arc::new(Preview { title: entry.name.clone(), content: PreviewContent::Loading, - target_line: entry.line_number.unwrap_or(0) as u16, + target_line: 0, }); } } diff --git a/src/components/television.rs b/src/components/television.rs index d9a2125..2fedf85 100644 --- a/src/components/television.rs +++ b/src/components/television.rs @@ -103,6 +103,8 @@ impl Television { self.picker_state.select(Some(new_index)); if new_index == 0 { self.picker_view_offset = 0; + self.relative_picker_state.select(Some(0)); + return; } if self.relative_picker_state.selected().unwrap_or(0) == self.results_area_height as usize - 3