use hjkl_buffer::{BufferView, Gutter, Viewport};
use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::style::Style;
use ratatui::widgets::{Block, Borders, Paragraph};
use crate::highlight::PreviewHighlighter;
use crate::picker::Picker;
use crate::preview::PreviewSpans;
pub struct PreviewTheme {
pub border: Style,
pub gutter: Style,
pub non_text: Style,
pub cursor_line: Style,
}
pub fn preview_pane(
frame: &mut Frame,
picker: &Picker,
highlighter: &dyn PreviewHighlighter,
theme: &PreviewTheme,
area: Rect,
) {
let label = picker.preview_label().unwrap_or("(none)").to_string();
let status = picker.preview_status();
let title = if status.is_empty() {
format!(" preview — {label} ")
} else {
format!(" preview — {label} [{status}] ")
};
let block = Block::default()
.borders(Borders::ALL)
.border_style(theme.border)
.title(title);
let inner = block.inner(area);
frame.render_widget(block, area);
if !status.is_empty() {
let para = Paragraph::new(format!(" ({status})"));
frame.render_widget(para, inner);
return;
}
let buf = picker.preview_buffer();
let line_count = buf.lines().len();
let preview_spans = match picker.preview_path() {
Some(path) => {
let mut bytes = buf.lines().join("\n").into_bytes();
if !bytes.is_empty() {
bytes.push(b'\n');
}
highlighter.spans_for(path, &bytes)
}
None => PreviewSpans::default(),
};
let gw = gutter_width(line_count.max(1));
let viewport = Viewport {
top_row: picker.preview_top_row(),
top_col: 0,
width: inner.width.saturating_sub(gw),
height: inner.height,
text_width: inner.width.saturating_sub(gw),
..Viewport::default()
};
let resolver = |id: u32| {
preview_spans
.styles
.get(id as usize)
.copied()
.unwrap_or_default()
};
let cursor_line_bg = if picker.preview_match_row().is_some() {
theme.cursor_line
} else {
Style::default()
};
let view = BufferView {
buffer: buf,
viewport: &viewport,
selection: None,
resolver: &resolver,
cursor_line_bg,
cursor_column_bg: Style::default(),
selection_bg: Style::default(),
cursor_style: Style::default(),
gutter: Some(Gutter {
width: gw,
style: theme.gutter,
line_offset: picker.preview_line_offset(),
..Default::default()
}),
search_bg: Style::default(),
signs: &[],
conceals: &[],
spans: &preview_spans.by_row,
search_pattern: None,
non_text_style: theme.non_text,
diag_overlays: &[],
};
frame.render_widget(view, inner);
}
fn gutter_width(line_count: usize) -> u16 {
const NUMBERWIDTH: usize = 4;
let needed = line_count.to_string().len() + 1;
needed.max(NUMBERWIDTH) as u16
}