diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 0f3c1d2..e676702 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -1,9 +1,10 @@ name: changelog permissions: contents: write + pull_request: write + on: - pull_request: push: tags: - '[v]?[0-9]+.[0-9]+.[0-9]+' diff --git a/crates/television-previewers/src/previewers.rs b/crates/television-previewers/src/previewers.rs index a7e1c4b..33518aa 100644 --- a/crates/television-previewers/src/previewers.rs +++ b/crates/television-previewers/src/previewers.rs @@ -35,7 +35,7 @@ pub enum PreviewContent { } pub const PREVIEW_NOT_SUPPORTED_MSG: &str = - "Preview for this file type is not yet supported"; + "Preview for this file type is not supported"; pub const FILE_TOO_LARGE_MSG: &str = "File too large"; /// A preview of an entry. diff --git a/crates/television-previewers/src/previewers/files.rs b/crates/television-previewers/src/previewers/files.rs index 72b247a..a9314ea 100644 --- a/crates/television-previewers/src/previewers/files.rs +++ b/crates/television-previewers/src/previewers/files.rs @@ -5,6 +5,7 @@ use parking_lot::Mutex; use std::fs::File; use std::io::{BufRead, BufReader, Seek}; use std::path::PathBuf; +use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::Arc; use syntect::{ @@ -28,6 +29,8 @@ pub struct FilePreviewer { cache: Arc>, pub syntax_set: Arc, pub syntax_theme: Arc, + concurrent_preview_tasks: Arc, + last_previewed: Arc>>, //image_picker: Arc>, } @@ -42,6 +45,12 @@ impl FilePreviewerConfig { } } +/// The maximum file size that we will try to preview. +/// 4 MB +const MAX_FILE_SIZE: u64 = 4 * 1024 * 1024; + +const MAX_CONCURRENT_PREVIEW_TASKS: u8 = 2; + impl FilePreviewer { pub fn new(config: Option) -> Self { let hl_assets = load_highlighting_assets(); @@ -62,14 +71,12 @@ impl FilePreviewer { cache: Arc::new(Mutex::new(PreviewCache::default())), syntax_set: Arc::new(syntax_set), syntax_theme: Arc::new(theme), + concurrent_preview_tasks: Arc::new(AtomicU8::new(0)), + last_previewed: Arc::new(Mutex::new(Arc::new(Preview::default()))), //image_picker: Arc::new(Mutex::new(image_picker)), } } - /// The maximum file size that we will try to preview. - /// 4 MB - const MAX_FILE_SIZE: u64 = 4 * 1024 * 1024; - /// Get a preview for a file entry. /// /// # Panics @@ -83,44 +90,32 @@ impl FilePreviewer { } 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) + if self.concurrent_preview_tasks.load(Ordering::Relaxed) + < MAX_CONCURRENT_PREVIEW_TASKS { - debug!("File too large: {:?}", entry.name); - let preview = meta::file_too_large(&entry.name); - self.cache_preview(entry.name.clone(), preview.clone()); - return preview; + self.concurrent_preview_tasks + .fetch_add(1, Ordering::Relaxed); + let cache = self.cache.clone(); + let entry_c = entry.clone(); + let syntax_set = self.syntax_set.clone(); + let syntax_theme = self.syntax_theme.clone(); + let path = path_buf.clone(); + let concurrent_tasks = self.concurrent_preview_tasks.clone(); + let last_previewed = self.last_previewed.clone(); + tokio::spawn(async move { + try_preview( + entry_c, + path, + cache, + syntax_set, + syntax_theme, + concurrent_tasks, + last_previewed, + ); + }); } - // try to determine file type - debug!("Computing preview for {:?}", entry.name); - if let FileType::Text = FileType::from(&path_buf) { - debug!("File is text-based: {:?}", entry.name); - match File::open(&path_buf) { - Ok(file) => { - // insert a loading preview into the cache - let preview = meta::loading(&entry.name); - self.cache_preview(entry.name.clone(), preview.clone()); - - // compute the highlighted version in the background - let mut reader = BufReader::new(file); - reader.seek(std::io::SeekFrom::Start(0)).unwrap(); - self.compute_highlighted_text_preview(entry, reader); - preview - } - Err(e) => { - warn!("Error opening file: {:?}", e); - let p = meta::not_supported(&entry.name); - self.cache_preview(entry.name.clone(), p.clone()); - p - } - } - } else { - debug!("File isn't text-based: {:?}", entry.name); - let preview = meta::not_supported(&entry.name); - self.cache_preview(entry.name.clone(), preview.clone()); - preview - } + self.last_previewed.lock().clone() } //async fn compute_image_preview(&self, entry: &entry::Entry) { @@ -145,65 +140,101 @@ impl FilePreviewer { // }); //} - fn compute_highlighted_text_preview( - &self, - entry: &entry::Entry, - reader: BufReader, - ) { - 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 - ); - let lines: Vec = reader - .lines() - .map_while(Result::ok) - // we need to add a newline here because sublime syntaxes expect one - // to be present at the end of each line - .map(|line| preprocess_line(&line).0 + "\n") - .collect(); - - match syntax::compute_highlights_for_path( - &PathBuf::from(&entry_c.name), - lines, - &syntax_set, - &syntax_theme, - ) { - Ok(highlighted_lines) => { - debug!( - "Successfully computed highlights for {:?}", - entry_c.name - ); - cache.lock().insert( - entry_c.name.clone(), - Arc::new(Preview::new( - entry_c.name, - PreviewContent::SyntectHighlightedText( - highlighted_lines, - ), - entry_c.icon, - )), - ); - debug!("Inserted highlighted preview into cache"); - } - Err(e) => { - warn!("Error computing highlights: {:?}", e); - let preview = meta::not_supported(&entry_c.name); - cache.lock().insert(entry_c.name.clone(), preview); - } - }; - }); - } - + #[allow(dead_code)] fn cache_preview(&mut self, key: String, preview: Arc) { self.cache.lock().insert(key, preview); } } +pub fn try_preview( + entry: entry::Entry, + path: PathBuf, + cache: Arc>, + syntax_set: Arc, + syntax_theme: Arc, + concurrent_tasks: Arc, + last_previewed: Arc>>, +) { + debug!("Computing preview for {:?}", entry.name); + tokio::spawn(async move { + // check file size + if get_file_size(&path).map_or(false, |s| s > MAX_FILE_SIZE) { + debug!("File too large: {:?}", entry.name); + let preview = meta::file_too_large(&entry.name); + cache.lock().insert(entry.name.clone(), preview.clone()); + } + + if matches!(FileType::from(&path), FileType::Text) { + debug!("File is text-based: {:?}", entry.name); + match File::open(&path) { + Ok(file) => { + // compute the highlighted version in the background + let mut reader = BufReader::new(file); + reader.seek(std::io::SeekFrom::Start(0)).unwrap(); + let preview = compute_highlighted_text_preview( + &entry, + reader, + &syntax_set, + &syntax_theme, + ); + cache.lock().insert(entry.name.clone(), preview.clone()); + let mut tp = last_previewed.lock(); + *tp = preview; + } + Err(e) => { + warn!("Error opening file: {:?}", e); + let p = meta::not_supported(&entry.name); + cache.lock().insert(entry.name.clone(), p.clone()); + } + } + } else { + debug!("File isn't text-based: {:?}", entry.name); + let preview = meta::not_supported(&entry.name); + cache.lock().insert(entry.name.clone(), preview.clone()); + } + concurrent_tasks.fetch_sub(1, Ordering::Relaxed); + }); +} + +fn compute_highlighted_text_preview( + entry: &entry::Entry, + reader: BufReader, + syntax_set: &SyntaxSet, + syntax_theme: &Theme, +) -> Arc { + debug!( + "Computing highlights in the background for {:?}", + entry.name + ); + let lines: Vec = reader + .lines() + .map_while(Result::ok) + // we need to add a newline here because sublime syntaxes expect one + // to be present at the end of each line + .map(|line| preprocess_line(&line).0 + "\n") + .collect(); + + match syntax::compute_highlights_for_path( + &PathBuf::from(&entry.name), + lines, + syntax_set, + syntax_theme, + ) { + Ok(highlighted_lines) => { + debug!("Successfully computed highlights for {:?}", entry.name); + Arc::new(Preview::new( + entry.name.clone(), + PreviewContent::SyntectHighlightedText(highlighted_lines), + entry.icon, + )) + } + Err(e) => { + warn!("Error computing highlights: {:?}", e); + meta::not_supported(&entry.name) + } + } +} + //fn get_image_picker() -> Picker { // let mut picker = match Picker::from_termios() { // Ok(p) => p,