use crate::search::SearchState;
use crate::{app::State, error::Error};
use aho_corasick::AhoCorasick;
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
widgets::{Block, BorderType, Borders, Paragraph},
Frame,
};
use tracing::{debug, trace};
use tui_input::Input;
pub fn pager(
f: &mut Frame,
state: &State,
git_log: &[String],
commit: Option<&[String]>,
vertical_size: &mut u16,
hilights: Option<String>,
) -> Result<(), Error> {
trace!("Rendering screen");
let hilight_style = Style::new()
.fg(ratatui::style::Color::Black)
.bg(ratatui::style::Color::Gray);
let commit_len = commit.map_or(0, |commit| commit.iter().len() + 1);
let commit = commit.map(|commit| commit.join("\n"));
let layout = match state {
State::Search { .. } => vec![
#[allow(clippy::cast_possible_truncation)]
Constraint::Max(std::cmp::min(7, commit_len as u16)),
Constraint::Min(8),
Constraint::Max(3),
],
State::Pager => vec![
#[allow(clippy::cast_possible_truncation)]
Constraint::Max(std::cmp::min(7, commit_len as u16)),
Constraint::Min(8),
],
State::Exit => unreachable!(),
};
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(layout)
.margin(1)
.split(f.area());
let commit_paragraph = Paragraph::new(commit.unwrap_or_default()).block(
Block::default()
.borders(Borders::BOTTOM)
.border_type(BorderType::Double),
);
f.render_widget(commit_paragraph, chunks[0]);
let paragraph = if let Some(hilights) = hilights {
let hilighted_log: Vec<_> = git_log
.iter()
.map(|line| {
let ac = AhoCorasick::builder()
.ascii_case_insensitive(true)
.build([hilights.as_str()])?;
let matches = ac.find_iter(line);
let hilights: Vec<_> = matches.map(|m| (m.start(), m.end())).collect();
debug!("Got hilights at: {hilights:?}");
let line_hilighted = hilights
.windows(2)
.map(<&[(usize, usize); 2]>::try_from)
.collect::<Result<Vec<_>, _>>()?
.iter()
.fold(
vec![Span::from(
line[0..hilights.first().map_or(line.chars().count(), |m| m.0)]
.to_string(),
)],
|mut coll, [(start, end), (next_start, _next_end)]| {
let hilight = Span::styled(&line[*start..*end], hilight_style);
let text_between_hilights = Span::from(&line[*end..*next_start]);
coll.append(&mut vec![hilight, text_between_hilights]);
coll
},
);
let line_hilighted = if let Some((last_start, last_end)) = hilights.last() {
let hilight = Span::styled(&line[*last_start..*last_end], hilight_style);
let rest_of_line = Span::from(&line[*last_end..]);
Line::from(
vec![line_hilighted, vec![hilight], vec![rest_of_line]]
.into_iter()
.flatten()
.collect::<Vec<_>>(),
)
} else {
Line::from(line_hilighted)
};
Ok::<Line, Error>(line_hilighted)
})
.collect::<Result<Vec<_>, _>>()?;
Paragraph::new(hilighted_log)
} else {
Paragraph::new(git_log.join("\n"))
};
f.render_widget(paragraph, chunks[1]);
*vertical_size = chunks[1].height;
match state {
State::Search(SearchState::GetInput { term }) => {
draw_search_box(f, chunks[2], term);
}
State::Search(SearchState::Searching {
term,
position: _position,
}) => {
draw_search_box(f, chunks[2], term);
}
State::Pager => (),
State::Exit => unreachable!(),
}
Ok(())
}
fn draw_search_box(f: &mut Frame, area: Rect, input: &Input) {
let search_box =
Paragraph::new(input.value()).block(Block::default().borders(Borders::ALL).title("Search"));
f.render_widget(search_box, area);
}