use std::cell::RefCell;
use ratatui::prelude::*;
use ratatui::widgets::{Block, Paragraph, Wrap};
use super::{render_info_line, render_search_bar, truncate_str};
use crate::tui::state::PopupState;
use crate::tui::theme::Theme;
use crate::tui::widgets::diff_view::try_render_with_delta;
thread_local! {
static DELTA_CACHE: RefCell<Option<(u64, u16, bool, Text<'static>)>> = const { RefCell::new(None) };
}
pub(super) fn render_info(
popup: &PopupState,
popup_area: Rect,
block: Block<'_>,
theme: &Theme,
buf: &mut Buffer,
) {
let inner = block.inner(popup_area);
block.render(popup_area, buf);
let looks_like_diff = popup.content.contains("\n+++ ")
|| popup.content.contains("\n--- ")
|| popup.content.starts_with("diff ")
|| popup.content.starts_with("--- ");
let content_hash = if looks_like_diff {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
popup.content.hash(&mut hasher);
hasher.finish()
} else {
0
};
let cached_delta = if looks_like_diff {
DELTA_CACHE.with(|cell| {
let c = cell.borrow();
if let Some((hash, w, dark, ref text)) = *c
&& hash == content_hash
&& w == inner.width
&& dark == theme.is_dark
{
return Some(text.clone());
}
None
})
} else {
None
};
let paragraph = if looks_like_diff {
let delta_text = cached_delta.or_else(|| {
let result = try_render_with_delta(&popup.content, theme.is_dark, inner.width);
if let Some(ref text) = result {
DELTA_CACHE.with(|cell| {
*cell.borrow_mut() =
Some((content_hash, inner.width, theme.is_dark, text.clone()));
});
}
result
});
if let Some(text) = delta_text {
Paragraph::new(text)
.wrap(Wrap { trim: false })
.scroll((popup.scroll, 0))
} else {
let lines: Vec<Line> = popup
.content
.lines()
.map(|line| render_info_line(line, theme))
.collect();
Paragraph::new(lines)
.wrap(Wrap { trim: false })
.scroll((popup.scroll, 0))
}
} else {
let lines: Vec<Line> = popup
.content
.lines()
.map(|line| render_info_line(line, theme))
.collect();
Paragraph::new(lines)
.wrap(Wrap { trim: false })
.scroll((popup.scroll, 0))
};
paragraph.render(inner, buf);
}
pub(super) fn render_select(
popup: &PopupState,
items: &[String],
selected: usize,
popup_area: Rect,
block: Block<'_>,
theme: &Theme,
buf: &mut Buffer,
) {
let q = popup.search.to_lowercase();
let filtered: Vec<&String> = items
.iter()
.filter(|s| s.to_lowercase().contains(&q))
.collect();
let inner = block.inner(popup_area);
block.render(popup_area, buf);
render_search_bar(
inner,
buf,
&popup.search,
filtered.len(),
items.len(),
theme,
);
let list_area = Rect::new(
inner.x,
inner.y + 1,
inner.width,
inner.height.saturating_sub(1),
);
let skip = popup.scroll as usize;
let mut row_y = list_area.y;
for (i, item) in filtered.iter().enumerate() {
if i < skip {
continue;
}
if row_y >= list_area.y + list_area.height {
break;
}
let is_sel = i == selected;
let row_area = Rect::new(list_area.x, row_y, list_area.width, 1);
if is_sel {
Paragraph::new(Span::styled(
" ".repeat(list_area.width as usize),
Style::default().bg(theme.accent),
))
.render(row_area, buf);
Paragraph::new(Span::styled(
format!(
" {}",
truncate_str(item, list_area.width.saturating_sub(2) as usize)
),
Style::default()
.fg(theme.bg)
.bg(theme.accent)
.add_modifier(Modifier::BOLD),
))
.render(row_area, buf);
} else {
Paragraph::new(Span::styled(
format!(
" {}",
truncate_str(item, list_area.width.saturating_sub(2) as usize)
),
Style::default().fg(theme.text_dim),
))
.render(row_area, buf);
}
row_y += 1;
}
if filtered.is_empty() {
let no_match_area = Rect::new(list_area.x, list_area.y, list_area.width, 1);
Paragraph::new(Span::styled(
" no matches",
Style::default().fg(theme.text_muted),
))
.render(no_match_area, buf);
}
}