diff options
author | James Campos <james.r.campos@gmail.com> | 2020-07-05 16:48:22 -0700 |
---|---|---|
committer | James Campos <james.r.campos@gmail.com> | 2020-07-05 16:48:22 -0700 |
commit | e580dab8a303b08cab5e4a1780fc4448d1234b74 (patch) | |
tree | 48891ffbf7415bb2cf18fb178877cdc69a9ff88a | |
parent | f54dfbd9ffdf1367bf027dc062bab82483def2ab (diff) | |
download | bk-e580dab8a303b08cab5e4a1780fc4448d1234b74.tar.gz |
lines as byte indexes
-rw-r--r-- | src/epub.rs | 5 | ||||
-rw-r--r-- | src/main.rs | 77 |
2 files changed, 40 insertions, 42 deletions
diff --git a/src/epub.rs b/src/epub.rs index 0945ad6..c4f79e3 100644 --- a/src/epub.rs +++ b/src/epub.rs @@ -1,8 +1,5 @@ -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; - use roxmltree::{Document, Node}; +use std::{collections::HashMap, fs::File, io::Read}; pub struct Epub { container: zip::ZipArchive<File>, diff --git a/src/main.rs b/src/main.rs index 9fb3b9a..0eed56b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ use crossterm::{ mod epub; // XXX assumes a char is i unit wide -fn wrap(text: &str, width: usize) -> Vec<(usize, String)> { +fn wrap(text: &str, width: usize) -> Vec<(usize, usize)> { let mut lines = Vec::new(); // bytes let mut start = 0; @@ -53,7 +53,7 @@ fn wrap(text: &str, width: usize) -> Vec<(usize, String)> { end = i; skip = false; } - lines.push((start, String::from(&text[start..end]))); + lines.push((start, end)); start = end; if skip { start += 1; @@ -117,16 +117,16 @@ impl View for Metadata { let pages = lines[bk.chapter] / bk.rows; let page = bk.line / bk.rows; - let mut vec = vec![format!("chapter: {}/{}", page, pages), + let mut vec = vec![ + format!("chapter: {}/{}", page, pages), format!("total: {:.0}%", progress), - String::new() + String::new(), ]; vec.extend_from_slice(&bk.meta); vec } } - struct Help; impl View for Help { fn run(&self, bk: &mut Bk, _: KeyCode) { @@ -243,17 +243,16 @@ impl View for Page { KeyCode::Char('i') => bk.view = Some(&Metadata), KeyCode::Char('?') => bk.start_search(Direction::Backward), KeyCode::Char('/') => bk.start_search(Direction::Forward), + // XXX temporarily broken (well needing to manually advance before searching) KeyCode::Char('N') => { bk.search(Direction::Backward); } KeyCode::Char('n') => { - // FIXME - bk.scroll_down(1); bk.search(Direction::Forward); } KeyCode::End | KeyCode::Char('G') => { bk.mark('\''); - bk.line = bk.lines().len().saturating_sub(bk.rows); + bk.line = bk.chap().lines.len().saturating_sub(bk.rows); } KeyCode::Home | KeyCode::Char('g') => { bk.mark('\''); @@ -287,8 +286,12 @@ impl View for Page { } } fn render(&self, bk: &Bk) -> Vec<String> { - let end = min(bk.line + bk.rows, bk.lines().len()); - bk.lines()[bk.line..end].iter().map(String::from).collect() + let c = bk.chap(); + let end = min(bk.line + bk.rows, c.lines.len()); + c.lines[bk.line..end] + .iter() + .map(|&(a, b)| String::from(&c.text[a..b])) + .collect() } } @@ -318,10 +321,12 @@ impl View for Search { } } fn render(&self, bk: &Bk) -> Vec<String> { - let end = min(bk.line + bk.rows - 1, bk.lines().len()); + let c = bk.chap(); + let end = min(bk.line + bk.rows - 1, c.lines.len()); let mut buf = Vec::with_capacity(bk.rows); - for line in bk.lines()[bk.line..end].iter() { + for &(a, b) in c.lines[bk.line..end].iter() { + let line = String::from(&c.text[a..b]); if let Some(i) = line.find(&bk.query) { buf.push(format!( "{}{}{}{}{}", @@ -332,7 +337,7 @@ impl View for Search { &line[i + bk.query.len()..], )); } else { - buf.push(String::from(line)); + buf.push(line); } } @@ -348,13 +353,12 @@ impl View for Search { } } -// search the text to find the byte index of the query, then find the containing line -// ideally we could use string slices as pointers, but self referential structs are hard struct Chapter { title: String, + // a single string for searching text: String, - bytes: Vec<usize>, - lines: Vec<String>, + // byte indexes + lines: Vec<(usize, usize)>, } struct Bk<'a> { @@ -380,7 +384,10 @@ impl Bk<'_> { fn new(epub: epub::Epub, args: Props) -> Self { let (cols, rows) = terminal::size().unwrap(); let width = min(cols, args.width) as usize; - let meta = wrap(&epub.meta, width).into_iter().map(|(_, b)| b).collect(); + let meta = wrap(&epub.meta, width) + .into_iter() + .map(|(a, b)| String::from(&epub.meta[a..b])) + .collect(); let mut chapters = Vec::with_capacity(epub.chapters.len()); for (text, title) in epub.chapters { @@ -393,13 +400,8 @@ impl Bk<'_> { } else { title }; - let (bytes, lines) = wrap(&text, width).into_iter().unzip(); - chapters.push(Chapter { - title, - text, - lines, - bytes, - }); + let lines = wrap(&text, width); + chapters.push(Chapter { title, text, lines }); } let mut mark = HashMap::new(); @@ -476,8 +478,8 @@ impl Bk<'_> { self.chapter = c; self.line = l; } - fn lines(&self) -> &Vec<String> { - &self.chapters[self.chapter].lines + fn chap(&self) -> &Chapter { + &self.chapters[self.chapter] } fn next_chapter(&mut self) { if self.chapter < self.chapters.len() - 1 { @@ -492,7 +494,7 @@ impl Bk<'_> { } } fn scroll_down(&mut self, n: usize) { - if self.line + self.rows < self.lines().len() { + if self.line + self.rows < self.chap().lines.len() { self.line += n; } else { self.next_chapter(); @@ -503,7 +505,7 @@ impl Bk<'_> { self.line = self.line.saturating_sub(n); } else { self.prev_chapter(); - self.line = self.lines().len().saturating_sub(self.rows); + self.line = self.chap().lines.len().saturating_sub(self.rows); } } fn start_search(&mut self, dir: Direction) { @@ -513,22 +515,20 @@ impl Bk<'_> { self.view = Some(&Search); } fn search(&mut self, dir: Direction) -> bool { - // https://doc.rust-lang.org/std/vec/struct.Vec.html#method.binary_search - // If the value is not found then Result::Err is returned, containing the index where a matching element - // could be inserted while maintaining sorted order. - let get_line = |bytes: &Vec<usize>, byte: usize| -> usize { - match bytes.binary_search(&byte) { + let get_line = |lines: &Vec<(usize, usize)>, byte: usize| -> usize { + match lines.binary_search_by_key(&byte, |&(a, _)| a) { Ok(n) => n, Err(n) => n - 1, } }; - let head = (self.chapter, self.chapters[self.chapter].bytes[self.line]); + let (start, end) = self.chap().lines[self.line]; match dir { Direction::Forward => { + let head = (self.chapter, start); 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].bytes, index + byte); + self.line = get_line(&self.chapters[c].lines, index + byte); self.chapter = c; return true; } @@ -536,12 +536,13 @@ impl Bk<'_> { false } Direction::Backward => { - let tail = (0..self.chapter - 1) + let head = (self.chapter, end); + 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].bytes, index); + self.line = get_line(&self.chapters[c].lines, index); self.chapter = c; return true; } |