diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | src/main.rs | 67 | ||||
-rw-r--r-- | src/view.rs | 115 |
3 files changed, 83 insertions, 101 deletions
@@ -1,5 +1,5 @@ # bk -bk is a WIP terminal EPUB reader, written in Rust. +bk is a terminal EPUB reader, written in Rust. # Features - Cross platform - Linux, macOS and Windows support diff --git a/src/main.rs b/src/main.rs index fefce50..d3e978c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use std::{ collections::HashMap, env, fs, io::{self, Write}, + iter, process::exit, }; use unicode_width::UnicodeWidthChar; @@ -19,7 +20,6 @@ mod view; use view::{Page, Toc, View}; mod epub; -use epub::Chapter; fn wrap(text: &str, max_cols: usize) -> Vec<(usize, usize)> { let mut lines = Vec::new(); @@ -76,13 +76,6 @@ fn wrap(text: &str, max_cols: usize) -> Vec<(usize, usize)> { lines } -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, - } -} - struct SearchArgs { dir: Direction, skip: bool, @@ -139,7 +132,7 @@ impl Bk<'_> { let mut bk = Bk { quit: false, chapters, - chapter: args.chapter, + chapter: 0, line: 0, mark: HashMap::new(), links: epub.links, @@ -153,7 +146,7 @@ impl Bk<'_> { query: String::new(), }; - bk.line = get_line(&bk.chap().lines, args.byte); + bk.jump_byte(args.chapter, args.byte); bk.mark('\''); bk @@ -220,14 +213,21 @@ impl Bk<'_> { )?; terminal::disable_raw_mode() } - fn chap(&self) -> &Chapter { - &self.chapters[self.chapter] - } fn jump(&mut self, (c, l): (usize, usize)) { self.mark('\''); self.chapter = c; self.line = l; } + fn jump_byte(&mut self, c: usize, byte: usize) { + self.chapter = c; + self.line = match self.chapters[c] + .lines + .binary_search_by_key(&byte, |&(a, _)| a) + { + Ok(n) => n, + Err(n) => n - 1, + } + } fn jump_reset(&mut self) { let &(c, l) = self.mark.get(&'\'').unwrap(); self.chapter = c; @@ -239,6 +239,37 @@ impl Bk<'_> { fn pad(&self) -> u16 { self.cols.saturating_sub(self.max_width) / 2 } + fn search(&mut self, args: SearchArgs) -> bool { + let (start, end) = self.chapters[self.chapter].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.jump_byte(c, index + byte); + 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.jump_byte(c, index); + return true; + } + } + false + } + } + } } #[derive(argh::FromArgs)] @@ -295,10 +326,10 @@ fn init() -> Result<State, Box<dyn std::error::Error>> { }); let args: Args = argh::from_env(); - let mut path = args.path; - if let Some(p) = path { - path = Some(fs::canonicalize(p)?.to_str().unwrap().to_string()); - } + let path = match args.path { + Some(p) => Some(fs::canonicalize(p)?.to_str().unwrap().to_string()), + None => None, + }; let (path, save, chapter, byte) = match (save, path) { (Err(e), None) => return Err(Box::new(e)), @@ -350,7 +381,7 @@ fn main() { exit(1); }); - let byte = bk.chap().lines[bk.line].0; + let byte = bk.chapters[bk.chapter].lines[bk.line].0; state .save .files diff --git a/src/view.rs b/src/view.rs index 4a5cb73..fa50532 100644 --- a/src/view.rs +++ b/src/view.rs @@ -5,10 +5,7 @@ use crossterm::{ }, style::Attribute, }; -use std::{ - cmp::{min, Ordering}, - iter, -}; +use std::cmp::{min, Ordering}; use unicode_width::UnicodeWidthChar; use crate::{Bk, Direction, SearchArgs}; @@ -199,7 +196,7 @@ impl Page { } } fn scroll_down(&self, bk: &mut Bk, n: usize) { - if bk.line + bk.rows < bk.chap().lines.len() { + if bk.line + bk.rows < bk.chapters[bk.chapter].lines.len() { bk.line += n; } else { self.next_chapter(bk); @@ -210,11 +207,11 @@ impl Page { 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); + bk.line = bk.chapters[bk.chapter].lines.len().saturating_sub(bk.rows); } } fn click(&self, bk: &mut Bk, e: MouseEvent) { - let c = bk.chap(); + let c = &bk.chapters[bk.chapter]; let line = bk.line + e.row as usize; if e.column < bk.pad() || line >= c.lines.len() { @@ -251,9 +248,9 @@ impl Page { 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)); + let &(c, byte) = bk.links.get(url).unwrap(); + bk.mark('\''); + bk.jump_byte(c, byte); } } fn start_search(&self, bk: &mut Bk, dir: Direction) { @@ -287,28 +284,20 @@ impl View for Page { Char('?') => self.start_search(bk, Direction::Prev), Char('/') => self.start_search(bk, Direction::Next), Char('N') => { - Search::search( - &Search, - bk, - SearchArgs { - dir: Direction::Prev, - skip: true, - }, - ); + bk.search(SearchArgs { + dir: Direction::Prev, + skip: true, + }); } Char('n') => { - Search::search( - &Search, - bk, - SearchArgs { - dir: Direction::Next, - skip: true, - }, - ); + bk.search(SearchArgs { + dir: Direction::Next, + skip: true, + }); } End | Char('G') => { bk.mark('\''); - bk.line = bk.chap().lines.len().saturating_sub(bk.rows); + bk.line = bk.chapters[bk.chapter].lines.len().saturating_sub(bk.rows); } Home | Char('g') => { bk.mark('\''); @@ -329,14 +318,24 @@ impl View for Page { } fn on_resize(&self, bk: &mut Bk) { // lazy - bk.line = min(bk.line, bk.chap().lines.len() - 1); + bk.line = min(bk.line, bk.chapters[bk.chapter].lines.len() - 1); } fn render(&self, bk: &Bk) -> Vec<String> { - let c = bk.chap(); + let c = &bk.chapters[bk.chapter]; let last_line = min(bk.line + bk.rows, c.lines.len()); let text_start = c.lines[bk.line].0; let text_end = c.lines[last_line - 1].1; + let mut search = Vec::new(); + if !bk.query.is_empty() { + let len = bk.query.len(); + 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 + len, Attribute::NoReverse)); + } + } + let mut search = search.into_iter().peekable(); + let mut base = { let start = match c.attrs.binary_search_by_key(&text_start, |&x| x.0) { Ok(n) => n, @@ -358,16 +357,6 @@ impl View for Page { head.into_iter().chain(tail).peekable() }; - let mut search = Vec::new(); - if !bk.query.is_empty() { - let len = bk.query.len(); - 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 + len, Attribute::NoReverse)); - } - } - let mut search = search.into_iter().peekable(); - let mut attrs = Vec::new(); loop { match (search.peek(), base.peek()) { @@ -410,41 +399,6 @@ 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 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 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 { @@ -459,13 +413,10 @@ impl View for Search { Backspace => { bk.query.pop(); bk.jump_reset(); - self.search( - bk, - SearchArgs { - dir: bk.dir.clone(), - skip: false, - }, - ); + bk.search(SearchArgs { + dir: bk.dir.clone(), + skip: false, + }); } Char(c) => { bk.query.push(c); @@ -473,7 +424,7 @@ impl View for Search { dir: bk.dir.clone(), skip: false, }; - if !self.search(bk, args) { + if !bk.search(args) { bk.jump_reset(); } } |