aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/main.rs85
-rw-r--r--src/view.rs219
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();
}
}