Implement search for CellBuffer
parent
4ac52d9d5b
commit
ac71d627f1
|
@ -139,7 +139,7 @@ fn test_globmatch() {
|
||||||
assert!(!"INBOX/Lists/".matches_glob("INBOX/Lists/*"));
|
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
|
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
|
bank, and of having nothing to do: once or twice she had peeped into the
|
||||||
|
|
|
@ -23,10 +23,41 @@ use super::TextProcessing;
|
||||||
|
|
||||||
use smallvec::SmallVec;
|
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]> {
|
fn kmp_search(&self, pattern: &str) -> SmallVec<[usize; 256]> {
|
||||||
let w = pattern.split_graphemes();
|
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 j = 0; // (the position of the current character in text)
|
||||||
let mut k = 0; // (the position of the current character in pattern)
|
let mut k = 0; // (the position of the current character in pattern)
|
||||||
let mut ret = SmallVec::new();
|
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]
|
#[test]
|
||||||
fn test_search() {
|
fn test_search() {
|
||||||
use super::_ALICE_CHAPTER_1;
|
use super::_ALICE_CHAPTER_1;
|
||||||
|
|
|
@ -760,7 +760,7 @@ impl Component for Pager {
|
||||||
pattern: pattern.to_string(),
|
pattern: pattern.to_string(),
|
||||||
positions: vec![],
|
positions: vec![],
|
||||||
cursor: 0,
|
cursor: 0,
|
||||||
movement: None,
|
movement: Some(PageMovement::Home),
|
||||||
});
|
});
|
||||||
self.initialised = false;
|
self.initialised = false;
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
@ -1442,6 +1442,7 @@ pub struct Tabbed {
|
||||||
help_screen_cursor: (usize, usize),
|
help_screen_cursor: (usize, usize),
|
||||||
help_content: CellBuffer,
|
help_content: CellBuffer,
|
||||||
help_curr_views: ShortcutMaps,
|
help_curr_views: ShortcutMaps,
|
||||||
|
help_search: Option<SearchPattern>,
|
||||||
|
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
id: ComponentId,
|
id: ComponentId,
|
||||||
|
@ -1458,6 +1459,7 @@ impl Tabbed {
|
||||||
help_content: CellBuffer::default(),
|
help_content: CellBuffer::default(),
|
||||||
help_screen_cursor: (0, 0),
|
help_screen_cursor: (0, 0),
|
||||||
help_curr_views: ShortcutMaps::default(),
|
help_curr_views: ShortcutMaps::default(),
|
||||||
|
help_search: None,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
id: ComponentId::new_v4(),
|
id: ComponentId::new_v4(),
|
||||||
}
|
}
|
||||||
|
@ -1737,6 +1739,67 @@ impl Component for Tabbed {
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
self.help_curr_views = children_maps;
|
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(
|
copy_area(
|
||||||
grid,
|
grid,
|
||||||
&self.help_content,
|
&self.help_content,
|
||||||
|
@ -1755,11 +1818,11 @@ impl Component for Tabbed {
|
||||||
}
|
}
|
||||||
self.dirty = false;
|
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);
|
let shortcuts = self.get_shortcuts(context);
|
||||||
match *event {
|
match &mut event {
|
||||||
UIEvent::Input(Key::Alt(no)) if no >= '1' && no <= '9' => {
|
UIEvent::Input(Key::Alt(no)) if *no >= '1' && *no <= '9' => {
|
||||||
let no = no as usize - '1' as usize;
|
let no = *no as usize - '1' as usize;
|
||||||
if no < self.children.len() {
|
if no < self.children.len() {
|
||||||
self.cursor_pos = no % self.children.len();
|
self.cursor_pos = no % self.children.len();
|
||||||
context
|
context
|
||||||
|
@ -1791,7 +1854,7 @@ impl Component for Tabbed {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
UIEvent::Action(Tab(NewDraft(account_idx, ref draft))) => {
|
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 {
|
if let Some(draft) = draft {
|
||||||
composer.set_draft(draft.clone());
|
composer.set_draft(draft.clone());
|
||||||
}
|
}
|
||||||
|
@ -1801,13 +1864,17 @@ impl Component for Tabbed {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
UIEvent::Action(Tab(Reply(coordinates, msg))) => {
|
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.cursor_pos = self.children.len() - 1;
|
||||||
self.children[self.cursor_pos].set_dirty(true);
|
self.children[self.cursor_pos].set_dirty(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
UIEvent::Action(Tab(Edit(account_pos, msg))) => {
|
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,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
context.replies.push_back(UIEvent::Notification(
|
context.replies.push_back(UIEvent::Notification(
|
||||||
|
@ -1818,9 +1885,9 @@ impl Component for Tabbed {
|
||||||
log(
|
log(
|
||||||
format!(
|
format!(
|
||||||
"Failed to open envelope {}: {}",
|
"Failed to open envelope {}: {}",
|
||||||
context.accounts[account_pos]
|
context.accounts[*account_pos]
|
||||||
.collection
|
.collection
|
||||||
.get_env(msg)
|
.get_env(*msg)
|
||||||
.message_id_display(),
|
.message_id_display(),
|
||||||
e.to_string()
|
e.to_string()
|
||||||
),
|
),
|
||||||
|
@ -1853,7 +1920,7 @@ impl Component for Tabbed {
|
||||||
if self.pinned > self.cursor_pos {
|
if self.pinned > self.cursor_pos {
|
||||||
return true;
|
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.children.remove(c_idx);
|
||||||
self.cursor_pos = 0;
|
self.cursor_pos = 0;
|
||||||
self.set_dirty(true);
|
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 => {
|
UIEvent::Resize => {
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -252,7 +252,7 @@ pub mod segment_tree {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_segment_tree() {
|
fn test_segment_tree() {
|
||||||
let array: SmallVec<[u8; 1024]> = [9, 1, 17, 2, 3, 23, 4, 5, 6, 37]
|
let array: SmallVec<[u8; 1024]> = [9, 1, 17, 2, 3, 23, 4, 5, 6, 37]
|
||||||
.into_iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<SmallVec<[u8; 1024]>>();
|
.collect::<SmallVec<[u8; 1024]>>();
|
||||||
let segment_tree = SegmentTree::from(array.clone());
|
let segment_tree = SegmentTree::from(array.clone());
|
||||||
|
|
Loading…
Reference in New Issue