perf: preview

This commit is contained in:
Alexandre Pasmantier 2025-01-11 22:16:35 +01:00
parent d68ae21630
commit cb77f5c663
11 changed files with 277 additions and 115 deletions

1
Cargo.lock generated
View File

@ -3093,6 +3093,7 @@ name = "television-screen"
version = "0.0.21" version = "0.0.21"
dependencies = [ dependencies = [
"color-eyre", "color-eyre",
"devicons",
"ratatui", "ratatui",
"rustc-hash", "rustc-hash",
"serde", "serde",

View File

@ -56,6 +56,7 @@ readme = "README.md"
[workspace.dependencies] [workspace.dependencies]
directories = "5.0.1" directories = "5.0.1"
devicons = "0.6.11"
color-eyre = "0.6.3" color-eyre = "0.6.3"
lazy_static = "1.5.0" lazy_static = "1.5.0"
tokio = { version = "1.41.1", features = ["full"] } tokio = { version = "1.41.1", features = ["full"] }
@ -104,7 +105,7 @@ copypasta = "0.10.1"
[dev-dependencies] [dev-dependencies]
criterion = "0.5.1" criterion = "0.5.1"
devicons = "0.6.11" devicons = { workspace = true }
[[bin]] [[bin]]
bench = false bench = false

View File

@ -20,6 +20,7 @@ television-derive = { path = "../television-derive", version = "0.0.21" }
tracing = { workspace = true } tracing = { workspace = true }
tokio = { workspace = true, features = ["rt"] } tokio = { workspace = true, features = ["rt"] }
clap = { workspace = true, features = ["derive"] } clap = { workspace = true, features = ["derive"] }
devicons = { workspace = true }
directories = { workspace = true } directories = { workspace = true }
color-eyre = { workspace = true } color-eyre = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
@ -27,7 +28,6 @@ lazy_static = { workspace = true }
toml = { workspace = true } toml = { workspace = true }
rustc-hash = { workspace = true } rustc-hash = { workspace = true }
devicons = "0.6.11"
ignore = "0.4.23" ignore = "0.4.23"
strum = { version = "0.26.3", features = ["derive"] } strum = { version = "0.26.3", features = ["derive"] }
regex = "1.11.1" regex = "1.11.1"

View File

@ -17,6 +17,7 @@ television-channels = { path = "../television-channels", version = "0.0.21" }
television-utils = { path = "../television-utils", version = "0.0.21" } television-utils = { path = "../television-utils", version = "0.0.21" }
syntect = { workspace = true } syntect = { workspace = true }
devicons = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
color-eyre = { workspace = true } color-eyre = { workspace = true }
@ -24,7 +25,6 @@ lazy_static = { workspace = true }
rustc-hash = { workspace = true } rustc-hash = { workspace = true }
parking_lot = "0.12.3" parking_lot = "0.12.3"
devicons = "0.6.11"
regex = "1.11.1" regex = "1.11.1"
nom = "7.1" nom = "7.1"
tui = { version = "0.29", default-features = false, package = "ratatui" } tui = { version = "0.29", default-features = false, package = "ratatui" }

View File

@ -1,6 +1,11 @@
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;
use cache::PreviewCache;
use devicons::FileIcon; use devicons::FileIcon;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use television_channels::entry::{Entry, PreviewType}; use television_channels::entry::{Entry, PreviewType};
pub mod basic; pub mod basic;
@ -20,6 +25,7 @@ pub use env::EnvVarPreviewerConfig;
pub use files::FilePreviewer; pub use files::FilePreviewer;
pub use files::FilePreviewerConfig; pub use files::FilePreviewerConfig;
use syntect::highlighting::Style; use syntect::highlighting::Style;
use tracing::debug;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum PreviewContent { pub enum PreviewContent {
@ -47,7 +53,6 @@ pub struct Preview {
pub title: String, pub title: String,
pub content: PreviewContent, pub content: PreviewContent,
pub icon: Option<FileIcon>, pub icon: Option<FileIcon>,
pub stale: bool,
} }
impl Default for Preview { impl Default for Preview {
@ -56,7 +61,6 @@ impl Default for Preview {
title: String::new(), title: String::new(),
content: PreviewContent::Empty, content: PreviewContent::Empty,
icon: None, icon: None,
stale: false,
} }
} }
} }
@ -72,14 +76,6 @@ impl Preview {
title, title,
content, content,
icon, icon,
stale,
}
}
pub fn stale(&self) -> Self {
Preview {
stale: true,
..self.clone()
} }
} }
@ -105,6 +101,8 @@ pub struct Previewer {
file: FilePreviewer, file: FilePreviewer,
env_var: EnvVarPreviewer, env_var: EnvVarPreviewer,
command: CommandPreviewer, command: CommandPreviewer,
requests: FxHashMap<Entry, Instant>,
cache: Arc<Mutex<PreviewCache>>,
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -132,6 +130,9 @@ impl PreviewerConfig {
} }
} }
const DEBOUNCE_DURATION: Duration = Duration::from_millis(20);
const REQUEST_TIMEOUT: Duration = Duration::from_millis(200);
impl Previewer { impl Previewer {
pub fn new(config: Option<PreviewerConfig>) -> Self { pub fn new(config: Option<PreviewerConfig>) -> Self {
let config = config.unwrap_or_default(); let config = config.unwrap_or_default();
@ -140,16 +141,54 @@ impl Previewer {
file: FilePreviewer::new(Some(config.file)), file: FilePreviewer::new(Some(config.file)),
env_var: EnvVarPreviewer::new(Some(config.env_var)), env_var: EnvVarPreviewer::new(Some(config.env_var)),
command: CommandPreviewer::new(Some(config.command)), command: CommandPreviewer::new(Some(config.command)),
requests: FxHashMap::default(),
cache: Arc::new(Mutex::new(PreviewCache::default())),
} }
} }
pub fn preview(&mut self, entry: &Entry) -> Arc<Preview> { pub fn preview(&mut self, entry: &Entry) -> Option<Arc<Preview>> {
match &entry.preview_type { // remove any requests that have timed out
PreviewType::Basic => self.basic.preview(entry), self.requests
PreviewType::EnvVar => self.env_var.preview(entry), .retain(|e, v| v.elapsed() < REQUEST_TIMEOUT || e == entry);
// if we have a preview in cache, return it
if let Some(preview) = self.cache.lock().get(&entry.name) {
debug!("Preview already in cache");
return Some(preview);
}
// if we've already acknowledged the request
if let Some(initial_request) = self.requests.get(entry) {
debug!("Request already acknowledged");
// and we're past the debounce duration
if initial_request.elapsed() > DEBOUNCE_DURATION {
debug!("Past debounce duration");
// forward the request to the appropriate previewer
let preview = match &entry.preview_type {
PreviewType::Basic => Some(self.basic.preview(entry)),
PreviewType::EnvVar => Some(self.env_var.preview(entry)),
PreviewType::Files => self.file.preview(entry), PreviewType::Files => self.file.preview(entry),
PreviewType::Command(cmd) => self.command.preview(entry, cmd), PreviewType::Command(cmd) => {
PreviewType::None => Arc::new(Preview::default()), self.command.preview(entry, cmd)
}
PreviewType::None => Some(Arc::new(Preview::default())),
};
// if we got a preview, cache it
if let Some(preview) = preview {
self.cache.lock().insert(entry.name.clone(), &preview);
Some(preview)
} else {
None
}
} else {
debug!("Not past debounce duration");
None
}
}
// if we haven't acknowledged the request yet
else {
debug!("Request not acknowledged, acknowledging");
self.requests.insert(entry.clone(), Instant::now());
None
} }
} }

View File

@ -16,7 +16,6 @@ pub struct CommandPreviewer {
cache: Arc<Mutex<PreviewCache>>, cache: Arc<Mutex<PreviewCache>>,
config: CommandPreviewerConfig, config: CommandPreviewerConfig,
concurrent_preview_tasks: Arc<AtomicU8>, concurrent_preview_tasks: Arc<AtomicU8>,
last_previewed: Arc<Mutex<Arc<Preview>>>,
in_flight_previews: Arc<Mutex<FxHashSet<String>>>, in_flight_previews: Arc<Mutex<FxHashSet<String>>>,
} }
@ -53,9 +52,6 @@ impl CommandPreviewer {
cache: Arc::new(Mutex::new(PreviewCache::default())), cache: Arc::new(Mutex::new(PreviewCache::default())),
config, config,
concurrent_preview_tasks: Arc::new(AtomicU8::new(0)), concurrent_preview_tasks: Arc::new(AtomicU8::new(0)),
last_previewed: Arc::new(Mutex::new(Arc::new(
Preview::default().stale(),
))),
in_flight_previews: Arc::new(Mutex::new(FxHashSet::default())), in_flight_previews: Arc::new(Mutex::new(FxHashSet::default())),
} }
} }
@ -64,17 +60,17 @@ impl CommandPreviewer {
&mut self, &mut self,
entry: &Entry, entry: &Entry,
command: &PreviewCommand, command: &PreviewCommand,
) -> Arc<Preview> { ) -> Option<Arc<Preview>> {
// do we have a preview in cache for that entry? // do we have a preview in cache for that entry?
if let Some(preview) = self.cache.lock().get(&entry.name) { if let Some(preview) = self.cache.lock().get(&entry.name) {
return preview.clone(); return Some(preview);
} }
debug!("Preview cache miss for {:?}", entry.name); debug!("Preview cache miss for {:?}", entry.name);
// are we already computing a preview in the background for that entry? // are we already computing a preview in the background for that entry?
if self.in_flight_previews.lock().contains(&entry.name) { if self.in_flight_previews.lock().contains(&entry.name) {
debug!("Preview already in flight for {:?}", entry.name); debug!("Preview already in flight for {:?}", entry.name);
return self.last_previewed.lock().clone(); return None;
} }
if self.concurrent_preview_tasks.load(Ordering::Relaxed) if self.concurrent_preview_tasks.load(Ordering::Relaxed)
@ -86,21 +82,14 @@ impl CommandPreviewer {
let entry_c = entry.clone(); let entry_c = entry.clone();
let concurrent_tasks = self.concurrent_preview_tasks.clone(); let concurrent_tasks = self.concurrent_preview_tasks.clone();
let command = command.clone(); let command = command.clone();
let last_previewed = self.last_previewed.clone();
tokio::spawn(async move { tokio::spawn(async move {
try_preview( try_preview(&command, &entry_c, &cache, &concurrent_tasks);
&command,
&entry_c,
&cache,
&concurrent_tasks,
&last_previewed,
);
}); });
} else { } else {
debug!("Too many concurrent preview tasks running"); debug!("Too many concurrent preview tasks running");
} }
self.last_previewed.lock().clone() None
} }
} }
@ -149,7 +138,6 @@ pub fn try_preview(
entry: &Entry, entry: &Entry,
cache: &Arc<Mutex<PreviewCache>>, cache: &Arc<Mutex<PreviewCache>>,
concurrent_tasks: &Arc<AtomicU8>, concurrent_tasks: &Arc<AtomicU8>,
last_previewed: &Arc<Mutex<Arc<Preview>>>,
) { ) {
debug!("Computing preview for {:?}", entry.name); debug!("Computing preview for {:?}", entry.name);
let command = format_command(command, entry); let command = format_command(command, entry);
@ -170,8 +158,6 @@ pub fn try_preview(
)); ));
cache.lock().insert(entry.name.clone(), &preview); cache.lock().insert(entry.name.clone(), &preview);
let mut tp = last_previewed.lock();
*tp = preview.stale().into();
} else { } else {
let content = String::from_utf8_lossy(&output.stderr); let content = String::from_utf8_lossy(&output.stderr);
let preview = Arc::new(Preview::new( let preview = Arc::new(Preview::new(

View File

@ -28,7 +28,6 @@ pub struct FilePreviewer {
pub syntax_set: Arc<SyntaxSet>, pub syntax_set: Arc<SyntaxSet>,
pub syntax_theme: Arc<Theme>, pub syntax_theme: Arc<Theme>,
concurrent_preview_tasks: Arc<AtomicU8>, concurrent_preview_tasks: Arc<AtomicU8>,
last_previewed: Arc<Mutex<Arc<Preview>>>,
in_flight_previews: Arc<Mutex<FxHashSet<String>>>, in_flight_previews: Arc<Mutex<FxHashSet<String>>>,
} }
@ -72,9 +71,6 @@ impl FilePreviewer {
syntax_set: Arc::new(syntax_set), syntax_set: Arc::new(syntax_set),
syntax_theme: Arc::new(theme), syntax_theme: Arc::new(theme),
concurrent_preview_tasks: Arc::new(AtomicU8::new(0)), concurrent_preview_tasks: Arc::new(AtomicU8::new(0)),
last_previewed: Arc::new(Mutex::new(Arc::new(
Preview::default().stale(),
))),
in_flight_previews: Arc::new(Mutex::new(HashSet::with_hasher( in_flight_previews: Arc::new(Mutex::new(HashSet::with_hasher(
FxBuildHasher, FxBuildHasher,
))), ))),
@ -85,17 +81,17 @@ impl FilePreviewer {
/// ///
/// # Panics /// # Panics
/// Panics if seeking to the start of the file fails. /// Panics if seeking to the start of the file fails.
pub fn preview(&mut self, entry: &entry::Entry) -> Arc<Preview> { pub fn preview(&mut self, entry: &entry::Entry) -> Option<Arc<Preview>> {
// do we have a preview in cache for that entry? // do we have a preview in cache for that entry?
if let Some(preview) = self.cache.lock().get(&entry.name) { if let Some(preview) = self.cache.lock().get(&entry.name) {
return preview; return Some(preview);
} }
debug!("Preview cache miss for {:?}", entry.name); debug!("Preview cache miss for {:?}", entry.name);
// are we already computing a preview in the background for that entry? // are we already computing a preview in the background for that entry?
if self.in_flight_previews.lock().contains(&entry.name) { if self.in_flight_previews.lock().contains(&entry.name) {
debug!("Preview already in flight for {:?}", entry.name); debug!("Preview already in flight for {:?}", entry.name);
return self.last_previewed.lock().clone(); return None;
} }
if self.concurrent_preview_tasks.load(Ordering::Relaxed) if self.concurrent_preview_tasks.load(Ordering::Relaxed)
@ -109,7 +105,6 @@ impl FilePreviewer {
let syntax_set = self.syntax_set.clone(); let syntax_set = self.syntax_set.clone();
let syntax_theme = self.syntax_theme.clone(); let syntax_theme = self.syntax_theme.clone();
let concurrent_tasks = self.concurrent_preview_tasks.clone(); let concurrent_tasks = self.concurrent_preview_tasks.clone();
let last_previewed = self.last_previewed.clone();
let in_flight_previews = self.in_flight_previews.clone(); let in_flight_previews = self.in_flight_previews.clone();
tokio::spawn(async move { tokio::spawn(async move {
try_preview( try_preview(
@ -118,13 +113,12 @@ impl FilePreviewer {
&syntax_set, &syntax_set,
&syntax_theme, &syntax_theme,
&concurrent_tasks, &concurrent_tasks,
&last_previewed,
&in_flight_previews, &in_flight_previews,
); );
}); });
} }
self.last_previewed.lock().clone() None
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -139,7 +133,6 @@ pub fn try_preview(
syntax_set: &Arc<SyntaxSet>, syntax_set: &Arc<SyntaxSet>,
syntax_theme: &Arc<Theme>, syntax_theme: &Arc<Theme>,
concurrent_tasks: &Arc<AtomicU8>, concurrent_tasks: &Arc<AtomicU8>,
last_previewed: &Arc<Mutex<Arc<Preview>>>,
in_flight_previews: &Arc<Mutex<FxHashSet<String>>>, in_flight_previews: &Arc<Mutex<FxHashSet<String>>>,
) { ) {
debug!("Computing preview for {:?}", entry.name); debug!("Computing preview for {:?}", entry.name);
@ -166,8 +159,6 @@ pub fn try_preview(
); );
cache.lock().insert(entry.name.clone(), &preview); cache.lock().insert(entry.name.clone(), &preview);
in_flight_previews.lock().remove(&entry.name); in_flight_previews.lock().remove(&entry.name);
let mut tp = last_previewed.lock();
*tp = preview.stale().into();
} }
Err(e) => { Err(e) => {
warn!("Error opening file: {:?}", e); warn!("Error opening file: {:?}", e);

View File

@ -23,6 +23,7 @@ color-eyre = { workspace = true }
syntect = { workspace = true } syntect = { workspace = true }
rustc-hash = { workspace = true } rustc-hash = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
devicons = { workspace = true }
[lints] [lints]
workspace = true workspace = true

View File

@ -1,15 +1,41 @@
use devicons::FileIcon;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::sync::Arc; use std::sync::Arc;
use ratatui::widgets::Paragraph; use ratatui::widgets::Paragraph;
use television_utils::cache::RingSet; use television_utils::cache::RingSet;
const DEFAULT_RENDERED_PREVIEW_CACHE_SIZE: usize = 25; const DEFAULT_RENDERED_PREVIEW_CACHE_SIZE: usize = 10;
#[derive(Clone, Debug)]
pub struct CachedPreview<'a> {
pub key: String,
pub icon: Option<FileIcon>,
pub title: String,
pub paragraph: Arc<Paragraph<'a>>,
}
impl<'a> CachedPreview<'a> {
pub fn new(
key: String,
icon: Option<FileIcon>,
title: String,
paragraph: Arc<Paragraph<'a>>,
) -> Self {
CachedPreview {
key,
icon,
title,
paragraph,
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct RenderedPreviewCache<'a> { pub struct RenderedPreviewCache<'a> {
previews: FxHashMap<String, Arc<Paragraph<'a>>>, previews: FxHashMap<String, CachedPreview<'a>>,
ring_set: RingSet<String>, ring_set: RingSet<String>,
pub last_preview: Option<CachedPreview<'a>>,
} }
impl<'a> RenderedPreviewCache<'a> { impl<'a> RenderedPreviewCache<'a> {
@ -17,15 +43,29 @@ impl<'a> RenderedPreviewCache<'a> {
RenderedPreviewCache { RenderedPreviewCache {
previews: FxHashMap::default(), previews: FxHashMap::default(),
ring_set: RingSet::with_capacity(capacity), ring_set: RingSet::with_capacity(capacity),
last_preview: None,
} }
} }
pub fn get(&self, key: &str) -> Option<Arc<Paragraph<'a>>> { pub fn get(&self, key: &str) -> Option<CachedPreview<'a>> {
self.previews.get(key).cloned() self.previews.get(key).cloned()
} }
pub fn insert(&mut self, key: String, preview: &Arc<Paragraph<'a>>) { pub fn insert(
self.previews.insert(key.clone(), preview.clone()); &mut self,
key: String,
icon: Option<FileIcon>,
title: String,
paragraph: &Arc<Paragraph<'a>>,
) {
let cached_preview = CachedPreview::new(
key.clone(),
icon,
title.clone(),
paragraph.clone(),
);
self.last_preview = Some(cached_preview.clone());
self.previews.insert(key.clone(), cached_preview);
if let Some(oldest_key) = self.ring_set.push(key) { if let Some(oldest_key) = self.ring_set.push(key) {
self.previews.remove(&oldest_key); self.previews.remove(&oldest_key);
} }

View File

@ -3,6 +3,7 @@ use crate::{
colors::{Colorscheme, PreviewColorscheme}, colors::{Colorscheme, PreviewColorscheme},
}; };
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use devicons::FileIcon;
use ratatui::widgets::{Block, BorderType, Borders, Padding, Paragraph, Wrap}; use ratatui::widgets::{Block, BorderType, Borders, Padding, Paragraph, Wrap};
use ratatui::Frame; use ratatui::Frame;
use ratatui::{ use ratatui::{
@ -22,20 +23,28 @@ use television_utils::strings::{
replace_non_printable, shrink_with_ellipsis, ReplaceNonPrintableConfig, replace_non_printable, shrink_with_ellipsis, ReplaceNonPrintableConfig,
EMPTY_STRING, EMPTY_STRING,
}; };
use tracing::debug;
#[allow(dead_code)] #[allow(dead_code)]
const FILL_CHAR_SLANTED: char = ''; const FILL_CHAR_SLANTED: char = '';
const FILL_CHAR_EMPTY: char = ' '; const FILL_CHAR_EMPTY: char = ' ';
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
pub fn build_preview_paragraph( pub fn build_preview_paragraph<'a>(
preview_block: Block<'_>, //preview_block: Block<'_>,
inner: Rect, inner: Rect,
preview_content: PreviewContent, preview_content: PreviewContent,
target_line: Option<u16>, target_line: Option<u16>,
preview_scroll: u16, preview_scroll: u16,
colorscheme: Colorscheme, colorscheme: Colorscheme,
) -> Paragraph<'_> { ) -> Paragraph<'a> {
let preview_block =
Block::default().style(Style::default()).padding(Padding {
top: 0,
right: 1,
bottom: 0,
left: 1,
});
match preview_content { match preview_content {
PreviewContent::AnsiText(text) => { PreviewContent::AnsiText(text) => {
build_ansi_text_paragraph(text, preview_block, preview_scroll) build_ansi_text_paragraph(text, preview_block, preview_scroll)
@ -244,20 +253,18 @@ pub fn build_meta_preview_paragraph<'a>(
Paragraph::new(Text::from(lines)) Paragraph::new(Text::from(lines))
} }
#[allow(clippy::too_many_arguments)] fn draw_content_outer_block(
pub fn draw_preview_content_block(
f: &mut Frame, f: &mut Frame,
rect: Rect, rect: Rect,
entry: &Entry,
preview: &Arc<Preview>,
rendered_preview_cache: &Arc<Mutex<RenderedPreviewCache<'static>>>,
preview_scroll: u16,
use_nerd_font_icons: bool,
colorscheme: &Colorscheme, colorscheme: &Colorscheme,
) -> Result<()> { icon: Option<FileIcon>,
title: &str,
use_nerd_font_icons: bool,
) -> Result<Rect> {
let mut preview_title_spans = vec![Span::from(" ")]; let mut preview_title_spans = vec![Span::from(" ")];
if preview.icon.is_some() && use_nerd_font_icons { // optional icon
let icon = preview.icon.as_ref().unwrap(); if icon.is_some() && use_nerd_font_icons {
let icon = icon.as_ref().unwrap();
preview_title_spans.push(Span::styled( preview_title_spans.push(Span::styled(
{ {
let mut icon_str = String::from(icon.icon); let mut icon_str = String::from(icon.icon);
@ -267,10 +274,11 @@ pub fn draw_preview_content_block(
Style::default().fg(Color::from_str(icon.color)?), Style::default().fg(Color::from_str(icon.color)?),
)); ));
} }
// preview title
preview_title_spans.push(Span::styled( preview_title_spans.push(Span::styled(
shrink_with_ellipsis( shrink_with_ellipsis(
&replace_non_printable( &replace_non_printable(
preview.title.as_bytes(), title.as_bytes(),
&ReplaceNonPrintableConfig::default(), &ReplaceNonPrintableConfig::default(),
) )
.0, .0,
@ -279,6 +287,8 @@ pub fn draw_preview_content_block(
Style::default().fg(colorscheme.preview.title_fg).bold(), Style::default().fg(colorscheme.preview.title_fg).bold(),
)); ));
preview_title_spans.push(Span::from(" ")); preview_title_spans.push(Span::from(" "));
// build the preview block
let preview_outer_block = Block::default() let preview_outer_block = Block::default()
.title_top( .title_top(
Line::from(preview_title_spans) Line::from(preview_title_spans)
@ -294,47 +304,136 @@ pub fn draw_preview_content_block(
) )
.padding(Padding::new(0, 1, 1, 0)); .padding(Padding::new(0, 1, 1, 0));
let preview_inner_block =
Block::default().style(Style::default()).padding(Padding {
top: 0,
right: 1,
bottom: 0,
left: 1,
});
let inner = preview_outer_block.inner(rect); let inner = preview_outer_block.inner(rect);
f.render_widget(preview_outer_block, rect); f.render_widget(preview_outer_block, rect);
Ok(inner)
let target_line = entry.line_number.map(|l| u16::try_from(l).unwrap_or(0));
let cache_key = compute_cache_key(entry);
// Check if the rendered preview content is already in the cache
if let Some(preview_paragraph) =
rendered_preview_cache.lock().unwrap().get(&cache_key)
{
let p = preview_paragraph.as_ref().clone();
f.render_widget(p.scroll((preview_scroll, 0)), inner);
return Ok(());
} }
// If not, render the preview content and cache it if not empty
let c_scheme = colorscheme.clone(); #[allow(clippy::too_many_arguments)]
let rp = build_preview_paragraph( pub fn draw_preview_content_block(
preview_inner_block, f: &mut Frame,
inner, rect: Rect,
preview.content.clone(), entry: &Entry,
target_line, preview: &Option<Arc<Preview>>,
preview_scroll, rendered_preview_cache: &Arc<Mutex<RenderedPreviewCache<'static>>>,
c_scheme, preview_scroll: u16,
); use_nerd_font_icons: bool,
if !preview.stale { colorscheme: &Colorscheme,
rendered_preview_cache ) -> Result<()> {
if let Some(preview) = preview {
debug!("preview is Some");
let inner = draw_content_outer_block(
f,
rect,
colorscheme,
preview.icon,
&preview.title,
use_nerd_font_icons,
)?;
// check if the rendered preview content is already in the cache
let cache_key = compute_cache_key(entry);
let cached_preview = rendered_preview_cache
.lock() .lock()
.unwrap() .unwrap()
.insert(cache_key, &Arc::new(rp.clone())); .get(&cache_key)
} .into_iter();
f.render_widget( if let Some(rp) = cached_preview.clone().next() {
Arc::new(rp).as_ref().clone().scroll((preview_scroll, 0)), debug!("cached preview found");
let p = rp.paragraph.as_ref().clone();
f.render_widget(p.scroll((preview_scroll, 0)), inner);
return Ok(());
} else {
debug!("cached preview not found");
// render the preview content and cache it if not empty
let rp = build_preview_paragraph(
//preview_inner_block,
inner, inner,
preview.content.clone(),
entry.line_number.map(|l| u16::try_from(l).unwrap_or(0)),
preview_scroll,
colorscheme.clone(),
); );
debug!("inserting into cache");
rendered_preview_cache.lock().unwrap().insert(
cache_key,
preview.icon,
preview.title.clone(),
&Arc::new(rp.clone()),
);
debug!("rendering widget");
f.render_widget(rp.scroll((preview_scroll, 0)), inner);
return Ok(());
}
// else if last_preview exists
} else {
debug!("preview is None");
let maybe_last_preview =
&rendered_preview_cache.lock().unwrap().last_preview.clone();
if let Some(last_preview) = maybe_last_preview {
let inner = draw_content_outer_block(
f,
rect,
colorscheme,
last_preview.icon,
&last_preview.title,
use_nerd_font_icons,
)?;
// check if the rendered preview content is already in the cache
let cache_key = last_preview.key.clone();
let maybe_cached_preview =
rendered_preview_cache.lock().unwrap().get(&cache_key);
if let Some(cached_preview) = maybe_cached_preview.clone() {
let p = cached_preview.paragraph.as_ref().clone();
f.render_widget(p.scroll((preview_scroll, 0)), inner);
return Ok(());
} else {
// render the preview content and cache it if not empty
let rp = build_preview_paragraph(
inner,
PreviewContent::Empty,
None,
preview_scroll,
colorscheme.clone(),
);
rendered_preview_cache.lock().unwrap().insert(
cache_key,
last_preview.icon,
last_preview.title.clone(),
&Arc::new(rp.clone()),
);
f.render_widget(rp.scroll((preview_scroll, 0)), inner);
}
return Ok(());
}
// render empty preview
let inner = draw_content_outer_block(
f,
rect,
colorscheme,
None,
"",
use_nerd_font_icons,
)?;
let preview_outer_block = Block::default()
.title_top(Line::from(Span::styled(
" ",
Style::default().fg(colorscheme.preview.title_fg),
)))
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(colorscheme.general.border_fg))
.style(
Style::default()
.bg(colorscheme.general.background.unwrap_or_default()),
)
.padding(Padding::new(0, 1, 1, 0));
f.render_widget(preview_outer_block, inner);
}
Ok(()) Ok(())
} }

View File

@ -560,7 +560,9 @@ impl Television {
&& !matches!(selected_entry.preview_type, PreviewType::None) && !matches!(selected_entry.preview_type, PreviewType::None)
{ {
// preview content // preview content
let preview = self.previewer.preview(&selected_entry); let maybe_preview = self.previewer.preview(&selected_entry);
if let Some(preview) = &maybe_preview {
self.current_preview_total_lines = preview.total_lines(); self.current_preview_total_lines = preview.total_lines();
// initialize preview scroll // initialize preview scroll
self.maybe_init_preview_scroll( self.maybe_init_preview_scroll(
@ -569,11 +571,13 @@ impl Television {
.map(|l| u16::try_from(l).unwrap_or(0)), .map(|l| u16::try_from(l).unwrap_or(0)),
layout.preview_window.unwrap().height, layout.preview_window.unwrap().height,
); );
}
draw_preview_content_block( draw_preview_content_block(
f, f,
layout.preview_window.unwrap(), layout.preview_window.unwrap(),
&selected_entry, &selected_entry,
&preview, &maybe_preview,
&self.rendered_preview_cache, &self.rendered_preview_cache,
self.preview_scroll.unwrap_or(0), self.preview_scroll.unwrap_or(0),
self.config.ui.use_nerd_font_icons, self.config.ui.use_nerd_font_icons,