diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 416 |
1 files changed, 9 insertions, 407 deletions
diff --git a/src/main.rs b/src/main.rs index 0acbc59..0b322c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,14 @@ use anyhow::Result; use crossterm::{ cursor, - event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, MouseEvent}, + event::{self, DisableMouseCapture, EnableMouseCapture, Event}, queue, - style::{Attribute, Print}, + style::{self, Print}, terminal, }; use serde::{Deserialize, Serialize}; use std::{ - cmp::{min, Ordering}, + cmp::min, collections::HashMap, env, fs, io::{stdout, Write}, @@ -17,6 +17,9 @@ use std::{ }; use unicode_width::UnicodeWidthChar; +mod view; +use view::{Nav, Page, Search, View}; + mod epub; use epub::Chapter; @@ -75,7 +78,7 @@ fn wrap(text: &str, max_cols: usize) -> Vec<(usize, usize)> { lines } -fn get_line(lines: &Vec<(usize, usize)>, byte: usize) -> usize { +fn get_line(lines: &[(usize, usize)], byte: usize) -> usize { match lines.binary_search_by_key(&byte, |&(a, _)| a) { Ok(n) => n, Err(n) => n - 1, @@ -93,408 +96,7 @@ enum Direction { Prev, } -trait View { - fn render(&self, bk: &Bk) -> Vec<String>; - fn on_key(&self, bk: &mut Bk, kc: KeyCode); - fn on_mouse(&self, _: &mut Bk, _: MouseEvent) {} -} - -// TODO render something useful? -struct Mark; -impl View for Mark { - fn on_key(&self, bk: &mut Bk, kc: KeyCode) { - if let KeyCode::Char(c) = kc { - bk.mark(c) - } - bk.view = Some(&Page) - } - fn render(&self, bk: &Bk) -> Vec<String> { - Page::render(&Page, bk) - } -} - -struct Jump; -impl View for Jump { - fn on_key(&self, bk: &mut Bk, kc: KeyCode) { - if let KeyCode::Char(c) = kc { - if let Some(&pos) = bk.mark.get(&c) { - bk.jump(pos); - } - } - bk.view = Some(&Page); - } - fn render(&self, bk: &Bk) -> Vec<String> { - Page::render(&Page, bk) - } -} - -struct Metadata; -impl View for Metadata { - fn on_key(&self, bk: &mut Bk, _: KeyCode) { - bk.view = Some(&Page); - } - fn render(&self, bk: &Bk) -> Vec<String> { - let lines: Vec<usize> = bk.chapters.iter().map(|c| c.lines.len()).collect(); - let current = lines[..bk.chapter].iter().sum::<usize>() + bk.line; - let total = lines.iter().sum::<usize>(); - let progress = current as f32 / total as f32 * 100.0; - - let pages = lines[bk.chapter] / bk.rows; - let page = bk.line / bk.rows; - - let mut vec = vec![ - format!("chapter: {}/{}", page, pages), - format!("total: {:.0}%", progress), - String::new(), - ]; - vec.extend_from_slice(&bk.meta); - vec - } -} - -struct Help; -impl View for Help { - fn on_key(&self, bk: &mut Bk, _: KeyCode) { - bk.view = Some(&Page); - } - fn render(&self, _: &Bk) -> Vec<String> { - let text = r#" - Esc q Quit - Fn Help - Tab Table of Contents - i Progress and Metadata - -PageDown Right Space f l Page Down - PageUp Left b h Page Up - d Half Page Down - u Half Page Up - Down j Line Down - Up k Line Up - Home g Chapter Start - End G Chapter End - [ Previous Chapter - ] Next Chapter - - / Search Forward - ? Search Backward - n Repeat search forward - N Repeat search backward - mx Set mark x - 'x Jump to mark x - "#; - - text.lines().map(String::from).collect() - } -} - -struct Nav; -impl View for Nav { - fn on_key(&self, bk: &mut Bk, kc: KeyCode) { - match kc { - KeyCode::Esc - | KeyCode::Tab - | KeyCode::Left - | KeyCode::Char('h') - | KeyCode::Char('q') => { - bk.jump_reset(); - bk.view = Some(&Page); - } - KeyCode::Enter | KeyCode::Right | KeyCode::Char('l') => { - bk.line = 0; - bk.view = Some(&Page); - } - KeyCode::Down | KeyCode::Char('j') => { - if bk.chapter < bk.chapters.len() - 1 { - bk.chapter += 1; - if bk.chapter == bk.nav_top + bk.rows { - bk.nav_top += 1; - } - } - } - KeyCode::Up | KeyCode::Char('k') => { - if bk.chapter > 0 { - if bk.chapter == bk.nav_top { - bk.nav_top -= 1; - } - bk.chapter -= 1; - } - } - KeyCode::Home | KeyCode::Char('g') => { - bk.chapter = 0; - bk.nav_top = 0; - } - KeyCode::End | KeyCode::Char('G') => { - bk.chapter = bk.chapters.len() - 1; - bk.nav_top = bk.chapters.len().saturating_sub(bk.rows); - } - _ => (), - } - } - fn render(&self, bk: &Bk) -> Vec<String> { - let end = min(bk.nav_top + bk.rows, bk.chapters.len()); - - bk.chapters[bk.nav_top..end] - .iter() - .enumerate() - .map(|(i, chapter)| { - if bk.chapter == bk.nav_top + i { - format!( - "{}{}{}", - Attribute::Reverse, - chapter.title, - Attribute::Reset - ) - } else { - chapter.title.to_string() - } - }) - .collect() - } -} - -struct Page; -impl View for Page { - fn on_mouse(&self, bk: &mut Bk, e: MouseEvent) { - match e { - MouseEvent::Down(_, col, row, _) => { - let c = bk.chap(); - let line = bk.line + row as usize; - - if col < bk.pad() || line >= c.lines.len() { - return; - } - let (start, end) = c.lines[line]; - let line_col = (col - bk.pad()) as usize; - - let mut cols = 0; - let mut found = false; - let mut byte = start; - for (i, c) in c.text[start..end].char_indices() { - cols += c.width().unwrap(); - if cols > line_col { - byte += i; - found = true; - break; - } - } - - if !found { - return; - } - - let r = c.links.binary_search_by(|&(start, end, _)| { - if start > byte { - Ordering::Greater - } else if end <= byte { - Ordering::Less - } else { - Ordering::Equal - } - }); - - if let Ok(i) = r { - let url = &c.links[i].2; - let &(chapter, byte) = bk.links.get(url).unwrap(); - let line = get_line(&bk.chapters[chapter].lines, byte); - bk.jump((chapter, line)); - } - } - MouseEvent::ScrollDown(_, _, _) => bk.scroll_down(3), - MouseEvent::ScrollUp(_, _, _) => bk.scroll_up(3), - _ => (), - } - } - fn on_key(&self, bk: &mut Bk, kc: KeyCode) { - match kc { - KeyCode::Esc | KeyCode::Char('q') => bk.view = None, - KeyCode::Tab => { - bk.nav_top = bk.chapter.saturating_sub(bk.rows - 1); - bk.mark('\''); - bk.view = Some(&Nav); - } - KeyCode::F(_) => bk.view = Some(&Help), - KeyCode::Char('m') => bk.view = Some(&Mark), - KeyCode::Char('\'') => bk.view = Some(&Jump), - KeyCode::Char('i') => bk.view = Some(&Metadata), - KeyCode::Char('?') => bk.start_search(Direction::Prev), - KeyCode::Char('/') => bk.start_search(Direction::Next), - KeyCode::Char('N') => { - bk.search(SearchArgs { - dir: Direction::Prev, - skip: true, - }); - } - KeyCode::Char('n') => { - bk.search(SearchArgs { - dir: Direction::Next, - skip: true, - }); - } - KeyCode::End | KeyCode::Char('G') => { - bk.mark('\''); - bk.line = bk.chap().lines.len().saturating_sub(bk.rows); - } - KeyCode::Home | KeyCode::Char('g') => { - bk.mark('\''); - bk.line = 0; - } - KeyCode::Char('d') => bk.scroll_down(bk.rows / 2), - KeyCode::Char('u') => bk.scroll_up(bk.rows / 2), - KeyCode::Up | KeyCode::Char('k') => bk.scroll_up(3), - KeyCode::Left | KeyCode::PageUp | KeyCode::Char('b') | KeyCode::Char('h') => { - bk.scroll_up(bk.rows); - } - KeyCode::Down | KeyCode::Char('j') => bk.scroll_down(3), - KeyCode::Right - | KeyCode::PageDown - | KeyCode::Char('f') - | KeyCode::Char('l') - | KeyCode::Char(' ') => bk.scroll_down(bk.rows), - KeyCode::Char('[') => bk.prev_chapter(), - KeyCode::Char(']') => bk.next_chapter(), - _ => (), - } - } - fn render(&self, bk: &Bk) -> Vec<String> { - let c = bk.chap(); - let line_end = min(bk.line + bk.rows, c.lines.len()); - - let attrs = { - let text_start = c.lines[bk.line].0; - let text_end = c.lines[line_end - 1].1; - - let qlen = bk.query.len(); - let mut search = Vec::new(); - if qlen > 0 { - for (pos, _) in c.text[text_start..text_end].match_indices(&bk.query) { - search.push((text_start + pos, Attribute::Reverse)); - search.push((text_start + pos + qlen, Attribute::NoReverse)); - } - } - let mut search_iter = search.into_iter().peekable(); - - let mut merged = Vec::new(); - let attr_start = match c - .attrs - .binary_search_by_key(&text_start, |&(pos, _, _)| pos) - { - Ok(n) => n, - Err(n) => n - 1, - }; - let mut attrs_iter = c.attrs[attr_start..].iter(); - let (_, _, attr) = attrs_iter.next().unwrap(); - if attr.has(Attribute::Bold) { - merged.push((text_start, Attribute::Bold)); - } - if attr.has(Attribute::Italic) { - merged.push((text_start, Attribute::Italic)); - } - if attr.has(Attribute::Underlined) { - merged.push((text_start, Attribute::Underlined)); - } - let mut attrs_iter = attrs_iter - .map(|&(pos, a, _)| (pos, a)) - .take_while(|(pos, _)| pos <= &text_end) - .peekable(); - - // use itertools? - loop { - match (search_iter.peek(), attrs_iter.peek()) { - (None, None) => break, - (Some(_), None) => { - merged.extend(search_iter); - break; - } - (None, Some(_)) => { - merged.extend(attrs_iter); - break; - } - (Some(&s), Some(&a)) => { - if s.0 < a.0 { - merged.push(s); - search_iter.next(); - } else { - merged.push(a); - attrs_iter.next(); - } - } - } - } - - merged - }; - - let mut buf = Vec::new(); - let mut iter = attrs.into_iter().peekable(); - for &(mut start, end) in &c.lines[bk.line..line_end] { - let mut s = String::new(); - while let Some(&(pos, attr)) = iter.peek() { - if pos > end { - break; - } - s.push_str(&c.text[start..pos]); - s.push_str(&attr.to_string()); - start = pos; - iter.next(); - } - s.push_str(&c.text[start..end]); - buf.push(s); - } - buf - } -} - -struct Search; -impl View for Search { - fn on_key(&self, bk: &mut Bk, kc: KeyCode) { - match kc { - KeyCode::Esc => { - bk.jump_reset(); - bk.view = Some(&Page); - } - KeyCode::Enter => { - bk.view = Some(&Page); - } - KeyCode::Backspace => { - bk.query.pop(); - bk.jump_reset(); - bk.search(SearchArgs { - dir: bk.dir.clone(), - skip: false, - }); - } - KeyCode::Char(c) => { - bk.query.push(c); - let args = SearchArgs { - dir: bk.dir.clone(), - skip: false, - }; - if !bk.search(args) { - bk.jump_reset(); - } - } - _ => (), - } - } - fn render(&self, bk: &Bk) -> Vec<String> { - let mut buf = Page::render(&Page, bk); - if buf.len() == bk.rows { - buf.pop(); - } else { - for _ in buf.len()..bk.rows - 1 { - buf.push(String::new()); - } - } - let prefix = match bk.dir { - Direction::Next => '/', - Direction::Prev => '?', - }; - buf.push(format!("{}{}", prefix, bk.query)); - buf - } -} - -struct Bk<'a> { +pub struct Bk<'a> { chapters: Vec<epub::Chapter>, // position in the book chapter: usize, @@ -573,7 +175,7 @@ impl Bk<'_> { queue!( stdout, terminal::Clear(terminal::ClearType::All), - Print(Attribute::Reset) + Print(style::Attribute::Reset) )?; for (i, line) in view.render(self).iter().enumerate() { queue!(stdout, cursor::MoveTo(self.pad(), i as u16), Print(line))?; |