use super::*;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::{Duration, Instant};
use chrono::Local;
use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use crate::domain::repo::RepoKind;
use crate::service::preview_service::{self};
pub(super) const PREVIEW_DEBOUNCE: Duration = Duration::from_millis(120);
pub(super) const PREVIEW_LOG_LINES: usize = 5;
pub(super) const PREVIEW_PAGE_ROWS: i32 = 10;
pub(super) const PANEL_GUTTER: u16 = 1;
pub(super) const MIN_LIST_COLS: u16 = 20;
pub(super) const MIN_LIST_ROWS: u16 = 3;
impl App {
pub(super) fn toggle_preview(&mut self) {
self.preview.toggle();
self.preview_scroll.reset();
self.save_ui_state();
}
pub(super) fn flip_preview_position(&mut self) {
self.preview.flip_position();
self.save_ui_state();
}
pub(super) fn resize_preview(&mut self, step: i16) {
if !self.preview.visible {
return;
}
self.preview.resize(step);
self.save_ui_state();
}
pub(super) fn scroll_preview(&mut self, delta: i32) {
if self.preview.visible {
self.preview_scroll.scroll_by(delta);
}
}
pub(super) fn page_preview(&mut self, pages: i32) {
if self.preview.visible {
self.preview_scroll.scroll_by(pages * PREVIEW_PAGE_ROWS);
}
}
pub(super) fn split_preview(&self, body: Rect) -> (Rect, Option<Rect>) {
if !self.preview.visible {
return (body, None);
}
let (direction, minimum, panel) = match self.preview.position {
crate::tui::preview::PreviewPosition::Right => (
Direction::Horizontal,
MIN_LIST_COLS,
Constraint::Percentage(self.preview.width_pct),
),
crate::tui::preview::PreviewPosition::Bottom => (
Direction::Vertical,
MIN_LIST_ROWS,
Constraint::Length(self.preview.height_rows),
),
};
let parts = Layout::default()
.direction(direction)
.constraints([
Constraint::Min(minimum),
Constraint::Length(PANEL_GUTTER),
panel,
])
.split(body);
(parts[0], Some(parts[2]))
}
pub(super) fn preview_log_path(&self) -> Option<PathBuf> {
if !self.preview.visible || self.config.example_mode {
return None;
}
let repo = self.selected_index().and_then(|i| self.service.get(i))?;
if repo.kind != RepoKind::Git {
return None;
}
Some(repo.path.clone())
}
pub(super) fn request_preview_log(&mut self) {
let Some(path) = self.preview_log_path() else {
self.preview_target = None;
return;
};
if self.preview_log.contains_key(&path)
|| self.preview_pending.contains(&path)
{
return;
}
if self.preview_target.as_deref() != Some(&path) {
self.preview_target = Some(path);
self.preview_target_at = Instant::now();
return;
}
if self.preview_target_at.elapsed() < PREVIEW_DEBOUNCE {
return;
}
self.fetch_logs(vec![path]);
}
pub(super) fn fetch_logs(&mut self, paths: Vec<PathBuf>) {
let wanted: Vec<PathBuf> = paths
.into_iter()
.filter(|path| !self.preview_log.contains_key(path))
.collect();
if wanted.is_empty() {
return;
}
for path in &wanted {
self.preview_pending.insert(path.clone());
}
preview_service::spawn_logs(
Arc::clone(&self.git_client),
wanted,
PREVIEW_LOG_LINES,
self.preview_tx.clone(),
);
}
pub(super) fn drain_preview(&mut self) {
while let Ok(log) = self.preview_rx.try_recv() {
self.preview_pending.remove(&log.path);
self.preview_log.insert(log.path, log.lines);
}
}
pub(super) fn preview_busy(&self) -> bool {
if !self.preview_pending.is_empty() {
return true;
}
self.preview_log_path()
.is_some_and(|path| !self.preview_log.contains_key(&path))
}
pub(super) fn invalidate_logs(&mut self, paths: &[PathBuf]) {
for path in paths {
self.preview_log.remove(path);
self.preview_pending.remove(path);
}
}
pub(super) fn render_preview(&self, frame: &mut Frame, area: Rect) {
let repo = self.selected_index().and_then(|i| self.service.get(i));
let log = repo
.map(|r| self.preview_log.get(&r.path).map(Vec::as_slice))
.unwrap_or(None);
let log_loading = self
.preview_log_path()
.is_some_and(|path| !self.preview_log.contains_key(&path));
crate::tui::preview::render(
frame,
area,
&self.skin,
crate::tui::preview::PreviewContext {
repo,
icons: &self.icons,
colors: &self.colors,
example_mode: self.config.example_mode,
log: log.unwrap_or(&[]),
log_loading,
code: repo.and_then(|r| self.stats.code.get(&r.path)),
git: repo.and_then(|r| self.stats.git.get(&r.path)),
now: Local::now().timestamp(),
scroll: &self.preview_scroll,
},
);
}
}