From ac71d627f15e3c8638d8101957dbe4f896f173a8 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Wed, 26 Feb 2020 12:25:57 +0200 Subject: [PATCH] Implement search for CellBuffer --- melib/src/text_processing/mod.rs | 2 +- melib/src/text_processing/search.rs | 65 +++++++------- src/components/utilities.rs | 130 +++++++++++++++++++++++++--- src/terminal/cells.rs | 71 +++++++++++++++ src/types.rs | 2 +- 5 files changed, 225 insertions(+), 45 deletions(-) diff --git a/melib/src/text_processing/mod.rs b/melib/src/text_processing/mod.rs index 228316f88..e63cf6a45 100644 --- a/melib/src/text_processing/mod.rs +++ b/melib/src/text_processing/mod.rs @@ -139,7 +139,7 @@ fn test_globmatch() { assert!(!"INBOX/Lists/".matches_glob("INBOX/Lists/*")); } -const _ALICE_CHAPTER_1: &'static str = r#"CHAPTER I. Down the Rabbit-Hole +pub const _ALICE_CHAPTER_1: &'static str = r#"CHAPTER I. Down the Rabbit-Hole Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the diff --git a/melib/src/text_processing/search.rs b/melib/src/text_processing/search.rs index 5220ce81a..57282e34c 100644 --- a/melib/src/text_processing/search.rs +++ b/melib/src/text_processing/search.rs @@ -23,10 +23,41 @@ use super::TextProcessing; use smallvec::SmallVec; -pub trait KMP: TextProcessing { +pub trait KMP { + fn kmp_search(&self, pattern: &str) -> SmallVec<[usize; 256]>; + fn kmp_table(graphemes: &[&str]) -> SmallVec<[i32; 256]> { + let mut ret: SmallVec<_> = SmallVec::with_capacity(graphemes.len() + 1); + if graphemes.is_empty() { + return ret; + } + ret.push(-1); + for _ in 0..graphemes.len() { + ret.push(0); + } + let mut pos: usize = 1; + let mut cnd: i32 = 0; + while pos < graphemes.len() { + if graphemes[pos] == graphemes[cnd as usize] { + ret[pos] = ret[cnd as usize]; + } else { + ret[pos] = cnd; + cnd = ret[cnd as usize]; + while cnd >= 0 && graphemes[pos] != graphemes[cnd as usize] { + cnd = ret[cnd as usize]; + } + } + pos += 1; + cnd += 1; + } + ret[pos] = cnd; + ret + } +} + +impl KMP for str { fn kmp_search(&self, pattern: &str) -> SmallVec<[usize; 256]> { let w = pattern.split_graphemes(); - let t = kmp_table(&w); + let t = Self::kmp_table(&w); let mut j = 0; // (the position of the current character in text) let mut k = 0; // (the position of the current character in pattern) let mut ret = SmallVec::new(); @@ -52,36 +83,6 @@ pub trait KMP: TextProcessing { } } -impl KMP for str {} - -fn kmp_table(graphemes: &[&str]) -> SmallVec<[i32; 256]> { - let mut ret: SmallVec<_> = SmallVec::with_capacity(graphemes.len() + 1); - if graphemes.is_empty() { - return ret; - } - ret.push(-1); - for _ in 0..graphemes.len() { - ret.push(0); - } - let mut pos: usize = 1; - let mut cnd: i32 = 0; - while pos < graphemes.len() { - if graphemes[pos] == graphemes[cnd as usize] { - ret[pos] = ret[cnd as usize]; - } else { - ret[pos] = cnd; - cnd = ret[cnd as usize]; - while cnd >= 0 && graphemes[pos] != graphemes[cnd as usize] { - cnd = ret[cnd as usize]; - } - } - pos += 1; - cnd += 1; - } - ret[pos] = cnd; - ret -} - #[test] fn test_search() { use super::_ALICE_CHAPTER_1; diff --git a/src/components/utilities.rs b/src/components/utilities.rs index 2b266c4fb..1b4adb05f 100644 --- a/src/components/utilities.rs +++ b/src/components/utilities.rs @@ -760,7 +760,7 @@ impl Component for Pager { pattern: pattern.to_string(), positions: vec![], cursor: 0, - movement: None, + movement: Some(PageMovement::Home), }); self.initialised = false; self.dirty = true; @@ -1442,6 +1442,7 @@ pub struct Tabbed { help_screen_cursor: (usize, usize), help_content: CellBuffer, help_curr_views: ShortcutMaps, + help_search: Option, dirty: bool, id: ComponentId, @@ -1458,6 +1459,7 @@ impl Tabbed { help_content: CellBuffer::default(), help_screen_cursor: (0, 0), help_curr_views: ShortcutMaps::default(), + help_search: None, dirty: true, id: ComponentId::new_v4(), } @@ -1737,6 +1739,67 @@ impl Component for Tabbed { idx += 1; } self.help_curr_views = children_maps; + if let Some(ref mut search) = self.help_search { + use crate::melib::text_processing::search::KMP; + search.positions = self + .help_content + .kmp_search(&search.pattern) + .into_iter() + .map(|offset| (offset / width, offset % width)) + .collect::>(); + let results_attr = crate::conf::value(context, "pager.highlight_search"); + let results_current_attr = + crate::conf::value(context, "pager.highlight_search_current"); + search.cursor = + std::cmp::min(search.positions.len().saturating_sub(1), search.cursor); + for (i, (y, x)) in search.positions.iter().enumerate() { + for c in self + .help_content + .row_iter(*x..*x + search.pattern.grapheme_len(), *y) + { + if i == search.cursor { + self.help_content[c] + .set_fg(results_current_attr.fg) + .set_bg(results_current_attr.bg) + .set_attrs(results_current_attr.attrs); + } else { + self.help_content[c] + .set_fg(results_attr.fg) + .set_bg(results_attr.bg) + .set_attrs(results_attr.attrs); + } + } + } + if !search.positions.is_empty() { + if let Some(mvm) = search.movement.take() { + match mvm { + PageMovement::Home => { + if self.help_screen_cursor.1 > search.positions[search.cursor].0 { + self.help_screen_cursor.1 = search.positions[search.cursor].0; + } + if self.help_screen_cursor.1 + rows + < search.positions[search.cursor].0 + { + self.help_screen_cursor.1 = search.positions[search.cursor].0; + } + } + PageMovement::Up(_) => { + if self.help_screen_cursor.1 > search.positions[search.cursor].0 { + self.help_screen_cursor.1 = search.positions[search.cursor].0; + } + } + PageMovement::Down(_) => { + if self.help_screen_cursor.1 + rows + < search.positions[search.cursor].0 + { + self.help_screen_cursor.1 = search.positions[search.cursor].0; + } + } + _ => {} + } + } + } + } copy_area( grid, &self.help_content, @@ -1755,11 +1818,11 @@ impl Component for Tabbed { } self.dirty = false; } - fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { + fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool { let shortcuts = self.get_shortcuts(context); - match *event { - UIEvent::Input(Key::Alt(no)) if no >= '1' && no <= '9' => { - let no = no as usize - '1' as usize; + match &mut event { + UIEvent::Input(Key::Alt(no)) if *no >= '1' && *no <= '9' => { + let no = *no as usize - '1' as usize; if no < self.children.len() { self.cursor_pos = no % self.children.len(); context @@ -1791,7 +1854,7 @@ impl Component for Tabbed { return true; } UIEvent::Action(Tab(NewDraft(account_idx, ref draft))) => { - let mut composer = Composer::new(account_idx, context); + let mut composer = Composer::new(*account_idx, context); if let Some(draft) = draft { composer.set_draft(draft.clone()); } @@ -1801,13 +1864,17 @@ impl Component for Tabbed { return true; } UIEvent::Action(Tab(Reply(coordinates, msg))) => { - self.add_component(Box::new(Composer::with_context(coordinates, msg, context))); + self.add_component(Box::new(Composer::with_context( + *coordinates, + *msg, + context, + ))); self.cursor_pos = self.children.len() - 1; self.children[self.cursor_pos].set_dirty(true); return true; } UIEvent::Action(Tab(Edit(account_pos, msg))) => { - let composer = match Composer::edit(account_pos, msg, context) { + let composer = match Composer::edit(*account_pos, *msg, context) { Ok(c) => c, Err(e) => { context.replies.push_back(UIEvent::Notification( @@ -1818,9 +1885,9 @@ impl Component for Tabbed { log( format!( "Failed to open envelope {}: {}", - context.accounts[account_pos] + context.accounts[*account_pos] .collection - .get_env(msg) + .get_env(*msg) .message_id_display(), e.to_string() ), @@ -1853,7 +1920,7 @@ impl Component for Tabbed { if self.pinned > self.cursor_pos { return true; } - if let Some(c_idx) = self.children.iter().position(|x| x.id() == id) { + if let Some(c_idx) = self.children.iter().position(|x| x.id() == *id) { self.children.remove(c_idx); self.cursor_pos = 0; self.set_dirty(true); @@ -1865,6 +1932,47 @@ impl Component for Tabbed { ); } } + UIEvent::Action(Action::Listing(ListingAction::Filter(pattern))) + if self.show_shortcuts => + { + self.help_search = Some(SearchPattern { + pattern: pattern.to_string(), + positions: vec![], + cursor: 0, + movement: Some(PageMovement::Home), + }); + self.dirty = true; + return true; + } + UIEvent::Input(Key::Char('n')) if self.show_shortcuts && self.help_search.is_some() => { + if let Some(ref mut search) = self.help_search { + search.movement = Some(PageMovement::Down(1)); + search.cursor += 1; + } else { + unsafe { + std::hint::unreachable_unchecked(); + } + } + self.dirty = true; + return true; + } + UIEvent::Input(Key::Char('N')) if self.show_shortcuts && self.help_search.is_some() => { + if let Some(ref mut search) = self.help_search { + search.movement = Some(PageMovement::Up(1)); + search.cursor = search.cursor.saturating_sub(1); + } else { + unsafe { + std::hint::unreachable_unchecked(); + } + } + self.dirty = true; + return true; + } + UIEvent::Input(Key::Esc) if self.show_shortcuts && self.help_search.is_some() => { + self.help_search = None; + self.dirty = true; + return true; + } UIEvent::Resize => { self.dirty = true; } diff --git a/src/terminal/cells.rs b/src/terminal/cells.rs index 64ea384d4..bbb2f015e 100644 --- a/src/terminal/cells.rs +++ b/src/terminal/cells.rs @@ -2425,3 +2425,74 @@ pub mod boundaries { ) } } + +use melib::text_processing::search::KMP; + +impl KMP for CellBuffer { + fn kmp_search(&self, pattern: &str) -> smallvec::SmallVec<[usize; 256]> { + let (mut w, prev_ind) = + pattern + .char_indices() + .skip(1) + .fold((vec![], 0), |(mut acc, prev_ind), (i, _)| { + acc.push(&pattern[prev_ind..i]); + (acc, i) + }); + w.push(&pattern[prev_ind..]); + let t = Self::kmp_table(&w); + let mut j = 0; // (the position of the current character in text) + let mut k = 0; // (the position of the current character in pattern) + let mut ret = smallvec::SmallVec::new(); + + while j < self.buf.len() && k < w.len() as i32 { + if self.buf[j].ch() == '\n' { + j += 1; + continue; + } + if w[k as usize] == self.buf[j].ch().encode_utf8(&mut [0; 4]) { + j += 1; + k += 1; + if k as usize == w.len() { + ret.push(j - (k as usize)); + k = t[k as usize]; + } + } else { + k = t[k as usize]; + if k < 0 { + j += 1; + k += 1; + } + } + } + ret + } +} + +#[test] +fn test_cellbuffer_search() { + use melib::text_processing::{Reflow, TextProcessing, _ALICE_CHAPTER_1}; + let lines: Vec = _ALICE_CHAPTER_1.split_lines_reflow(Reflow::All, Some(78)); + let mut buf = CellBuffer::new( + lines.iter().map(String::len).max().unwrap(), + lines.len(), + Cell::with_char(' '), + ); + let width = buf.size().0; + for (i, l) in lines.iter().enumerate() { + write_string_to_grid( + l, + &mut buf, + Color::Default, + Color::Default, + Attr::Default, + ((0, i), (width.saturating_sub(1), i)), + None, + ); + } + for ind in buf.kmp_search("Alice") { + for c in &buf.cellvec()[ind..std::cmp::min(buf.cellvec().len(), ind + 25)] { + print!("{}", c.ch()); + } + println!(""); + } +} diff --git a/src/types.rs b/src/types.rs index 283b551c0..4c7b8db7e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -252,7 +252,7 @@ pub mod segment_tree { #[test] fn test_segment_tree() { let array: SmallVec<[u8; 1024]> = [9, 1, 17, 2, 3, 23, 4, 5, 6, 37] - .into_iter() + .iter() .cloned() .collect::>(); let segment_tree = SegmentTree::from(array.clone());