Add Knuth–Morris–Pratt to pager
parent
c9469f26ee
commit
1245eae0be
|
@ -1127,57 +1127,8 @@ easy to take MORE than nothing.'"#;
|
|||
println!("{}", l);
|
||||
}
|
||||
println!("");
|
||||
let text = 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
|
||||
book her sister was reading, but it had no pictures or conversations in
|
||||
it, ‘and what is the use of a book,’ thought Alice ‘without pictures or
|
||||
conversations?’
|
||||
|
||||
So she was considering in her own mind (as well as she could, for the
|
||||
hot day made her feel very sleepy and stupid), whether the pleasure
|
||||
of making a daisy-chain would be worth the trouble of getting up and
|
||||
picking the daisies, when suddenly a White Rabbit with pink eyes ran
|
||||
close by her.
|
||||
|
||||
>>There was nothing so VERY remarkable in that; nor did Alice think it so
|
||||
>>VERY much out of the way to hear the Rabbit say to itself, ‘Oh dear!
|
||||
>> Oh dear! I shall be late!’ (when she thought it over afterwards, it
|
||||
>>occurred to her that she ought to have wondered at this, but at the time
|
||||
>>it all seemed quite natural); but when the Rabbit actually TOOK A WATCH
|
||||
OUT OF ITS WAISTCOAT-POCKET, and looked at it, and then hurried on,
|
||||
>>Alice started to her feet, for it flashed across her mind that she had
|
||||
>>never before seen a rabbit with either a waistcoat-pocket, or a watch
|
||||
>>to take out of it, and burning with curiosity, she ran across the field
|
||||
after it, and fortunately was just in time to see it pop down a large
|
||||
rabbit-hole under the hedge.
|
||||
|
||||
In another moment down went Alice after it, never once considering how
|
||||
in the world she was to get out again.
|
||||
|
||||
The rabbit-hole went straight on like a tunnel for some way, and then
|
||||
dipped suddenly down, so suddenly that Alice had not a moment to think
|
||||
about stopping herself before she found herself falling down a very deep
|
||||
well.
|
||||
|
||||
Either the well was very deep, or she fell very slowly, for she had
|
||||
plenty of time as she went down to look about her and to wonder what was
|
||||
going to happen next. First, she tried to look down and make out what
|
||||
she was coming to, but it was too dark to see anything; then she
|
||||
looked at the sides of the well, and noticed that they were filled with
|
||||
cupboards and book-shelves; here and there she saw maps and pictures
|
||||
hung upon pegs. She took down a jar from one of the shelves as
|
||||
she passed; it was labelled ‘ORANGE MARMALADE’, but to her great
|
||||
disappointment it was empty: she did not like to drop the jar for fear
|
||||
of killing somebody, so managed to put it into one of the cupboards as
|
||||
she fell past it.
|
||||
|
||||
‘Well!’ thought Alice to herself, ‘after such a fall as this, I shall
|
||||
think nothing of tumbling down stairs! How brave they’ll all think me at
|
||||
home! Why, I wouldn’t say anything about it, even if I fell off the top
|
||||
of the house!’ (Which was very likely true.)"#;
|
||||
for l in split_lines_reflow(text, Reflow::FormatFlowed, Some(72)) {
|
||||
use super::_ALICE_CHAPTER_1;
|
||||
for l in split_lines_reflow(_ALICE_CHAPTER_1, Reflow::FormatFlowed, Some(72)) {
|
||||
println!("{}", l);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
pub mod grapheme_clusters;
|
||||
pub mod line_break;
|
||||
pub mod search;
|
||||
mod tables;
|
||||
mod types;
|
||||
pub use types::Reflow;
|
||||
|
@ -137,3 +138,54 @@ fn test_globmatch() {
|
|||
|
||||
assert!(!"INBOX/Lists/".matches_glob("INBOX/Lists/*"));
|
||||
}
|
||||
|
||||
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
|
||||
book her sister was reading, but it had no pictures or conversations in
|
||||
it, ‘and what is the use of a book,’ thought Alice ‘without pictures or
|
||||
conversations?’
|
||||
|
||||
So she was considering in her own mind (as well as she could, for the
|
||||
hot day made her feel very sleepy and stupid), whether the pleasure
|
||||
of making a daisy-chain would be worth the trouble of getting up and
|
||||
picking the daisies, when suddenly a White Rabbit with pink eyes ran
|
||||
close by her.
|
||||
|
||||
>>There was nothing so VERY remarkable in that; nor did Alice think it so
|
||||
>>VERY much out of the way to hear the Rabbit say to itself, ‘Oh dear!
|
||||
>> Oh dear! I shall be late!’ (when she thought it over afterwards, it
|
||||
>>occurred to her that she ought to have wondered at this, but at the time
|
||||
>>it all seemed quite natural); but when the Rabbit actually TOOK A WATCH
|
||||
OUT OF ITS WAISTCOAT-POCKET, and looked at it, and then hurried on,
|
||||
>>Alice started to her feet, for it flashed across her mind that she had
|
||||
>>never before seen a rabbit with either a waistcoat-pocket, or a watch
|
||||
>>to take out of it, and burning with curiosity, she ran across the field
|
||||
after it, and fortunately was just in time to see it pop down a large
|
||||
rabbit-hole under the hedge.
|
||||
|
||||
In another moment down went Alice after it, never once considering how
|
||||
in the world she was to get out again.
|
||||
|
||||
The rabbit-hole went straight on like a tunnel for some way, and then
|
||||
dipped suddenly down, so suddenly that Alice had not a moment to think
|
||||
about stopping herself before she found herself falling down a very deep
|
||||
well.
|
||||
|
||||
Either the well was very deep, or she fell very slowly, for she had
|
||||
plenty of time as she went down to look about her and to wonder what was
|
||||
going to happen next. First, she tried to look down and make out what
|
||||
she was coming to, but it was too dark to see anything; then she
|
||||
looked at the sides of the well, and noticed that they were filled with
|
||||
cupboards and book-shelves; here and there she saw maps and pictures
|
||||
hung upon pegs. She took down a jar from one of the shelves as
|
||||
she passed; it was labelled ‘ORANGE MARMALADE’, but to her great
|
||||
disappointment it was empty: she did not like to drop the jar for fear
|
||||
of killing somebody, so managed to put it into one of the cupboards as
|
||||
she fell past it.
|
||||
|
||||
‘Well!’ thought Alice to herself, ‘after such a fall as this, I shall
|
||||
think nothing of tumbling down stairs! How brave they’ll all think me at
|
||||
home! Why, I wouldn’t say anything about it, even if I fell off the top
|
||||
of the house!’ (Which was very likely true.)"#;
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* meli - text_processing mod.
|
||||
*
|
||||
* Copyright 2020 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use super::TextProcessing;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub trait KMP: TextProcessing {
|
||||
fn kmp_search(&self, pattern: &str) -> SmallVec<[usize; 256]> {
|
||||
let w = pattern.split_graphemes();
|
||||
let t = 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();
|
||||
|
||||
let s = self.graphemes_indices();
|
||||
while j < s.len() && k < w.len() as i32 {
|
||||
if w[k as usize] == s[j].1 {
|
||||
j += 1;
|
||||
k += 1;
|
||||
if k as usize == w.len() {
|
||||
ret.push(s[j - (k as usize)].0);
|
||||
k = t[k as usize];
|
||||
}
|
||||
} else {
|
||||
k = t[k as usize];
|
||||
if k < 0 {
|
||||
j += 1;
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
for ind in _ALICE_CHAPTER_1.kmp_search("Alice") {
|
||||
println!(
|
||||
"{:#?}",
|
||||
&_ALICE_CHAPTER_1
|
||||
[ind.saturating_sub(0)..std::cmp::min(_ALICE_CHAPTER_1.len(), ind + 25)]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -90,6 +90,7 @@ pub struct MailView {
|
|||
pager: Pager,
|
||||
subview: Option<Box<dyn Component>>,
|
||||
dirty: bool,
|
||||
initialised: bool,
|
||||
mode: ViewMode,
|
||||
expand_headers: bool,
|
||||
headers_no: usize,
|
||||
|
@ -133,6 +134,7 @@ impl MailView {
|
|||
pager: pager.unwrap_or_default(),
|
||||
subview,
|
||||
dirty: true,
|
||||
initialised: false,
|
||||
mode: ViewMode::Normal,
|
||||
expand_headers: false,
|
||||
|
||||
|
@ -343,6 +345,7 @@ impl MailView {
|
|||
pub fn update(&mut self, new_coordinates: (usize, FolderHash, EnvelopeHash)) {
|
||||
self.coordinates = new_coordinates;
|
||||
self.mode = ViewMode::Normal;
|
||||
self.initialised = false;
|
||||
self.set_dirty(true);
|
||||
}
|
||||
}
|
||||
|
@ -587,7 +590,8 @@ impl Component for MailView {
|
|||
}
|
||||
};
|
||||
|
||||
if self.dirty {
|
||||
if !self.initialised {
|
||||
self.initialised = true;
|
||||
let body = {
|
||||
let account = &mut context.accounts[self.coordinates.0];
|
||||
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
|
||||
|
@ -595,7 +599,6 @@ impl Component for MailView {
|
|||
match envelope.body(op) {
|
||||
Ok(body) => body,
|
||||
Err(e) => {
|
||||
self.dirty = false;
|
||||
clear_area(
|
||||
grid,
|
||||
(set_y(upper_left, y), bottom_right),
|
||||
|
@ -626,10 +629,12 @@ impl Component for MailView {
|
|||
let attachment = &body.attachments()[aidx];
|
||||
self.subview = Some(Box::new(HtmlView::new(&attachment, context)));
|
||||
self.mode = ViewMode::Subview;
|
||||
self.initialised = false;
|
||||
}
|
||||
ViewMode::Normal if body.is_html() => {
|
||||
self.subview = Some(Box::new(HtmlView::new(&body, context)));
|
||||
self.mode = ViewMode::Subview;
|
||||
self.initialised = false;
|
||||
}
|
||||
ViewMode::Normal
|
||||
if context
|
||||
|
@ -659,6 +664,7 @@ impl Component for MailView {
|
|||
context,
|
||||
)));
|
||||
self.mode = ViewMode::Subview;
|
||||
self.initialised = false;
|
||||
}
|
||||
ViewMode::Subview | ViewMode::ContactSelector(_) => {}
|
||||
ViewMode::Source(source) => {
|
||||
|
@ -787,6 +793,7 @@ impl Component for MailView {
|
|||
}
|
||||
}
|
||||
self.mode = ViewMode::Normal;
|
||||
self.initialised = false;
|
||||
return true;
|
||||
}
|
||||
(ViewMode::ContactSelector(ref mut s), _) => {
|
||||
|
@ -879,6 +886,7 @@ impl Component for MailView {
|
|||
context,
|
||||
));
|
||||
self.dirty = true;
|
||||
self.initialised = false;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt(''))
|
||||
|
@ -886,6 +894,7 @@ impl Component for MailView {
|
|||
{
|
||||
self.mode = ViewMode::Normal;
|
||||
self.set_dirty(true);
|
||||
self.initialised = false;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt('')) if !self.cmd_buf.is_empty() => {
|
||||
|
@ -916,6 +925,7 @@ impl Component for MailView {
|
|||
_ => ViewMode::Source(Source::Decoded),
|
||||
};
|
||||
self.set_dirty(true);
|
||||
self.initialised = false;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
|
@ -931,6 +941,7 @@ impl Component for MailView {
|
|||
{
|
||||
self.mode = ViewMode::Normal;
|
||||
self.set_dirty(true);
|
||||
self.initialised = false;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
|
@ -1052,6 +1063,7 @@ impl Component for MailView {
|
|||
|
||||
ContentType::Text { .. } | ContentType::PGPSignature => {
|
||||
self.mode = ViewMode::Attachment(lidx);
|
||||
self.initialised = false;
|
||||
self.dirty = true;
|
||||
}
|
||||
ContentType::Multipart { .. } => {
|
||||
|
@ -1091,6 +1103,7 @@ impl Component for MailView {
|
|||
{
|
||||
let raw_buf = RawBuffer::new(buf, name_opt);
|
||||
self.mode = ViewMode::Ansi(raw_buf);
|
||||
self.initialised = false;
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
|
@ -1227,6 +1240,7 @@ impl Component for MailView {
|
|||
ViewMode::Url => self.mode = ViewMode::Normal,
|
||||
_ => {}
|
||||
}
|
||||
self.initialised = false;
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -276,6 +276,14 @@ pub enum PageMovement {
|
|||
End,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct SearchPattern {
|
||||
pattern: String,
|
||||
positions: Vec<(usize, usize)>,
|
||||
cursor: usize,
|
||||
movement: Option<PageMovement>,
|
||||
}
|
||||
|
||||
/// A pager for text.
|
||||
/// `Pager` holds its own content in its own `CellBuffer` and when `draw` is called, it draws the
|
||||
/// current view of the text. It is responsible for scrolling etc.
|
||||
|
@ -287,6 +295,7 @@ pub struct Pager {
|
|||
height: usize,
|
||||
width: usize,
|
||||
minimum_width: usize,
|
||||
search: Option<SearchPattern>,
|
||||
dirty: bool,
|
||||
|
||||
colors: ThemeAttribute,
|
||||
|
@ -530,7 +539,40 @@ impl Component for Pager {
|
|||
empty_cell.set_bg(self.colors.bg);
|
||||
let mut content = CellBuffer::new(width, height, empty_cell);
|
||||
content.set_ascii_drawing(self.content.ascii_drawing);
|
||||
if let Some(ref mut search) = self.search {
|
||||
use melib::text_processing::search::KMP;
|
||||
search.positions.clear();
|
||||
for (y, l) in lines.iter().enumerate() {
|
||||
search.positions.extend(
|
||||
l.kmp_search(&search.pattern)
|
||||
.into_iter()
|
||||
.map(|offset| (y, offset)),
|
||||
);
|
||||
}
|
||||
}
|
||||
Pager::print_string(&mut content, lines, self.colors);
|
||||
if let Some(ref mut search) = self.search {
|
||||
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 content.row_iter(*x..*x + search.pattern.grapheme_len(), *y) {
|
||||
if i == search.cursor {
|
||||
content[c]
|
||||
.set_fg(results_current_attr.fg)
|
||||
.set_bg(results_current_attr.bg)
|
||||
.set_attrs(results_current_attr.attrs);
|
||||
} else {
|
||||
content[c]
|
||||
.set_fg(results_attr.fg)
|
||||
.set_bg(results_attr.bg)
|
||||
.set_attrs(results_attr.attrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.content = content;
|
||||
self.height = height;
|
||||
self.width = width;
|
||||
|
@ -580,6 +622,25 @@ impl Component for Pager {
|
|||
if self.height == 0 || self.width == 0 {
|
||||
return;
|
||||
}
|
||||
if let Some(ref mut search) = self.search {
|
||||
if !search.positions.is_empty() {
|
||||
if let Some(mvm) = search.movement.take() {
|
||||
match mvm {
|
||||
PageMovement::Up(_) => {
|
||||
if self.cursor.1 > search.positions[search.cursor].0 {
|
||||
self.cursor.1 = search.positions[search.cursor].0;
|
||||
}
|
||||
}
|
||||
PageMovement::Down(_) => {
|
||||
if self.cursor.1 + height < search.positions[search.cursor].0 {
|
||||
self.cursor.1 = search.positions[search.cursor].0;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
||||
let (width, height) = self.content.size();
|
||||
|
@ -694,6 +755,49 @@ impl Component for Pager {
|
|||
))));
|
||||
return true;
|
||||
}
|
||||
UIEvent::Action(Action::Listing(ListingAction::Filter(pattern))) => {
|
||||
self.search = Some(SearchPattern {
|
||||
pattern: pattern.to_string(),
|
||||
positions: vec![],
|
||||
cursor: 0,
|
||||
movement: None,
|
||||
});
|
||||
self.initialised = false;
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(Key::Char('n')) if self.search.is_some() => {
|
||||
if let Some(ref mut search) = self.search {
|
||||
search.movement = Some(PageMovement::Down(1));
|
||||
search.cursor += 1;
|
||||
} else {
|
||||
unsafe {
|
||||
std::hint::unreachable_unchecked();
|
||||
}
|
||||
}
|
||||
self.initialised = false;
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(Key::Char('N')) if self.search.is_some() => {
|
||||
if let Some(ref mut search) = self.search {
|
||||
search.movement = Some(PageMovement::Up(1));
|
||||
search.cursor = search.cursor.saturating_sub(1);
|
||||
} else {
|
||||
unsafe {
|
||||
std::hint::unreachable_unchecked();
|
||||
}
|
||||
}
|
||||
self.initialised = false;
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(Key::Esc) if self.search.is_some() => {
|
||||
self.search = None;
|
||||
self.initialised = false;
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
UIEvent::Resize => {
|
||||
self.initialised = false;
|
||||
self.dirty = true;
|
||||
|
|
|
@ -195,6 +195,8 @@ const DEFAULT_KEYS: &'static [&'static str] = &[
|
|||
"mail.view.body",
|
||||
"mail.listing.attachment_flag",
|
||||
"mail.listing.thread_snooze_flag",
|
||||
"pager.highlight_search",
|
||||
"pager.highlight_search_current",
|
||||
];
|
||||
|
||||
/// `ThemeAttributeInner` but with the links resolved.
|
||||
|
@ -717,6 +719,8 @@ impl Default for Theme {
|
|||
}
|
||||
);
|
||||
|
||||
add!("pager.highlight_search", light = { fg: Color::White, bg: Color::Byte(6) /* Teal */, attrs: Attr::Bold }, dark = { fg: Color::White, bg: Color::Byte(6) /* Teal */, attrs: Attr::Bold });
|
||||
add!("pager.highlight_search_current", light = { fg: Color::White, bg: Color::Byte(17) /* NavyBlue */, attrs: Attr::Bold }, dark = { fg: Color::White, bg: Color::Byte(17) /* NavyBlue */, attrs: Attr::Bold });
|
||||
Theme {
|
||||
light,
|
||||
dark,
|
||||
|
|
Loading…
Reference in New Issue