diff --git a/melib/src/text_processing/line_break.rs b/melib/src/text_processing/line_break.rs
index c3209f6fb..97864bd25 100644
--- a/melib/src/text_processing/line_break.rs
+++ b/melib/src/text_processing/line_break.rs
@@ -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);
}
}
diff --git a/melib/src/text_processing/mod.rs b/melib/src/text_processing/mod.rs
index 77efca68c..228316f88 100644
--- a/melib/src/text_processing/mod.rs
+++ b/melib/src/text_processing/mod.rs
@@ -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.)"#;
diff --git a/melib/src/text_processing/search.rs b/melib/src/text_processing/search.rs
new file mode 100644
index 000000000..5220ce81a
--- /dev/null
+++ b/melib/src/text_processing/search.rs
@@ -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 .
+ */
+
+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)]
+ );
+ }
+}
diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs
index 6c699835e..d25655d71 100644
--- a/src/components/mail/view.rs
+++ b/src/components/mail/view.rs
@@ -90,6 +90,7 @@ pub struct MailView {
pager: Pager,
subview: Option>,
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;
}
diff --git a/src/components/utilities.rs b/src/components/utilities.rs
index 59745e63c..2b266c4fb 100644
--- a/src/components/utilities.rs
+++ b/src/components/utilities.rs
@@ -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,
+}
+
/// 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,
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;
diff --git a/src/conf/themes.rs b/src/conf/themes.rs
index 5f9939570..ab5532db0 100644
--- a/src/conf/themes.rs
+++ b/src/conf/themes.rs
@@ -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,