aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJames Campos <james.r.campos@gmail.com>2020-08-03 16:09:13 -0700
committerJames Campos <james.r.campos@gmail.com>2020-08-03 16:09:13 -0700
commitdb1099437729ccd0557e064a4aa15460a59213ca (patch)
treeb4d12ae174003434328e06e7dfacbf658b77571a /src
parentec36377b9f316741f1e4dc80585fac29777b8e5c (diff)
downloadbk-db1099437729ccd0557e064a4aa15460a59213ca.tar.gz
view.rs
Diffstat (limited to 'src')
-rw-r--r--src/main.rs416
-rw-r--r--src/view.rs430
2 files changed, 439 insertions, 407 deletions
diff --git a/src/main.rs b/src/main.rs
index 0acbc59..0b322c8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,14 +1,14 @@
use anyhow::Result;
use crossterm::{
cursor,
- event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, MouseEvent},
+ event::{self, DisableMouseCapture, EnableMouseCapture, Event},
queue,
- style::{Attribute, Print},
+ style::{self, Print},
terminal,
};
use serde::{Deserialize, Serialize};
use std::{
- cmp::{min, Ordering},
+ cmp::min,
collections::HashMap,
env, fs,
io::{stdout, Write},
@@ -17,6 +17,9 @@ use std::{
};
use unicode_width::UnicodeWidthChar;
+mod view;
+use view::{Nav, Page, Search, View};
+
mod epub;
use epub::Chapter;
@@ -75,7 +78,7 @@ fn wrap(text: &str, max_cols: usize) -> Vec<(usize, usize)> {
lines
}
-fn get_line(lines: &Vec<(usize, usize)>, byte: usize) -> usize {
+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,
@@ -93,408 +96,7 @@ enum Direction {
Prev,
}
-trait View {
- fn render(&self, bk: &Bk) -> Vec<String>;
- fn on_key(&self, bk: &mut Bk, kc: KeyCode);
- fn on_mouse(&self, _: &mut Bk, _: MouseEvent) {}
-}
-
-// TODO render something useful?
-struct Mark;
-impl View for Mark {
- fn on_key(&self, bk: &mut Bk, kc: KeyCode) {
- if let KeyCode::Char(c) = kc {
- bk.mark(c)
- }
- bk.view = Some(&Page)
- }
- fn render(&self, bk: &Bk) -> Vec<String> {
- Page::render(&Page, bk)
- }
-}
-
-struct Jump;
-impl View for Jump {
- fn on_key(&self, bk: &mut Bk, kc: KeyCode) {
- if let KeyCode::Char(c) = kc {
- if let Some(&pos) = bk.mark.get(&c) {
- bk.jump(pos);
- }
- }
- bk.view = Some(&Page);
- }
- fn render(&self, bk: &Bk) -> Vec<String> {
- Page::render(&Page, bk)
- }
-}
-
-struct Metadata;
-impl View for Metadata {
- fn on_key(&self, bk: &mut Bk, _: KeyCode) {
- bk.view = Some(&Page);
- }
- fn render(&self, bk: &Bk) -> Vec<String> {
- let lines: Vec<usize> = bk.chapters.iter().map(|c| c.lines.len()).collect();
- let current = lines[..bk.chapter].iter().sum::<usize>() + bk.line;
- let total = lines.iter().sum::<usize>();
- let progress = current as f32 / total as f32 * 100.0;
-
- let pages = lines[bk.chapter] / bk.rows;
- let page = bk.line / bk.rows;
-
- let mut vec = vec![
- format!("chapter: {}/{}", page, pages),
- format!("total: {:.0}%", progress),
- String::new(),
- ];
- vec.extend_from_slice(&bk.meta);
- vec
- }
-}
-
-struct Help;
-impl View for Help {
- fn on_key(&self, bk: &mut Bk, _: KeyCode) {
- bk.view = Some(&Page);
- }
- fn render(&self, _: &Bk) -> Vec<String> {
- let text = r#"
- Esc q Quit
- Fn Help
- Tab Table of Contents
- i Progress and Metadata
-
-PageDown Right Space f l Page Down
- PageUp Left b h Page Up
- d Half Page Down
- u Half Page Up
- Down j Line Down
- Up k Line Up
- Home g Chapter Start
- End G Chapter End
- [ Previous Chapter
- ] Next Chapter
-
- / Search Forward
- ? Search Backward
- n Repeat search forward
- N Repeat search backward
- mx Set mark x
- 'x Jump to mark x
- "#;
-
- text.lines().map(String::from).collect()
- }
-}
-
-struct Nav;
-impl View for Nav {
- fn on_key(&self, bk: &mut Bk, kc: KeyCode) {
- match kc {
- KeyCode::Esc
- | KeyCode::Tab
- | KeyCode::Left
- | KeyCode::Char('h')
- | KeyCode::Char('q') => {
- bk.jump_reset();
- bk.view = Some(&Page);
- }
- KeyCode::Enter | KeyCode::Right | KeyCode::Char('l') => {
- bk.line = 0;
- bk.view = Some(&Page);
- }
- KeyCode::Down | KeyCode::Char('j') => {
- if bk.chapter < bk.chapters.len() - 1 {
- bk.chapter += 1;
- if bk.chapter == bk.nav_top + bk.rows {
- bk.nav_top += 1;
- }
- }
- }
- KeyCode::Up | KeyCode::Char('k') => {
- if bk.chapter > 0 {
- if bk.chapter == bk.nav_top {
- bk.nav_top -= 1;
- }
- bk.chapter -= 1;
- }
- }
- KeyCode::Home | KeyCode::Char('g') => {
- bk.chapter = 0;
- bk.nav_top = 0;
- }
- KeyCode::End | KeyCode::Char('G') => {
- bk.chapter = bk.chapters.len() - 1;
- bk.nav_top = bk.chapters.len().saturating_sub(bk.rows);
- }
- _ => (),
- }
- }
- fn render(&self, bk: &Bk) -> Vec<String> {
- let end = min(bk.nav_top + bk.rows, bk.chapters.len());
-
- bk.chapters[bk.nav_top..end]
- .iter()
- .enumerate()
- .map(|(i, chapter)| {
- if bk.chapter == bk.nav_top + i {
- format!(
- "{}{}{}",
- Attribute::Reverse,
- chapter.title,
- Attribute::Reset
- )
- } else {
- chapter.title.to_string()
- }
- })
- .collect()
- }
-}
-
-struct Page;
-impl View for Page {
- fn on_mouse(&self, bk: &mut Bk, e: MouseEvent) {
- match e {
- MouseEvent::Down(_, col, row, _) => {
- let c = bk.chap();
- let line = bk.line + row as usize;
-
- if col < bk.pad() || line >= c.lines.len() {
- return;
- }
- let (start, end) = c.lines[line];
- let line_col = (col - bk.pad()) 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 !found {
- return;
- }
-
- let r = c.links.binary_search_by(|&(start, end, _)| {
- if start > byte {
- Ordering::Greater
- } else if end <= byte {
- Ordering::Less
- } else {
- Ordering::Equal
- }
- });
-
- 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));
- }
- }
- MouseEvent::ScrollDown(_, _, _) => bk.scroll_down(3),
- MouseEvent::ScrollUp(_, _, _) => bk.scroll_up(3),
- _ => (),
- }
- }
- fn on_key(&self, bk: &mut Bk, kc: KeyCode) {
- match kc {
- KeyCode::Esc | KeyCode::Char('q') => bk.view = None,
- KeyCode::Tab => {
- bk.nav_top = bk.chapter.saturating_sub(bk.rows - 1);
- bk.mark('\'');
- bk.view = Some(&Nav);
- }
- KeyCode::F(_) => bk.view = Some(&Help),
- KeyCode::Char('m') => bk.view = Some(&Mark),
- KeyCode::Char('\'') => bk.view = Some(&Jump),
- KeyCode::Char('i') => bk.view = Some(&Metadata),
- KeyCode::Char('?') => bk.start_search(Direction::Prev),
- KeyCode::Char('/') => bk.start_search(Direction::Next),
- KeyCode::Char('N') => {
- bk.search(SearchArgs {
- dir: Direction::Prev,
- skip: true,
- });
- }
- KeyCode::Char('n') => {
- bk.search(SearchArgs {
- dir: Direction::Next,
- skip: true,
- });
- }
- KeyCode::End | KeyCode::Char('G') => {
- bk.mark('\'');
- bk.line = bk.chap().lines.len().saturating_sub(bk.rows);
- }
- KeyCode::Home | KeyCode::Char('g') => {
- bk.mark('\'');
- bk.line = 0;
- }
- KeyCode::Char('d') => bk.scroll_down(bk.rows / 2),
- KeyCode::Char('u') => bk.scroll_up(bk.rows / 2),
- KeyCode::Up | KeyCode::Char('k') => bk.scroll_up(3),
- KeyCode::Left | KeyCode::PageUp | KeyCode::Char('b') | KeyCode::Char('h') => {
- bk.scroll_up(bk.rows);
- }
- KeyCode::Down | KeyCode::Char('j') => bk.scroll_down(3),
- KeyCode::Right
- | KeyCode::PageDown
- | KeyCode::Char('f')
- | KeyCode::Char('l')
- | KeyCode::Char(' ') => bk.scroll_down(bk.rows),
- KeyCode::Char('[') => bk.prev_chapter(),
- KeyCode::Char(']') => bk.next_chapter(),
- _ => (),
- }
- }
- fn render(&self, bk: &Bk) -> Vec<String> {
- let c = bk.chap();
- let line_end = min(bk.line + bk.rows, c.lines.len());
-
- let attrs = {
- let text_start = c.lines[bk.line].0;
- let text_end = c.lines[line_end - 1].1;
-
- let qlen = bk.query.len();
- let mut search = Vec::new();
- if qlen > 0 {
- 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 + qlen, Attribute::NoReverse));
- }
- }
- let mut search_iter = search.into_iter().peekable();
-
- let mut merged = Vec::new();
- let attr_start = match c
- .attrs
- .binary_search_by_key(&text_start, |&(pos, _, _)| pos)
- {
- Ok(n) => n,
- Err(n) => n - 1,
- };
- let mut attrs_iter = c.attrs[attr_start..].iter();
- let (_, _, attr) = attrs_iter.next().unwrap();
- if attr.has(Attribute::Bold) {
- merged.push((text_start, Attribute::Bold));
- }
- if attr.has(Attribute::Italic) {
- merged.push((text_start, Attribute::Italic));
- }
- if attr.has(Attribute::Underlined) {
- merged.push((text_start, Attribute::Underlined));
- }
- let mut attrs_iter = attrs_iter
- .map(|&(pos, a, _)| (pos, a))
- .take_while(|(pos, _)| pos <= &text_end)
- .peekable();
-
- // use itertools?
- loop {
- match (search_iter.peek(), attrs_iter.peek()) {
- (None, None) => break,
- (Some(_), None) => {
- merged.extend(search_iter);
- break;
- }
- (None, Some(_)) => {
- merged.extend(attrs_iter);
- break;
- }
- (Some(&s), Some(&a)) => {
- if s.0 < a.0 {
- merged.push(s);
- search_iter.next();
- } else {
- merged.push(a);
- attrs_iter.next();
- }
- }
- }
- }
-
- merged
- };
-
- let mut buf = Vec::new();
- let mut iter = attrs.into_iter().peekable();
- for &(mut start, end) in &c.lines[bk.line..line_end] {
- let mut s = String::new();
- while let Some(&(pos, attr)) = iter.peek() {
- if pos > end {
- break;
- }
- s.push_str(&c.text[start..pos]);
- s.push_str(&attr.to_string());
- start = pos;
- iter.next();
- }
- s.push_str(&c.text[start..end]);
- buf.push(s);
- }
- buf
- }
-}
-
-struct Search;
-impl View for Search {
- fn on_key(&self, bk: &mut Bk, kc: KeyCode) {
- match kc {
- KeyCode::Esc => {
- bk.jump_reset();
- bk.view = Some(&Page);
- }
- KeyCode::Enter => {
- bk.view = Some(&Page);
- }
- KeyCode::Backspace => {
- bk.query.pop();
- bk.jump_reset();
- bk.search(SearchArgs {
- dir: bk.dir.clone(),
- skip: false,
- });
- }
- KeyCode::Char(c) => {
- bk.query.push(c);
- let args = SearchArgs {
- dir: bk.dir.clone(),
- skip: false,
- };
- if !bk.search(args) {
- bk.jump_reset();
- }
- }
- _ => (),
- }
- }
- fn render(&self, bk: &Bk) -> Vec<String> {
- let mut buf = Page::render(&Page, bk);
- if buf.len() == bk.rows {
- buf.pop();
- } else {
- for _ in buf.len()..bk.rows - 1 {
- buf.push(String::new());
- }
- }
- let prefix = match bk.dir {
- Direction::Next => '/',
- Direction::Prev => '?',
- };
- buf.push(format!("{}{}", prefix, bk.query));
- buf
- }
-}
-
-struct Bk<'a> {
+pub struct Bk<'a> {
chapters: Vec<epub::Chapter>,
// position in the book
chapter: usize,
@@ -573,7 +175,7 @@ impl Bk<'_> {
queue!(
stdout,
terminal::Clear(terminal::ClearType::All),
- Print(Attribute::Reset)
+ Print(style::Attribute::Reset)
)?;
for (i, line) in view.render(self).iter().enumerate() {
queue!(stdout, cursor::MoveTo(self.pad(), i as u16), Print(line))?;
diff --git a/src/view.rs b/src/view.rs
new file mode 100644
index 0000000..d5818df
--- /dev/null
+++ b/src/view.rs
@@ -0,0 +1,430 @@
+use crossterm::{
+ event::{KeyCode, MouseEvent},
+ style::Attribute,
+};
+use std::cmp::{min, Ordering};
+use unicode_width::UnicodeWidthChar;
+
+use crate::{get_line, Bk, Direction, SearchArgs};
+
+pub trait View {
+ fn render(&self, bk: &Bk) -> Vec<String>;
+ fn on_key(&self, bk: &mut Bk, kc: KeyCode);
+ fn on_mouse(&self, _: &mut Bk, _: MouseEvent) {}
+}
+
+// TODO render something useful?
+struct Mark;
+impl View for Mark {
+ fn on_key(&self, bk: &mut Bk, kc: KeyCode) {
+ if let KeyCode::Char(c) = kc {
+ bk.mark(c)
+ }
+ bk.view = Some(&Page)
+ }
+ fn render(&self, bk: &Bk) -> Vec<String> {
+ Page::render(&Page, bk)
+ }
+}
+
+struct Jump;
+impl View for Jump {
+ fn on_key(&self, bk: &mut Bk, kc: KeyCode) {
+ if let KeyCode::Char(c) = kc {
+ if let Some(&pos) = bk.mark.get(&c) {
+ bk.jump(pos);
+ }
+ }
+ bk.view = Some(&Page);
+ }
+ fn render(&self, bk: &Bk) -> Vec<String> {
+ Page::render(&Page, bk)
+ }
+}
+
+struct Metadata;
+impl View for Metadata {
+ fn on_key(&self, bk: &mut Bk, _: KeyCode) {
+ bk.view = Some(&Page);
+ }
+ fn render(&self, bk: &Bk) -> Vec<String> {
+ let lines: Vec<usize> = bk.chapters.iter().map(|c| c.lines.len()).collect();
+ let current = lines[..bk.chapter].iter().sum::<usize>() + bk.line;
+ let total = lines.iter().sum::<usize>();
+ let progress = current as f32 / total as f32 * 100.0;
+
+ let pages = lines[bk.chapter] / bk.rows;
+ let page = bk.line / bk.rows;
+
+ let mut vec = vec![
+ format!("chapter: {}/{}", page, pages),
+ format!("total: {:.0}%", progress),
+ String::new(),
+ ];
+ vec.extend_from_slice(&bk.meta);
+ vec
+ }
+}
+
+struct Help;
+impl View for Help {
+ fn on_key(&self, bk: &mut Bk, _: KeyCode) {
+ bk.view = Some(&Page);
+ }
+ fn render(&self, _: &Bk) -> Vec<String> {
+ let text = r#"
+ Esc q Quit
+ Fn Help
+ Tab Table of Contents
+ i Progress and Metadata
+
+PageDown Right Space f l Page Down
+ PageUp Left b h Page Up
+ d Half Page Down
+ u Half Page Up
+ Down j Line Down
+ Up k Line Up
+ Home g Chapter Start
+ End G Chapter End
+ [ Previous Chapter
+ ] Next Chapter
+
+ / Search Forward
+ ? Search Backward
+ n Repeat search forward
+ N Repeat search backward
+ mx Set mark x
+ 'x Jump to mark x
+ "#;
+
+ text.lines().map(String::from).collect()
+ }
+}
+
+pub struct Nav;
+
+impl Nav {
+ fn scroll_up(&self, bk: &mut Bk) {
+ if bk.chapter > 0 {
+ if bk.chapter == bk.nav_top {
+ bk.nav_top -= 1;
+ }
+ bk.chapter -= 1;
+ }
+ }
+ fn scroll_down(&self, bk: &mut Bk) {
+ if bk.chapter < bk.chapters.len() - 1 {
+ bk.chapter += 1;
+ if bk.chapter == bk.nav_top + bk.rows {
+ bk.nav_top += 1;
+ }
+ }
+ }
+ fn click(&self, bk: &mut Bk, row: usize) {
+ if bk.nav_top + row < bk.chapters.len() {
+ bk.chapter = bk.nav_top + row;
+ bk.line = 0;
+ bk.view = Some(&Page);
+ }
+ }
+}
+
+impl View for Nav {
+ fn on_mouse(&self, bk: &mut Bk, e: MouseEvent) {
+ match e {
+ MouseEvent::Down(_, _, row, _) => self.click(bk, row as usize),
+ MouseEvent::ScrollDown(_, _, _) => self.scroll_down(bk),
+ MouseEvent::ScrollUp(_, _, _) => self.scroll_up(bk),
+ _ => (),
+ }
+ }
+ fn on_key(&self, bk: &mut Bk, kc: KeyCode) {
+ match kc {
+ KeyCode::Esc
+ | KeyCode::Tab
+ | KeyCode::Left
+ | KeyCode::Char('h')
+ | KeyCode::Char('q') => {
+ bk.jump_reset();
+ bk.view = Some(&Page);
+ }
+ KeyCode::Enter | KeyCode::Right | KeyCode::Char('l') => {
+ bk.line = 0;
+ bk.view = Some(&Page);
+ }
+ KeyCode::Down | KeyCode::Char('j') => self.scroll_down(bk),
+ KeyCode::Up | KeyCode::Char('k') => self.scroll_up(bk),
+ KeyCode::Home | KeyCode::Char('g') => {
+ bk.chapter = 0;
+ bk.nav_top = 0;
+ }
+ KeyCode::End | KeyCode::Char('G') => {
+ bk.chapter = bk.chapters.len() - 1;
+ bk.nav_top = bk.chapters.len().saturating_sub(bk.rows);
+ }
+ _ => (),
+ }
+ }
+ fn render(&self, bk: &Bk) -> Vec<String> {
+ let end = min(bk.nav_top + bk.rows, bk.chapters.len());
+
+ bk.chapters[bk.nav_top..end]
+ .iter()
+ .enumerate()
+ .map(|(i, chapter)| {
+ if bk.chapter == bk.nav_top + i {
+ format!(
+ "{}{}{}",
+ Attribute::Reverse,
+ chapter.title,
+ Attribute::Reset
+ )
+ } else {
+ chapter.title.to_string()
+ }
+ })
+ .collect()
+ }
+}
+
+pub struct Page;
+impl View for Page {
+ fn on_mouse(&self, bk: &mut Bk, e: MouseEvent) {
+ match e {
+ MouseEvent::Down(_, col, row, _) => {
+ let c = bk.chap();
+ let line = bk.line + row as usize;
+
+ if col < bk.pad() || line >= c.lines.len() {
+ return;
+ }
+ let (start, end) = c.lines[line];
+ let line_col = (col - bk.pad()) 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 !found {
+ return;
+ }
+
+ let r = c.links.binary_search_by(|&(start, end, _)| {
+ if start > byte {
+ Ordering::Greater
+ } else if end <= byte {
+ Ordering::Less
+ } else {
+ Ordering::Equal
+ }
+ });
+
+ 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));
+ }
+ }
+ MouseEvent::ScrollDown(_, _, _) => bk.scroll_down(3),
+ MouseEvent::ScrollUp(_, _, _) => bk.scroll_up(3),
+ _ => (),
+ }
+ }
+ fn on_key(&self, bk: &mut Bk, kc: KeyCode) {
+ match kc {
+ KeyCode::Esc | KeyCode::Char('q') => bk.view = None,
+ KeyCode::Tab => {
+ bk.nav_top = bk.chapter.saturating_sub(bk.rows - 1);
+ bk.mark('\'');
+ bk.view = Some(&Nav);
+ }
+ KeyCode::F(_) => bk.view = Some(&Help),
+ KeyCode::Char('m') => bk.view = Some(&Mark),
+ KeyCode::Char('\'') => bk.view = Some(&Jump),
+ KeyCode::Char('i') => bk.view = Some(&Metadata),
+ KeyCode::Char('?') => bk.start_search(Direction::Prev),
+ KeyCode::Char('/') => bk.start_search(Direction::Next),
+ KeyCode::Char('N') => {
+ bk.search(SearchArgs {
+ dir: Direction::Prev,
+ skip: true,
+ });
+ }
+ KeyCode::Char('n') => {
+ bk.search(SearchArgs {
+ dir: Direction::Next,
+ skip: true,
+ });
+ }
+ KeyCode::End | KeyCode::Char('G') => {
+ bk.mark('\'');
+ bk.line = bk.chap().lines.len().saturating_sub(bk.rows);
+ }
+ KeyCode::Home | KeyCode::Char('g') => {
+ bk.mark('\'');
+ bk.line = 0;
+ }
+ KeyCode::Char('d') => bk.scroll_down(bk.rows / 2),
+ KeyCode::Char('u') => bk.scroll_up(bk.rows / 2),
+ KeyCode::Up | KeyCode::Char('k') => bk.scroll_up(3),
+ KeyCode::Left | KeyCode::PageUp | KeyCode::Char('b') | KeyCode::Char('h') => {
+ bk.scroll_up(bk.rows);
+ }
+ KeyCode::Down | KeyCode::Char('j') => bk.scroll_down(3),
+ KeyCode::Right
+ | KeyCode::PageDown
+ | KeyCode::Char('f')
+ | KeyCode::Char('l')
+ | KeyCode::Char(' ') => bk.scroll_down(bk.rows),
+ KeyCode::Char('[') => bk.prev_chapter(),
+ KeyCode::Char(']') => bk.next_chapter(),
+ _ => (),
+ }
+ }
+ fn render(&self, bk: &Bk) -> Vec<String> {
+ let c = bk.chap();
+ let line_end = min(bk.line + bk.rows, c.lines.len());
+
+ let attrs = {
+ let text_start = c.lines[bk.line].0;
+ let text_end = c.lines[line_end - 1].1;
+
+ let qlen = bk.query.len();
+ let mut search = Vec::new();
+ if qlen > 0 {
+ 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 + qlen, Attribute::NoReverse));
+ }
+ }
+ let mut search_iter = search.into_iter().peekable();
+
+ let mut merged = Vec::new();
+ let attr_start = match c
+ .attrs
+ .binary_search_by_key(&text_start, |&(pos, _, _)| pos)
+ {
+ Ok(n) => n,
+ Err(n) => n - 1,
+ };
+ let mut attrs_iter = c.attrs[attr_start..].iter();
+ let (_, _, attr) = attrs_iter.next().unwrap();
+ if attr.has(Attribute::Bold) {
+ merged.push((text_start, Attribute::Bold));
+ }
+ if attr.has(Attribute::Italic) {
+ merged.push((text_start, Attribute::Italic));
+ }
+ if attr.has(Attribute::Underlined) {
+ merged.push((text_start, Attribute::Underlined));
+ }
+ let mut attrs_iter = attrs_iter
+ .map(|&(pos, a, _)| (pos, a))
+ .take_while(|(pos, _)| pos <= &text_end)
+ .peekable();
+
+ // use itertools?
+ loop {
+ match (search_iter.peek(), attrs_iter.peek()) {
+ (None, None) => break,
+ (Some(_), None) => {
+ merged.extend(search_iter);
+ break;
+ }
+ (None, Some(_)) => {
+ merged.extend(attrs_iter);
+ break;
+ }
+ (Some(&s), Some(&a)) => {
+ if s.0 < a.0 {
+ merged.push(s);
+ search_iter.next();
+ } else {
+ merged.push(a);
+ attrs_iter.next();
+ }
+ }
+ }
+ }
+
+ merged
+ };
+
+ let mut buf = Vec::new();
+ let mut iter = attrs.into_iter().peekable();
+ for &(mut start, end) in &c.lines[bk.line..line_end] {
+ let mut s = String::new();
+ while let Some(&(pos, attr)) = iter.peek() {
+ if pos > end {
+ break;
+ }
+ s.push_str(&c.text[start..pos]);
+ s.push_str(&attr.to_string());
+ start = pos;
+ iter.next();
+ }
+ s.push_str(&c.text[start..end]);
+ buf.push(s);
+ }
+ buf
+ }
+}
+
+pub struct Search;
+impl View for Search {
+ fn on_key(&self, bk: &mut Bk, kc: KeyCode) {
+ match kc {
+ KeyCode::Esc => {
+ bk.jump_reset();
+ bk.view = Some(&Page);
+ }
+ KeyCode::Enter => {
+ bk.view = Some(&Page);
+ }
+ KeyCode::Backspace => {
+ bk.query.pop();
+ bk.jump_reset();
+ bk.search(SearchArgs {
+ dir: bk.dir.clone(),
+ skip: false,
+ });
+ }
+ KeyCode::Char(c) => {
+ bk.query.push(c);
+ let args = SearchArgs {
+ dir: bk.dir.clone(),
+ skip: false,
+ };
+ if !bk.search(args) {
+ bk.jump_reset();
+ }
+ }
+ _ => (),
+ }
+ }
+ fn render(&self, bk: &Bk) -> Vec<String> {
+ let mut buf = Page::render(&Page, bk);
+ if buf.len() == bk.rows {
+ buf.pop();
+ } else {
+ for _ in buf.len()..bk.rows - 1 {
+ buf.push(String::new());
+ }
+ }
+ let prefix = match bk.dir {
+ Direction::Next => '/',
+ Direction::Prev => '?',
+ };
+ buf.push(format!("{}{}", prefix, bk.query));
+ buf
+ }
+}