aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Campos <james.r.campos@gmail.com>2021-04-01 18:31:42 -0700
committerJames Campos <james.r.campos@gmail.com>2021-04-01 18:31:42 -0700
commit5b9abdbafa6106dda8a7b862f4ce3d037a307961 (patch)
treeaa66a83c0a590cb028a074cd0224e0014c753263
parent9809dc99acd2ff65358b7bbe3c1be6c303f558b7 (diff)
downloadbk-5b9abdbafa6106dda8a7b862f4ce3d037a307961.tar.gz
internal
-rw-r--r--README.md2
-rw-r--r--src/main.rs67
-rw-r--r--src/view.rs115
3 files changed, 83 insertions, 101 deletions
diff --git a/README.md b/README.md
index fcb3702..5d94211 100644
--- a/README.md
+++ b/README.md
@@ -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();
}
}