From 941ed4671788fa7ba90b4b96f4b32cd46f305505 Mon Sep 17 00:00:00 2001 From: Alexandre Pasmantier Date: Sun, 6 Oct 2024 23:59:34 +0200 Subject: [PATCH] progress --- Cargo.toml | 4 + src/components/finders.rs | 7 + src/components/finders/env.rs | 76 ++++--- src/components/finders/grep.rs | 4 +- src/components/previewers.rs | 11 +- src/components/previewers/cache.rs | 98 +++++++++ src/components/previewers/env.rs | 1 - src/components/previewers/files.rs | 332 ++++++++++++++++------------- src/components/television.rs | 29 +-- src/components/utils/files.rs | 7 +- src/logging.rs | 11 +- src/main.rs | 6 +- 12 files changed, 381 insertions(+), 205 deletions(-) create mode 100644 src/components/previewers/cache.rs diff --git a/Cargo.toml b/Cargo.toml index efe7890..ae4cd91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,10 +65,13 @@ tracing-error = "0.2.0" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] } unicode-width = "0.2.0" + [build-dependencies] anyhow = "1.0.86" vergen-gix = { version = "1.0.0", features = ["build", "cargo"] } + + [profile.release] opt-level = 3 debug = "none" @@ -77,3 +80,4 @@ debug-assertions = false overflow-checks = false lto = "fat" panic = "abort" + diff --git a/src/components/finders.rs b/src/components/finders.rs index 28135f3..86f7995 100644 --- a/src/components/finders.rs +++ b/src/components/finders.rs @@ -27,6 +27,13 @@ impl Entry { pub fn display_name(&self) -> &str { self.display_name.as_ref().unwrap_or(&self.name) } + + pub fn stdout_repr(&self) -> String { + self.name.clone() + + &self + .line_number + .map_or(String::new(), |n| format!(":{}", n)) + } } pub const ENTRY_PLACEHOLDER: Entry = Entry { diff --git a/src/components/finders/env.rs b/src/components/finders/env.rs index 989c7c2..c823b94 100644 --- a/src/components/finders/env.rs +++ b/src/components/finders/env.rs @@ -35,8 +35,7 @@ impl EnvVarFinder { 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(); + cols[0] = (e.name.clone() + &e.value).into(); }); } EnvVarFinder { @@ -77,7 +76,6 @@ impl Finder for EnvVarFinder { 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(); @@ -85,12 +83,9 @@ impl Finder for EnvVarFinder { 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 col_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())) @@ -98,34 +93,29 @@ impl Finder for EnvVarFinder { snapshot.pattern().column_pattern(0).indices( item.matcher_columns[0].slice(..), &mut matcher, - &mut name_indices, + &mut col_indices, ); - name_indices.sort_unstable(); - name_indices.dedup(); - should_add_name_indices = !name_indices.is_empty(); - let name_indices = name_indices.drain(..); + col_indices.sort_unstable(); + col_indices.dedup(); - 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(); - let value_indices = value_indices.drain(..); + let ( + name_indices, + value_indices, + should_add_name_indices, + should_add_value_indices, + ) = sep_name_and_value_indices(&mut col_indices, item.data.name.len() as u32); Entry { - name: item.matcher_columns[0].to_string(), + name: item.data.name.clone(), display_name: None, - preview: Some(item.matcher_columns[1].to_string()), + preview: Some(item.data.value.clone()), name_match_ranges: if should_add_name_indices { - Some(name_indices.map(|i| (i, i + 1)).collect()) + Some(name_indices.into_iter().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()) + Some(value_indices.into_iter().map(|i| (i, i + 1)).collect()) } else { None }, @@ -139,8 +129,8 @@ impl Finder for EnvVarFinder { 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(); + let name = item.data.name.clone(); + let value = item.data.value.clone(); Some(Entry { name, display_name: None, @@ -154,3 +144,35 @@ impl Finder for EnvVarFinder { }) } } + +fn sep_name_and_value_indices( + indices: &mut Vec, + name_len: u32, +) -> (Vec, Vec, bool, bool) { + let mut name_indices = Vec::new(); + let mut value_indices = Vec::new(); + let mut should_add_name_indices = false; + let mut should_add_value_indices = false; + + for i in indices.drain(..) { + if i < name_len { + name_indices.push(i); + should_add_name_indices = true; + } else { + value_indices.push(i - name_len); + should_add_value_indices = true; + } + } + + name_indices.sort_unstable(); + name_indices.dedup(); + value_indices.sort_unstable(); + value_indices.dedup(); + + ( + name_indices, + value_indices, + should_add_name_indices, + should_add_value_indices, + ) +} diff --git a/src/components/finders/grep.rs b/src/components/finders/grep.rs index 8fd515c..d9daf33 100644 --- a/src/components/finders/grep.rs +++ b/src/components/finders/grep.rs @@ -133,8 +133,8 @@ impl Finder for GrepFinder { snapshot.get_matched_item(index).and_then(|item| { let display_path = item.data.path.to_string_lossy().to_string(); Some(Entry { - name: display_path.clone() + ":" + &item.data.line_number.to_string(), - display_name: Some(display_path), + name: display_path.clone(), + display_name: Some(display_path.clone() + ":" + &item.data.line_number.to_string()), preview: None, name_match_ranges: None, preview_match_ranges: None, diff --git a/src/components/previewers.rs b/src/components/previewers.rs index 78b4d03..0e29e7c 100644 --- a/src/components/previewers.rs +++ b/src/components/previewers.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use super::finders::Entry; +mod cache; mod env; mod files; @@ -41,7 +42,6 @@ pub const FILE_TOO_LARGE_MSG: &str = "File too large"; pub struct Preview { pub title: String, pub content: PreviewContent, - pub target_line: Option, } impl Default for Preview { @@ -49,18 +49,13 @@ impl Default for Preview { Preview { title: String::new(), content: PreviewContent::Empty, - target_line: None, } } } impl Preview { - pub fn new(title: String, content: PreviewContent, target_line: Option) -> Self { - Preview { - title, - content, - target_line, - } + pub fn new(title: String, content: PreviewContent) -> Self { + Preview { title, content } } pub fn total_lines(&self) -> u16 { diff --git a/src/components/previewers/cache.rs b/src/components/previewers/cache.rs new file mode 100644 index 0000000..22a15fc --- /dev/null +++ b/src/components/previewers/cache.rs @@ -0,0 +1,98 @@ +use std::{ + collections::{HashMap, HashSet, VecDeque}, + sync::Arc, +}; + +use tracing::debug; + +use crate::components::previewers::Preview; + +/// A ring buffer that also keeps track of the keys it contains to avoid duplicates. +struct SetRingBuffer { + ring_buffer: VecDeque, + known_keys: HashSet, + capacity: usize, +} + +impl SetRingBuffer +where + T: Eq + std::hash::Hash + Clone + std::fmt::Debug, +{ + pub fn with_capacity(capacity: usize) -> Self { + SetRingBuffer { + ring_buffer: VecDeque::with_capacity(capacity), + known_keys: HashSet::with_capacity(capacity), + capacity, + } + } + + // TODO: finish this + pub fn push(&mut self, key: T) -> Option { + // If the key is already in the buffer, do nothing + if self.contains(&key) { + debug!("Key already in ring buffer: {:?}", key); + return None; + } + // If the buffer is full, remove the oldest key + if self.ring_buffer.len() >= self.capacity { + self.pop(); + } + // finally, push the new key + self.ring_buffer.push_back(key.clone()); + self.known_keys.insert(key); + None + } + + pub fn pop(&mut self) -> Option { + if let Some(key) = self.ring_buffer.pop_front() { + debug!("Removing key from ring buffer: {:?}", key); + self.known_keys.remove(&key); + Some(key) + } else { + None + } + } + + pub fn contains(&self, key: &T) -> bool { + self.known_keys.contains(key) + } +} + +const DEFAULT_PREVIEW_CACHE_SIZE: usize = 100; + +/// A cache for previews. +pub struct PreviewCache { + entries: HashMap>, + set_ring_buffer: SetRingBuffer, +} + +impl PreviewCache { + pub fn new(max_size: usize) -> Self { + PreviewCache { + entries: HashMap::new(), + set_ring_buffer: SetRingBuffer::with_capacity(max_size), + } + } + + pub fn get(&self, key: &str) -> Option> { + self.entries.get(key).cloned() + } + + pub fn insert(&mut self, key: String, preview: Arc) { + debug!("Inserting preview into cache: {}", key); + self.entries.insert(key.clone(), preview.clone()); + self.set_ring_buffer.push(key); + if self.entry_buffer.len() >= self.max_size { + if let Some(oldest_key) = self.entry_buffer.pop_front() { + debug!("Cache full, removing oldest entry: {}", oldest_key); + self.entries.remove(&oldest_key); + } + } + } +} + +impl Default for PreviewCache { + fn default() -> Self { + PreviewCache::new(DEFAULT_PREVIEW_CACHE_SIZE) + } +} diff --git a/src/components/previewers/env.rs b/src/components/previewers/env.rs index 7050014..7dbc387 100644 --- a/src/components/previewers/env.rs +++ b/src/components/previewers/env.rs @@ -30,7 +30,6 @@ impl EnvVarPreviewer { } else { PreviewContent::Empty }, - target_line: None, }); self.cache.insert(entry.clone(), preview.clone()); preview diff --git a/src/components/previewers/files.rs b/src/components/previewers/files.rs index 82c09a6..3105b88 100644 --- a/src/components/previewers/files.rs +++ b/src/components/previewers/files.rs @@ -1,14 +1,9 @@ use color_eyre::Result; -use image::ImageReader; -use image::Rgb; +use image::{ImageReader, Rgb}; use ratatui_image::picker::Picker; -use std::collections::HashMap; use std::fs::File; -use std::io::BufRead; -use std::io::BufReader; -use std::io::Read; -use std::path::Path; -use std::path::PathBuf; +use std::io::{BufRead, BufReader, Read}; +use std::path::{Path, PathBuf}; use std::sync::Arc; use syntect::easy::HighlightLines; use tokio::sync::Mutex; @@ -21,13 +16,15 @@ use tracing::{debug, info, warn}; use crate::components::finders; use crate::components::previewers::{Preview, PreviewContent}; -use crate::components::utils::files::is_known_text_extension; use crate::components::utils::files::is_valid_utf8; use crate::components::utils::files::FileType; +use crate::components::utils::files::{get_file_size, is_known_text_extension}; use crate::components::utils::strings::preprocess_line; +use super::cache::PreviewCache; + pub struct FilePreviewer { - cache: Arc>>>, + cache: Arc>, syntax_set: Arc, syntax_theme: Arc, image_picker: Arc>, @@ -37,37 +34,78 @@ impl FilePreviewer { pub fn new() -> Self { let syntax_set = SyntaxSet::load_defaults_nonewlines(); let theme_set = ThemeSet::load_defaults(); - let mut image_picker = Picker::from_termios().unwrap(); - image_picker.guess_protocol(); - image_picker.background_color = Some(Rgb::([255, 0, 255])); + let image_picker = get_image_picker(); FilePreviewer { - cache: Arc::new(Mutex::new(HashMap::new())), + cache: Arc::new(Mutex::new(PreviewCache::default())), syntax_set: Arc::new(syntax_set), syntax_theme: Arc::new(theme_set.themes["base16-ocean.dark"].clone()), image_picker: Arc::new(Mutex::new(image_picker)), } } + async fn compute_image_preview(&self, entry: &finders::Entry) { + let cache = self.cache.clone(); + 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::new( + entry_c.name.clone(), + PreviewContent::Image(image), + )); + cache + .lock() + .await + .insert(entry_c.name.clone(), preview.clone()); + } + }); + } + + async fn compute_highlighted_text_preview(&self, entry: &finders::Entry, lines: Vec) { + 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 in the background for {:?}", + entry_c.name + ); + + match compute_highlights( + &PathBuf::from(&entry_c.name), + lines.clone(), + &syntax_set, + &syntax_theme, + ) { + Ok(highlighted_lines) => { + debug!("Successfully computed highlights for {:?}", entry_c.name); + cache.lock().await.insert( + entry_c.name.clone(), + Arc::new(Preview::new( + entry_c.name.clone(), + PreviewContent::HighlightedText(highlighted_lines), + )), + ); + debug!("Inserted highlighted preview into cache"); + } + Err(e) => { + warn!("Error computing highlights: {:?}", e); + } + }; + }); + } + /// The maximum file size that we will try to preview. /// 4 MB const MAX_FILE_SIZE: u64 = 4 * 1024 * 1024; - pub async fn preview(&mut self, entry: &finders::Entry) -> Arc { - let path_buf = PathBuf::from(&entry.display_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(&path_buf) { - if metadata.len() > Self::MAX_FILE_SIZE { - return Arc::new(file_too_large(&entry.name)); - } - } else { - warn!("Error getting metadata for {:?}", entry); - } - // try to determine file type - let mut file_type = match infer::get_from_path(&path_buf) { + fn get_file_type(&self, path: &Path) -> FileType { + debug!("Getting file type for {:?}", path); + let mut file_type = match infer::get_from_path(&path) { Ok(Some(t)) => { let mime_type = t.mime_type(); if mime_type.contains("image") { @@ -81,49 +119,12 @@ impl FilePreviewer { _ => FileType::Unknown, }; - // images - let cache = self.cache.clone(); - if matches!(file_type, FileType::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: None, - }), - ); - // 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: None, - }); - cache - .lock() - .await - .insert(entry_c.name.clone(), preview.clone()); - } - }); - return Arc::new(Preview { - title: entry.name.clone(), - content: PreviewContent::Loading, - target_line: None, - }); - } - // if the file type is unknown, try to determine it from the extension or the content if matches!(file_type, FileType::Unknown) { - if is_known_text_extension(&path_buf) { + if is_known_text_extension(&path) { file_type = FileType::Text; } else { - if let Ok(mut f) = File::open(&path_buf) { + if let Ok(mut f) = File::open(&path) { let mut buffer = [0u8; 256]; if let Ok(bytes_read) = f.read(&mut buffer) { if bytes_read > 0 && is_valid_utf8(&buffer) { @@ -133,95 +134,137 @@ impl FilePreviewer { } } } + debug!("File type for {:?}: {:?}", path, file_type); - if matches!(file_type, FileType::Unknown) { - return Arc::new(preview_not_supported(&entry.name)); + file_type + } + + async fn cache_preview(&mut self, key: String, preview: Arc) { + self.cache.lock().await.insert(key, preview); + } + + pub async fn preview(&mut self, entry: &finders::Entry) -> Arc { + let path_buf = PathBuf::from(&entry.name); + + // do we have a preview in cache for that entry? + if let Some(preview) = self.cache.lock().await.get(&entry.name) { + return preview.clone(); } + debug!("No preview in cache for {:?}", entry.name); + + // check file size + if get_file_size(&path_buf).map_or(false, |s| s > Self::MAX_FILE_SIZE) { + debug!("File too large: {:?}", entry.name); + let preview = file_too_large(&entry.name); + self.cache_preview(entry.name.clone(), preview.clone()) + .await; + return preview; + } + + // try to determine file type + debug!("Computing preview for {:?}", entry.name); + match self.get_file_type(&path_buf) { + FileType::Text => { + let preview = match File::open(&path_buf) { + Ok(file) => { + // insert a non-highlighted version of the preview into the cache + let reader = BufReader::new(file); + let preview = plain_text_preview(&entry.name, reader); + self.cache_preview(entry.name.clone(), preview.clone()) + .await; - // text files - let preview_content = match File::open(&path_buf) { - Ok(file) => { - let reader = BufReader::new(file); - let mut lines = Vec::new(); - let mut err = false; - - // read through the file and preprocess the lines - for maybe_line in reader.lines() { - match maybe_line { - Ok(line) => lines.push(preprocess_line(&line)), - Err(e) => { - warn!("Error reading file: {:?}", e); - err = true; - break; + match preview.content { + PreviewContent::PlainText(ref lines) => { + // compute the highlighted version in the background + self.compute_highlighted_text_preview(entry, lines.to_vec()) + .await; + } + _ => {} } + preview } - } - if err { - PreviewContent::NotSupported - } else { - PreviewContent::PlainText(lines) - } + Err(e) => { + warn!("Error opening file: {:?}", e); + let p = not_supported(&entry.name); + self.cache_preview(entry.name.clone(), p.clone()).await; + p + } + }; + preview } - Err(e) => { - warn!("Error opening file: {:?}", e); - PreviewContent::NotSupported + FileType::Image => { + debug!("Previewing image file: {:?}", entry.name); + // insert a loading preview into the cache + let preview = loading(&entry.name); + self.cache_preview(entry.name.clone(), preview.clone()) + .await; + // compute the image preview in the background + self.compute_image_preview(entry).await; + return preview; } - }; - let temp_preview = Arc::new(Preview { - title: entry.name.clone(), - content: preview_content.clone(), - target_line: entry.line_number.map(|l| l as u16), - }); - self.cache - .lock() - .await - .insert(entry.name.clone(), temp_preview.clone()); - let cache = self.cache.clone(); - let syntax_set = self.syntax_set.clone(); - let syntax_theme = self.syntax_theme.clone(); - let entry_c = entry.clone(); - match preview_content { - PreviewContent::PlainText(lines) => { - tokio::spawn(async move { - debug!("Computing highlights for {:?}", entry_c.name); - let preview = - match compute_highlights(&path_buf, lines, &syntax_set, &syntax_theme) { - Ok(highlighted_lines) => Preview { - title: entry_c.name.clone(), - content: PreviewContent::HighlightedText(highlighted_lines), - target_line: entry_c.line_number.map(|l| l 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)); - }); + FileType::Other => { + debug!("Previewing other file: {:?}", entry.name); + let preview = not_supported(&entry.name); + self.cache_preview(entry.name.clone(), preview.clone()) + .await; + return preview; + } + FileType::Unknown => { + debug!("Unknown file type: {:?}", entry.name); + let preview = not_supported(&entry.name); + self.cache_preview(entry.name.clone(), preview.clone()) + .await; + return preview; } - _ => {} } - return temp_preview; } } -fn preview_not_supported(title: &str) -> Preview { - Preview { - title: title.to_string(), - content: PreviewContent::NotSupported, - target_line: None, - } +fn get_image_picker() -> Picker { + //let mut picker = match Picker::from_termios() { + // Ok(p) => p, + // Err(_) => Picker::new((7, 14)), + //}; + let mut picker = Picker::new((7, 14)); + picker.guess_protocol(); + picker.background_color = Some(Rgb::([255, 0, 255])); + picker } -fn file_too_large(title: &str) -> Preview { - Preview { - title: title.to_string(), - content: PreviewContent::FileTooLarge, - target_line: None, +fn plain_text_preview(title: &str, reader: BufReader) -> Arc { + debug!("Creating plain text preview for {:?}", title); + let mut lines = Vec::new(); + for maybe_line in reader.lines() { + match maybe_line { + Ok(line) => lines.push(preprocess_line(&line)), + Err(e) => { + warn!("Error reading file: {:?}", e); + return not_supported(title); + } + } } + Arc::new(Preview::new( + title.to_string(), + PreviewContent::PlainText(lines), + )) +} + +fn not_supported(title: &str) -> Arc { + Arc::new(Preview::new( + title.to_string(), + PreviewContent::NotSupported, + )) +} + +fn file_too_large(title: &str) -> Arc { + Arc::new(Preview::new( + title.to_string(), + PreviewContent::FileTooLarge, + )) +} + +fn loading(title: &str) -> Arc { + Arc::new(Preview::new(title.to_string(), PreviewContent::Loading)) } fn compute_highlights<'a>( @@ -233,7 +276,10 @@ fn compute_highlights<'a>( let syntax = syntax_set .find_syntax_for_file(file_path)? .unwrap_or_else(|| { - warn!("No syntax found for {:?}", file_path); + warn!( + "No syntax found for {:?}, defaulting to plain text", + file_path + ); syntax_set.find_syntax_plain_text() }); let mut highlighter = HighlightLines::new(syntax, syntax_theme); diff --git a/src/components/television.rs b/src/components/television.rs index 0a7d652..ce413e3 100644 --- a/src/components/television.rs +++ b/src/components/television.rs @@ -300,6 +300,7 @@ const DEFAULT_RESULTS_COUNT_FG: Color = Color::Rgb(150, 150, 150); // preview const DEFAULT_PREVIEW_TITLE_FG: Color = Color::Blue; const DEFAULT_SELECTED_PREVIEW_BG: Color = Color::Rgb(50, 50, 50); +const DEFAULT_PREVIEW_CONTENT_FG: Color = Color::Rgb(150, 150, 180); const DEFAULT_PREVIEW_GUTTER_FG: Color = Color::Rgb(70, 70, 70); const DEFAULT_PREVIEW_GUTTER_SELECTED_FG: Color = Color::Rgb(255, 150, 150); @@ -479,12 +480,9 @@ impl Component for Television { } // top right block: preview title - let selected_entry = self.get_selected_entry(); + let selected_entry = self.get_selected_entry().unwrap_or(ENTRY_PLACEHOLDER); - let preview = block_on( - self.previewer - .preview(&selected_entry.unwrap_or(ENTRY_PLACEHOLDER)), - ); + let preview = block_on(self.previewer.preview(&selected_entry)); self.current_preview_total_lines = preview.total_lines(); let preview_title = Paragraph::new(Line::from(preview.title.clone())) .block( @@ -525,8 +523,12 @@ impl Component for Television { frame.render_stateful_widget(image_component, inner, &mut img.clone()); } _ => { - let preview_block = - self.build_preview_paragraph(preview_inner_block, &inner, preview); + let preview_block = self.build_preview_paragraph( + preview_inner_block, + &inner, + preview, + selected_entry.line_number.map(|l| l as u16), + ); frame.render_widget(preview_block, inner); } } @@ -678,15 +680,16 @@ impl Television { preview_block: Block<'b>, inner: &Rect, preview: Arc, + target_line: Option, ) -> Paragraph<'b> { - self.maybe_init_preview_scroll(preview.target_line, inner.height); + self.maybe_init_preview_scroll(target_line, inner.height); match &preview.content { PreviewContent::PlainText(content) => { let mut lines = Vec::new(); for (i, line) in content.iter().enumerate() { lines.push(Line::from(vec![ build_line_number_span(i + 1).style(Style::default().fg( - if matches!(preview.target_line, Some(l) if l == i as u16 + 1) { + if matches!(target_line, Some(l) if l == i as u16 + 1) { DEFAULT_PREVIEW_GUTTER_SELECTED_FG } else { DEFAULT_PREVIEW_GUTTER_FG @@ -695,8 +698,8 @@ impl Television { Span::styled(" │ ", Style::default().fg(DEFAULT_PREVIEW_GUTTER_FG).dim()), Span::styled( line.to_string(), - Style::default().fg(DEFAULT_RESULT_PREVIEW_FG).bg( - if matches!(preview.target_line, Some(l) if l == i as u16 + 1) { + Style::default().fg(DEFAULT_PREVIEW_CONTENT_FG).bg( + if matches!(target_line, Some(l) if l == i as u16 + 1) { DEFAULT_SELECTED_PREVIEW_BG } else { Color::Reset @@ -715,7 +718,7 @@ impl Television { for line in content.lines() { lines.push(Line::styled( line.to_string(), - Style::default().fg(DEFAULT_RESULT_PREVIEW_FG), + Style::default().fg(DEFAULT_PREVIEW_CONTENT_FG), )); } let text = Text::from(lines); @@ -726,7 +729,7 @@ impl Television { PreviewContent::HighlightedText(highlighted_lines) => { compute_paragraph_from_highlighted_lines( highlighted_lines, - preview.target_line.map(|l| l as usize), + target_line.map(|l| l as usize), ) .block(preview_block) .alignment(Alignment::Left) diff --git a/src/components/utils/files.rs b/src/components/utils/files.rs index 72c0a02..15c932f 100644 --- a/src/components/utils/files.rs +++ b/src/components/utils/files.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use std::path::Path; use ignore::{types::TypesBuilder, WalkBuilder}; -use infer::{get_from_path, Infer}; +use infer::Infer; use lazy_static::lazy_static; use crate::config::default_num_threads; @@ -23,6 +23,11 @@ pub fn walk_builder(path: &Path, n_threads: usize) -> WalkBuilder { builder } +pub fn get_file_size(path: &Path) -> Option { + std::fs::metadata(path).ok().map(|m| m.len()) +} + +#[derive(Debug)] pub enum FileType { Text, Image, diff --git a/src/logging.rs b/src/logging.rs index 0dbceff..907551f 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -5,7 +5,6 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter}; use crate::config; lazy_static::lazy_static! { - pub static ref LOG_ENV: String = format!("{}_LOGLEVEL", config::PROJECT_NAME.clone()); pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME")); } @@ -14,20 +13,14 @@ pub fn init() -> Result<()> { std::fs::create_dir_all(directory.clone())?; let log_path = directory.join(LOG_FILE.clone()); let log_file = std::fs::File::create(log_path)?; - let env_filter = EnvFilter::builder().with_default_directive(tracing::Level::INFO.into()); - // If the `RUST_LOG` environment variable is set, use that as the default, otherwise use the - // value of the `LOG_ENV` environment variable. If the `LOG_ENV` environment variable contains - // errors, then this will return an error. - let env_filter = env_filter - .try_from_env() - .or_else(|_| env_filter.with_env_var(LOG_ENV.clone()).from_env())?; let file_subscriber = fmt::layer() .with_file(true) .with_line_number(true) .with_writer(log_file) .with_target(false) .with_ansi(false) - .with_filter(env_filter); + .with_filter(EnvFilter::from_default_env()); + tracing_subscriber::registry() .with(file_subscriber) .with(ErrorLayer::default()) diff --git a/src/main.rs b/src/main.rs index 5e14161..6e35809 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use std::io::{stdout, Write}; + use clap::Parser; use cli::Cli; use color_eyre::Result; @@ -24,7 +26,9 @@ async fn main() -> Result<()> { let args = Cli::parse(); let mut app = App::new(args.channel, args.tick_rate, args.frame_rate).await?; if let Some(entry) = app.run().await? { - info!("{}", entry.name); + // print entry to stdout + info!("{:?}", entry); + stdout().write(entry.stdout_repr().as_bytes())?; } Ok(()) }