mirror of
https://git.meli.delivery/meli/meli
synced 2024-11-19 03:25:38 +00:00
Implement search for CellBuffer
This commit is contained in:
parent
4ac52d9d5b
commit
ac71d627f1
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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]
|
||||
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());
|
||||
|
Loading…
Reference in New Issue
Block a user