diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 85 | ||||
-rw-r--r-- | src/view.rs | 219 |
2 files changed, 160 insertions, 144 deletions
diff --git a/src/main.rs b/src/main.rs index d330bf8..f2ba392 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,13 +12,12 @@ use std::{ collections::HashMap, env, fs, io::{stdout, Write}, - iter, process::exit, }; use unicode_width::UnicodeWidthChar; mod view; -use view::{Page, Search, Toc, View}; +use view::{Page, Toc, View}; mod epub; use epub::Chapter; @@ -133,7 +132,7 @@ impl Bk<'_> { .title .chars() .take(width - 1) - .chain(iter::once('…')) + .chain(std::iter::once('…')) .collect(); } } @@ -160,9 +159,6 @@ impl Bk<'_> { bk } - fn pad(&self) -> u16 { - self.cols.saturating_sub(self.max_width) / 2 - } fn run(&mut self) -> crossterm::Result<()> { let mut stdout = stdout(); queue!( @@ -225,8 +221,8 @@ impl Bk<'_> { )?; terminal::disable_raw_mode() } - fn mark(&mut self, c: char) { - self.mark.insert(c, (self.chapter, self.line)); + fn chap(&self) -> &Chapter { + &self.chapters[self.chapter] } fn jump(&mut self, (c, l): (usize, usize)) { self.mark('\''); @@ -238,74 +234,11 @@ impl Bk<'_> { self.chapter = c; self.line = l; } - fn chap(&self) -> &Chapter { - &self.chapters[self.chapter] - } - fn next_chapter(&mut self) { - if self.chapter < self.chapters.len() - 1 { - self.chapter += 1; - self.line = 0; - } - } - fn prev_chapter(&mut self) { - if self.chapter > 0 { - self.chapter -= 1; - self.line = 0; - } - } - fn scroll_down(&mut self, n: usize) { - if self.line + self.rows < self.chap().lines.len() { - self.line += n; - } else { - self.next_chapter(); - } - } - fn scroll_up(&mut self, n: usize) { - if self.line > 0 { - self.line = self.line.saturating_sub(n); - } else if self.chapter > 0 { - self.chapter -= 1; - self.line = self.chap().lines.len().saturating_sub(self.rows); - } - } - fn start_search(&mut self, dir: Direction) { - self.mark('\''); - self.query.clear(); - self.dir = dir; - self.view = &Search; + fn mark(&mut self, c: char) { + self.mark.insert(c, (self.chapter, self.line)); } - fn search(&mut self, args: SearchArgs) -> bool { - let (start, end) = self.chap().lines[self.line]; - match args.dir { - Direction::Next => { - let byte = if args.skip { end } else { start }; - let head = (self.chapter, byte); - let tail = (self.chapter + 1..self.chapters.len() - 1).map(|n| (n, 0)); - for (c, byte) in iter::once(head).chain(tail) { - if let Some(index) = self.chapters[c].text[byte..].find(&self.query) { - self.line = get_line(&self.chapters[c].lines, index + byte); - self.chapter = c; - return true; - } - } - false - } - Direction::Prev => { - let byte = if args.skip { start } else { end }; - let head = (self.chapter, byte); - let tail = (0..self.chapter) - .rev() - .map(|c| (c, self.chapters[c].text.len())); - for (c, byte) in iter::once(head).chain(tail) { - if let Some(index) = self.chapters[c].text[..byte].rfind(&self.query) { - self.line = get_line(&self.chapters[c].lines, index); - self.chapter = c; - return true; - } - } - false - } - } + fn pad(&self) -> u16 { + self.cols.saturating_sub(self.max_width) / 2 } } @@ -377,7 +310,7 @@ fn init() -> Result<State> { } let (path, chapter, byte) = match (&save, &path) { - (Err(_), None) => return Err(anyhow::anyhow!("no path arg and no or invalid save file")), + (Err(_), None) => return Err(anyhow::anyhow!("no path arg and no valid save file")), (Err(_), Some(p)) => (p, 0, 0), (Ok(save), None) => { let &(chapter, byte) = save.files.get(&save.last).unwrap(); diff --git a/src/view.rs b/src/view.rs index 6d4af33..2028fda 100644 --- a/src/view.rs +++ b/src/view.rs @@ -8,7 +8,7 @@ use crossterm::{ use std::cmp::{min, Ordering}; use unicode_width::UnicodeWidthChar; -use crate::{get_line, Bk, Direction, SearchArgs}; +use crate::{Bk, Direction, SearchArgs}; pub trait View { fn render(&self, bk: &Bk) -> Vec<String>; @@ -148,8 +148,8 @@ impl View for Toc { bk.view = &Page; } Enter | Right | Char('l') => { - bk.cursor = 0; bk.line = 0; + bk.cursor = 0; bk.view = &Page; } Down | Char('j') => self.next(bk, 1), @@ -182,54 +182,90 @@ impl View for Toc { } pub struct Page; -impl View for Page { - fn on_mouse(&self, bk: &mut Bk, e: MouseEvent) { - match e.kind { - MouseEventKind::Down(_) => { - let c = bk.chap(); - let line = bk.line + e.row as usize; - - if e.column < bk.pad() || line >= c.lines.len() { - return; - } - let (start, end) = c.lines[line]; - let line_col = (e.column - bk.pad()) as usize; +impl Page { + fn next_chapter(&self, bk: &mut Bk) { + if bk.chapter < bk.chapters.len() - 1 { + bk.chapter += 1; + bk.line = 0; + } + } + fn prev_chapter(&self, bk: &mut Bk) { + if bk.chapter > 0 { + bk.chapter -= 1; + bk.line = 0; + } + } + fn scroll_down(&self, bk: &mut Bk, n: usize) { + if bk.line + bk.rows < bk.chap().lines.len() { + bk.line += n; + } else { + self.next_chapter(bk); + } + } + fn scroll_up(&self, bk: &mut Bk, n: usize) { + if bk.line > 0 { + bk.line = bk.line.saturating_sub(n); + } else if bk.chapter > 0 { + bk.chapter -= 1; + bk.line = bk.chap().lines.len().saturating_sub(bk.rows); + } + } + fn click(&self, bk: &mut Bk, e: MouseEvent) { + let c = bk.chap(); + let line = bk.line + e.row 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 e.column < bk.pad() || line >= c.lines.len() { + return; + } + let (start, end) = c.lines[line]; + let line_col = (e.column - bk.pad()) as usize; - if !found { - return; - } + 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; + } + } - let r = c.links.binary_search_by(|&(start, end, _)| { - if start > byte { - Ordering::Greater - } else if end <= byte { - Ordering::Less - } else { - Ordering::Equal - } - }); + if !found { + return; + } - 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)); - } + let r = c.links.binary_search_by(|&(start, end, _)| { + if start > byte { + Ordering::Greater + } else if end <= byte { + Ordering::Less + } else { + Ordering::Equal } - MouseEventKind::ScrollDown => bk.scroll_down(3), - MouseEventKind::ScrollUp => bk.scroll_up(3), + }); + + if let Ok(i) = r { + let url = &c.links[i].2; + let &(chapter, byte) = bk.links.get(url).unwrap(); + let line = super::get_line(&bk.chapters[chapter].lines, byte); + bk.jump((chapter, line)); + } + } + fn start_search(&self, bk: &mut Bk, dir: Direction) { + bk.mark('\''); + bk.query.clear(); + bk.dir = dir; + bk.view = &Search; + } +} +impl View for Page { + fn on_mouse(&self, bk: &mut Bk, e: MouseEvent) { + match e.kind { + MouseEventKind::Down(_) => self.click(bk, e), + MouseEventKind::ScrollDown => self.scroll_down(bk, 3), + MouseEventKind::ScrollUp => self.scroll_up(bk, 3), _ => (), } } @@ -245,19 +281,27 @@ impl View for Page { Char('m') => bk.view = &Mark, Char('\'') => bk.view = &Jump, Char('i') => bk.view = &Metadata, - Char('?') => bk.start_search(Direction::Prev), - Char('/') => bk.start_search(Direction::Next), + Char('?') => self.start_search(bk, Direction::Prev), + Char('/') => self.start_search(bk, Direction::Next), Char('N') => { - bk.search(SearchArgs { - dir: Direction::Prev, - skip: true, - }); + Search::search( + &Search, + bk, + SearchArgs { + dir: Direction::Prev, + skip: true, + }, + ); } Char('n') => { - bk.search(SearchArgs { - dir: Direction::Next, - skip: true, - }); + Search::search( + &Search, + bk, + SearchArgs { + dir: Direction::Next, + skip: true, + }, + ); } End | Char('G') => { bk.mark('\''); @@ -267,16 +311,16 @@ impl View for Page { bk.mark('\''); bk.line = 0; } - Char('d') => bk.scroll_down(bk.rows / 2), - Char('u') => bk.scroll_up(bk.rows / 2), - Up | Char('k') => bk.scroll_up(3), + Char('d') => self.scroll_down(bk, bk.rows / 2), + Char('u') => self.scroll_up(bk, bk.rows / 2), + Up | Char('k') => self.scroll_up(bk, 3), Left | PageUp | Char('b') | Char('h') => { - bk.scroll_up(bk.rows); + self.scroll_up(bk, bk.rows); } - Down | Char('j') => bk.scroll_down(3), - Right | PageDown | Char('f') | Char('l') | Char(' ') => bk.scroll_down(bk.rows), - Char('[') => bk.prev_chapter(), - Char(']') => bk.next_chapter(), + Down | Char('j') => self.scroll_down(bk, 3), + Right | PageDown | Char('f') | Char('l') | Char(' ') => self.scroll_down(bk, bk.rows), + Char('[') => self.prev_chapter(bk), + Char(']') => self.next_chapter(bk), _ => (), } } @@ -374,11 +418,47 @@ impl View for Page { } pub struct Search; +impl Search { + fn search(&self, bk: &mut Bk, args: SearchArgs) -> bool { + let (start, end) = bk.chap().lines[bk.line]; + match args.dir { + Direction::Next => { + let byte = if args.skip { end } else { start }; + let head = (bk.chapter, byte); + let tail = (bk.chapter + 1..bk.chapters.len() - 1).map(|n| (n, 0)); + for (c, byte) in std::iter::once(head).chain(tail) { + if let Some(index) = bk.chapters[c].text[byte..].find(&bk.query) { + bk.line = super::get_line(&bk.chapters[c].lines, index + byte); + bk.chapter = c; + return true; + } + } + false + } + Direction::Prev => { + let byte = if args.skip { start } else { end }; + let head = (bk.chapter, byte); + let tail = (0..bk.chapter) + .rev() + .map(|c| (c, bk.chapters[c].text.len())); + for (c, byte) in std::iter::once(head).chain(tail) { + if let Some(index) = bk.chapters[c].text[..byte].rfind(&bk.query) { + bk.line = super::get_line(&bk.chapters[c].lines, index); + bk.chapter = c; + return true; + } + } + false + } + } + } +} impl View for Search { fn on_key(&self, bk: &mut Bk, kc: KeyCode) { match kc { Esc => { bk.jump_reset(); + bk.query.clear(); bk.view = &Page; } Enter => { @@ -387,10 +467,13 @@ impl View for Search { Backspace => { bk.query.pop(); bk.jump_reset(); - bk.search(SearchArgs { - dir: bk.dir.clone(), - skip: false, - }); + self.search( + bk, + SearchArgs { + dir: bk.dir.clone(), + skip: false, + }, + ); } Char(c) => { bk.query.push(c); @@ -398,7 +481,7 @@ impl View for Search { dir: bk.dir.clone(), skip: false, }; - if !bk.search(args) { + if self.search(bk, args) { bk.jump_reset(); } } |