Implement search for CellBuffer

This commit is contained in:
Manos Pitsidianakis 2020-02-26 12:25:57 +02:00
parent 4ac52d9d5b
commit ac71d627f1
No known key found for this signature in database
GPG Key ID: 73627C2F690DF710
5 changed files with 225 additions and 45 deletions

View File

@ -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

View File

@ -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;

View File

@ -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<SearchPattern>,
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::<Vec<(usize, usize)>>();
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;
}

View File

@ -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<String> = _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!("");
}
}

View File

@ -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::<SmallVec<[u8; 1024]>>();
let segment_tree = SegmentTree::from(array.clone());