mirror of
https://github.com/itsjunetime/tdf.git
synced 2026-06-01 23:51:46 -04:00
Add formatter and format
This commit is contained in:
+126
-100
@@ -1,7 +1,17 @@
|
||||
use std::{io::stdout, rc::Rc};
|
||||
|
||||
use crossterm::{event::{Event, KeyCode, MouseEventKind}, execute, terminal::BeginSynchronizedUpdate};
|
||||
use ratatui::{layout::{Constraint, Flex, Layout, Rect}, style::{Color, Style}, text::Span, widgets::{Block, Borders, Padding}, Frame};
|
||||
use crossterm::{
|
||||
event::{Event, KeyCode, MouseEventKind},
|
||||
execute,
|
||||
terminal::BeginSynchronizedUpdate
|
||||
};
|
||||
use ratatui::{
|
||||
layout::{Constraint, Flex, Layout, Rect},
|
||||
style::{Color, Style},
|
||||
text::Span,
|
||||
widgets::{Block, Borders, Padding},
|
||||
Frame
|
||||
};
|
||||
use ratatui_image::{protocol::Protocol, Image};
|
||||
|
||||
use crate::{renderer::RenderError, skip::Skip};
|
||||
@@ -14,7 +24,7 @@ pub struct Tui {
|
||||
// we use `prev_msg` to, for example, restore the 'search results' message on the bottom after
|
||||
// jumping to a specific page
|
||||
prev_msg: Option<BottomMessage>,
|
||||
rendered: Vec<RenderedInfo>,
|
||||
rendered: Vec<RenderedInfo>
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@@ -61,7 +71,7 @@ impl Tui {
|
||||
prev_msg: None,
|
||||
bottom_msg: BottomMessage::Help,
|
||||
last_render: LastRender::default(),
|
||||
rendered: vec![],
|
||||
rendered: vec![]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,19 +103,12 @@ impl Tui {
|
||||
let top_layout = Layout::horizontal([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(page_nums_text.len() as u16)
|
||||
]).split(top_area);
|
||||
])
|
||||
.split(top_area);
|
||||
|
||||
let title = Span::styled(
|
||||
&self.name,
|
||||
Style::new()
|
||||
.fg(Color::Cyan)
|
||||
);
|
||||
let title = Span::styled(&self.name, Style::new().fg(Color::Cyan));
|
||||
|
||||
let page_nums = Span::styled(
|
||||
&page_nums_text,
|
||||
Style::new()
|
||||
.fg(Color::Cyan)
|
||||
);
|
||||
let page_nums = Span::styled(&page_nums_text, Style::new().fg(Color::Cyan));
|
||||
|
||||
frame.render_widget(top_block, main_area[0]);
|
||||
frame.render_widget(title, top_layout[0]);
|
||||
@@ -126,7 +129,8 @@ impl Tui {
|
||||
let rendered_str = if !self.rendered.is_empty() {
|
||||
format!(
|
||||
"Rendered: {}%",
|
||||
(self.rendered.iter().filter(|i| i.img.is_some()).count() * 100) / self.rendered.len()
|
||||
(self.rendered.iter().filter(|i| i.img.is_some()).count() * 100)
|
||||
/ self.rendered.len()
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
@@ -134,40 +138,45 @@ impl Tui {
|
||||
let bottom_layout = Layout::horizontal([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(rendered_str.len() as u16)
|
||||
]).split(bottom_area);
|
||||
])
|
||||
.split(bottom_area);
|
||||
|
||||
|
||||
let rendered_span = Span::styled(
|
||||
&rendered_str,
|
||||
Style::new()
|
||||
.fg(Color::Cyan)
|
||||
);
|
||||
let rendered_span = Span::styled(&rendered_str, Style::new().fg(Color::Cyan));
|
||||
frame.render_widget(rendered_span, bottom_layout[1]);
|
||||
|
||||
let (msg_str, color) = match self.bottom_msg {
|
||||
BottomMessage::Help => (
|
||||
"/: Search, g: Go To Page, n: Next Search Result, N: Previous Search Result".to_string(),
|
||||
"/: Search, g: Go To Page, n: Next Search Result, N: Previous Search Result"
|
||||
.to_string(),
|
||||
Color::Blue
|
||||
),
|
||||
BottomMessage::Error(ref e) => (
|
||||
format!("Couldn't render a page: {e}"),
|
||||
Color::Red
|
||||
),
|
||||
BottomMessage::Error(ref e) => (format!("Couldn't render a page: {e}"), Color::Red),
|
||||
BottomMessage::Input(ref input_state) => (
|
||||
match input_state {
|
||||
InputCommand::GoToPage(page) => format!("Go to: {page}"),
|
||||
InputCommand::Search(s) => format!("Search: {s}"),
|
||||
InputCommand::Search(s) => format!("Search: {s}")
|
||||
},
|
||||
Color::Blue
|
||||
),
|
||||
BottomMessage::SearchResults(ref term) => {
|
||||
let num_found = self.rendered.iter().filter_map(|r| r.num_results).sum::<usize>();
|
||||
let num_searched = self.rendered.iter().filter(|r| r.num_results.is_some()).count() * 100;
|
||||
let num_found = self
|
||||
.rendered
|
||||
.iter()
|
||||
.filter_map(|r| r.num_results)
|
||||
.sum::<usize>();
|
||||
let num_searched = self
|
||||
.rendered
|
||||
.iter()
|
||||
.filter(|r| r.num_results.is_some())
|
||||
.count() * 100;
|
||||
(
|
||||
format!("Results for '{term}': {num_found} (searched: {}%)", num_searched / self.rendered.len()),
|
||||
format!(
|
||||
"Results for '{term}': {num_found} (searched: {}%)",
|
||||
num_searched / self.rendered.len()
|
||||
),
|
||||
Color::Blue
|
||||
)
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let span = Span::styled(msg_str, Style::new().fg(color));
|
||||
@@ -186,28 +195,22 @@ impl Tui {
|
||||
// here we calculate how many pages can fit in the available area.
|
||||
let mut test_area_w = img_area.width;
|
||||
// go through our pages, starting at the first one we want to view
|
||||
let page_widths = self.rendered[self.page..].iter()
|
||||
let page_widths = self.rendered[self.page..]
|
||||
.iter()
|
||||
// and get their indices (I know it's offset, we fix it down below when we actually
|
||||
// render each page)
|
||||
.enumerate()
|
||||
// and only take as many as are ready to be rendered
|
||||
.take_while(|(_, page)| page.img.is_some())
|
||||
// and map it to their width (in cells on the terminal, not pixels)
|
||||
.flat_map(|(idx, page)|
|
||||
page.img.as_ref().map(|img| (
|
||||
idx,
|
||||
img.rect().width,
|
||||
))
|
||||
)
|
||||
.flat_map(|(idx, page)| page.img.as_ref().map(|img| (idx, img.rect().width)))
|
||||
// and then take them as long as they won't overflow the available area.
|
||||
.take_while(|(_, width)| {
|
||||
match test_area_w.checked_sub(*width) {
|
||||
Some(new_val) => {
|
||||
test_area_w = new_val;
|
||||
true
|
||||
},
|
||||
None => false
|
||||
.take_while(|(_, width)| match test_area_w.checked_sub(*width) {
|
||||
Some(new_val) => {
|
||||
test_area_w = new_val;
|
||||
true
|
||||
}
|
||||
None => false
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -218,10 +221,7 @@ impl Tui {
|
||||
execute!(stdout(), BeginSynchronizedUpdate).unwrap();
|
||||
*end_update = true;
|
||||
|
||||
let total_width = page_widths
|
||||
.iter()
|
||||
.map(|(_, w)| w)
|
||||
.sum::<u16>();
|
||||
let total_width = page_widths.iter().map(|(_, w)| w).sum::<u16>();
|
||||
|
||||
self.last_render.pages_shown = page_widths.len();
|
||||
|
||||
@@ -236,7 +236,10 @@ impl Tui {
|
||||
// reference + an immutable reference (and also we need to potentially temporarily
|
||||
// remove it from the array of rendered pages to replace it with a text-rendered
|
||||
// image)
|
||||
self.render_single_page(frame, page_idx + self.page, Rect { width, ..img_area });
|
||||
self.render_single_page(frame, page_idx + self.page, Rect {
|
||||
width,
|
||||
..img_area
|
||||
});
|
||||
img_area.x += width;
|
||||
}
|
||||
|
||||
@@ -256,10 +259,9 @@ impl Tui {
|
||||
|
||||
fn render_loading_in(frame: &mut Frame<'_>, area: Rect) {
|
||||
let loading_str = "Loading...";
|
||||
let inner_space = Layout::horizontal([
|
||||
Constraint::Length(loading_str.len() as u16),
|
||||
]).flex(Flex::Center)
|
||||
.split(area);
|
||||
let inner_space = Layout::horizontal([Constraint::Length(loading_str.len() as u16)])
|
||||
.flex(Flex::Center)
|
||||
.split(area);
|
||||
|
||||
let loading_span = Span::styled(loading_str, Style::new().fg(Color::Cyan));
|
||||
|
||||
@@ -275,7 +277,7 @@ impl Tui {
|
||||
let old = self.page;
|
||||
match change {
|
||||
PageChange::Next => self.set_page((self.page + diff).min(self.rendered.len() - 1)),
|
||||
PageChange::Prev => self.set_page(self.page.saturating_sub(diff)),
|
||||
PageChange::Prev => self.set_page(self.page.saturating_sub(diff))
|
||||
}
|
||||
|
||||
match self.page as isize - old as isize {
|
||||
@@ -312,7 +314,10 @@ impl Tui {
|
||||
// We always just set this here because we handle reloading in the `set_n_pages` function.
|
||||
// If the document was reloaded, then It'll have the `set_n_pages` called to set the new
|
||||
// number of pages, so the vec will already be cleared
|
||||
self.rendered[page_num] = RenderedInfo { img: Some(img), num_results: Some(num_results) };
|
||||
self.rendered[page_num] = RenderedInfo {
|
||||
img: Some(img),
|
||||
num_results: Some(num_results)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn got_num_results_on_page(&mut self, page_num: usize, num_results: usize) {
|
||||
@@ -320,7 +325,11 @@ impl Tui {
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, ev: Event) -> Option<InputAction> {
|
||||
fn jump_to_page(page: &mut usize, rect: &mut Rect, new_page: Option<usize>) -> Option<InputAction> {
|
||||
fn jump_to_page(
|
||||
page: &mut usize,
|
||||
rect: &mut Rect,
|
||||
new_page: Option<usize>
|
||||
) -> Option<InputAction> {
|
||||
new_page.map(|new_page| {
|
||||
*page = new_page;
|
||||
// Make sure we re-render
|
||||
@@ -332,62 +341,73 @@ impl Tui {
|
||||
match ev {
|
||||
Event::Key(key) => {
|
||||
match key.code {
|
||||
KeyCode::Char(c) if let BottomMessage::Input(InputCommand::Search(ref mut term)) = self.bottom_msg => {
|
||||
KeyCode::Char(c)
|
||||
if let BottomMessage::Input(InputCommand::Search(ref mut term)) =
|
||||
self.bottom_msg =>
|
||||
{
|
||||
term.push(c);
|
||||
Some(InputAction::Redraw)
|
||||
},
|
||||
KeyCode::Char(c) if let BottomMessage::Input(InputCommand::GoToPage(ref mut page)) = self.bottom_msg => {
|
||||
c.to_digit(10)
|
||||
.map(|input_num| {
|
||||
*page = (*page * 10) + input_num as usize;
|
||||
InputAction::Redraw
|
||||
})
|
||||
},
|
||||
KeyCode::Right | KeyCode::Char('l') => self.change_page(PageChange::Next, ChangeAmount::Single),
|
||||
KeyCode::Down | KeyCode::Char('j') => self.change_page(PageChange::Next, ChangeAmount::WholeScreen),
|
||||
KeyCode::Left | KeyCode::Char('h') => self.change_page(PageChange::Prev, ChangeAmount::Single),
|
||||
KeyCode::Up | KeyCode::Char('k') => self.change_page(PageChange::Prev, ChangeAmount::WholeScreen),
|
||||
}
|
||||
KeyCode::Char(c)
|
||||
if let BottomMessage::Input(InputCommand::GoToPage(ref mut page)) =
|
||||
self.bottom_msg =>
|
||||
c.to_digit(10).map(|input_num| {
|
||||
*page = (*page * 10) + input_num as usize;
|
||||
InputAction::Redraw
|
||||
}),
|
||||
KeyCode::Right | KeyCode::Char('l') =>
|
||||
self.change_page(PageChange::Next, ChangeAmount::Single),
|
||||
KeyCode::Down | KeyCode::Char('j') =>
|
||||
self.change_page(PageChange::Next, ChangeAmount::WholeScreen),
|
||||
KeyCode::Left | KeyCode::Char('h') =>
|
||||
self.change_page(PageChange::Prev, ChangeAmount::Single),
|
||||
KeyCode::Up | KeyCode::Char('k') =>
|
||||
self.change_page(PageChange::Prev, ChangeAmount::WholeScreen),
|
||||
KeyCode::Esc => match self.bottom_msg {
|
||||
BottomMessage::Input(_) => {
|
||||
self.set_bottom_msg(None);
|
||||
Some(InputAction::Redraw)
|
||||
},
|
||||
}
|
||||
_ => Some(InputAction::QuitApp)
|
||||
},
|
||||
KeyCode::Char('q') => Some(InputAction::QuitApp),
|
||||
KeyCode::Char('g') => {
|
||||
self.set_bottom_msg(Some(BottomMessage::Input(InputCommand::GoToPage(0))));
|
||||
Some(InputAction::Redraw)
|
||||
},
|
||||
}
|
||||
KeyCode::Char('/') => {
|
||||
self.set_bottom_msg(Some(BottomMessage::Input(InputCommand::Search(String::new()))));
|
||||
self.set_bottom_msg(Some(BottomMessage::Input(InputCommand::Search(
|
||||
String::new()
|
||||
))));
|
||||
Some(InputAction::Redraw)
|
||||
},
|
||||
}
|
||||
KeyCode::Char('n') if self.page < self.rendered.len() - 1 => {
|
||||
// TODO: If we can't find one, then maybe like block until we've verified
|
||||
// all the pages have been checked?
|
||||
let next_page = self.rendered[(self.page + 1)..]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(idx, p)| p.num_results
|
||||
.is_some_and(|num| num > 0)
|
||||
.then_some(self.page + 1 + idx)
|
||||
);
|
||||
.find_map(|(idx, p)| {
|
||||
p.num_results
|
||||
.is_some_and(|num| num > 0)
|
||||
.then_some(self.page + 1 + idx)
|
||||
});
|
||||
|
||||
jump_to_page(&mut self.page, &mut self.last_render.rect, next_page)
|
||||
},
|
||||
}
|
||||
KeyCode::Char('N') if self.page > 0 => {
|
||||
let prev_page = self.rendered[..(self.page)]
|
||||
.iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.find_map(|(idx, p)| p.num_results
|
||||
.is_some_and(|num| num > 0)
|
||||
.then_some(self.page - (idx + 1))
|
||||
);
|
||||
.find_map(|(idx, p)| {
|
||||
p.num_results
|
||||
.is_some_and(|num| num > 0)
|
||||
.then_some(self.page - (idx + 1))
|
||||
});
|
||||
|
||||
jump_to_page(&mut self.page, &mut self.last_render.rect, prev_page)
|
||||
},
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
let BottomMessage::Input(_) = self.bottom_msg else {
|
||||
return None;
|
||||
@@ -409,14 +429,16 @@ impl Tui {
|
||||
self.set_page(page);
|
||||
InputAction::JumpingToPage(page)
|
||||
})
|
||||
},
|
||||
}
|
||||
InputCommand::Search(term) => {
|
||||
let term = term.clone();
|
||||
|
||||
// We only want to show search results if there would actually be
|
||||
// data to show
|
||||
if !term.is_empty() {
|
||||
self.set_bottom_msg(Some(BottomMessage::SearchResults(term.clone())));
|
||||
self.set_bottom_msg(Some(BottomMessage::SearchResults(
|
||||
term.clone()
|
||||
)));
|
||||
} else {
|
||||
// else, if it's not empty, we just want to reset the bottom
|
||||
// area to show the default data; we don't want it to like show
|
||||
@@ -434,21 +456,25 @@ impl Tui {
|
||||
Some(InputAction::Search(term))
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
},
|
||||
Event::Mouse(mouse) => match mouse.kind {
|
||||
MouseEventKind::ScrollRight => self.change_page(PageChange::Next, ChangeAmount::Single),
|
||||
MouseEventKind::ScrollDown => self.change_page(PageChange::Next, ChangeAmount::WholeScreen),
|
||||
MouseEventKind::ScrollLeft => self.change_page(PageChange::Prev, ChangeAmount::Single),
|
||||
MouseEventKind::ScrollUp => self.change_page(PageChange::Prev, ChangeAmount::WholeScreen),
|
||||
_ => None,
|
||||
}
|
||||
Event::Mouse(mouse) => match mouse.kind {
|
||||
MouseEventKind::ScrollRight =>
|
||||
self.change_page(PageChange::Next, ChangeAmount::Single),
|
||||
MouseEventKind::ScrollDown =>
|
||||
self.change_page(PageChange::Next, ChangeAmount::WholeScreen),
|
||||
MouseEventKind::ScrollLeft =>
|
||||
self.change_page(PageChange::Prev, ChangeAmount::Single),
|
||||
MouseEventKind::ScrollUp =>
|
||||
self.change_page(PageChange::Prev, ChangeAmount::WholeScreen),
|
||||
_ => None
|
||||
},
|
||||
// One of these options is Event::Resize, and we don't care about that because
|
||||
// we always check, regardless, if the available area for the images has
|
||||
// changed.
|
||||
_ => None,
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,12 +500,12 @@ impl Tui {
|
||||
Some(mut msg) => {
|
||||
std::mem::swap(&mut self.bottom_msg, &mut msg);
|
||||
self.prev_msg = Some(msg);
|
||||
},
|
||||
}
|
||||
None => {
|
||||
let mut new_bottom = self.prev_msg.take().unwrap_or_default();
|
||||
std::mem::swap(&mut self.bottom_msg, &mut new_bottom);
|
||||
self.prev_msg = Some(new_bottom);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user