Commit 741f36fb authored by Michael Aaron Murphy's avatar Michael Aaron Murphy

Merge branch 'master' into 'master'

Incremental search and history browse change.

Closes #2

See merge request redox-os/liner!10
parents 0ccda02d 9a569d15
......@@ -223,6 +223,12 @@ impl Buffer {
self.insert(start, &other.data[start..])
}
pub fn copy_buffer(&mut self, other: &Buffer) {
let data_len = self.data.len();
self.remove(0, data_len);
self.insert(0, &other.data[0..])
}
pub fn range(&self, start: usize, end: usize) -> String {
self.data[start..end].iter().cloned().collect()
}
......@@ -287,6 +293,11 @@ impl Buffer {
}
}
/// Check if the other buffer has the same content as this one.
pub fn equals(&self, other: &Buffer) -> bool {
self.data == other.data
}
/// Check if the other buffer starts with the same content as this one.
/// Used to implement autosuggestions.
pub fn starts_with(&self, other: &Buffer) -> bool {
......@@ -304,6 +315,16 @@ impl Buffer {
}
}
/// Check if the buffer contains pattern.
/// Used to implement history search.
pub fn contains(&self, pattern: &Buffer) -> bool {
let search_term: &[char] = &pattern.data;
if search_term.is_empty() {
return false;
}
self.data.windows(search_term.len()).any(|window| window == search_term)
}
/// Return true if the buffer is empty.
pub fn is_empty(&self) -> bool {
self.data.is_empty()
......@@ -474,6 +495,33 @@ mod tests {
assert_eq!(buf.starts_with(&buf2), false);
}
#[test]
fn test_contains() {
let mut buf = Buffer::new();
buf.insert(0, &['a', 'b', 'c', 'd', 'e', 'f', 'g']);
let mut buf2 = Buffer::new();
buf2.insert(0, &['a', 'b', 'c']);
assert_eq!(buf.contains(&buf2), true);
let mut buf2 = Buffer::new();
buf2.insert(0, &['c', 'd', 'e']);
assert_eq!(buf.contains(&buf2), true);
let mut buf2 = Buffer::new();
buf2.insert(0, &['e', 'f', 'g']);
assert_eq!(buf.contains(&buf2), true);
}
#[test]
fn test_does_not_contain() {
let mut buf = Buffer::new();
buf.insert(0, &['a', 'b', 'c', 'd', 'e', 'f', 'g']);
let mut buf2 = Buffer::new();
buf2.insert(0, &['x', 'b', 'c']);
assert_eq!(buf.contains(&buf2), false);
let mut buf2 = Buffer::new();
buf2.insert(0, &['a', 'b', 'd']);
assert_eq!(buf.contains(&buf2), false);
}
#[test]
fn test_print_rest() {
let mut buf = Buffer::new();
......
This diff is collapsed.
......@@ -108,22 +108,49 @@ impl History {
});
}
/// Go through the history and try to find a buffer which starts the same as the new buffer
/// given to this function as argument.
pub fn get_newest_match<'a, 'b>(
&'a self,
curr_position: Option<usize>,
new_buff: &'b Buffer,
) -> Option<&'a Buffer> {
fn get_match<I>(&self, vals: I, search_term: &Buffer) -> Option<usize>
where I: Iterator<Item = usize>
{
vals.filter_map(|i| self.buffers.get(i).map(|t| (i, t)))
.filter(|(_i, tested)| tested.starts_with(search_term))
.next().map(|(i, _)| i)
}
/// Go through the history and try to find an index (newest to oldest) which starts the same
/// as the new buffer given to this function as argument. Starts at curr_position. Does no wrap.
pub fn get_newest_match(&self, curr_position: Option<usize>, new_buff: &Buffer, ) -> Option<usize> {
let pos = curr_position.unwrap_or_else(|| self.buffers.len());
for iter in (0..pos).rev() {
if let Some(tested) = self.buffers.get(iter) {
if tested.starts_with(new_buff) {
return self.buffers.get(iter);
if pos > 0 {
self.get_match((0..pos).rev(), new_buff)
} else {
None
}
}
pub fn get_history_subset(&self, search_term: &Buffer) -> Vec<usize> {
let mut v: Vec<usize> = Vec::new();
let mut ret: Vec<usize> = (0..self.len()).filter(|i| {
if let Some(tested) = self.buffers.get(*i) {
let starts = tested.starts_with(search_term);
let contains = tested.contains(search_term);
if starts {
v.push(*i);
}
if contains && !starts && !tested.equals(search_term) {
return true;
}
}
}
None
return false;
}).collect();
ret.append(&mut v);
ret
}
pub fn search_index(&self, search_term: &Buffer) -> Vec<usize>
{
(0..self.len()).filter_map(|i| self.buffers.get(i).map(|t| (i, t)))
.filter(|(_i, tested)| tested.contains(search_term))
.map(|(i, _)| i).collect()
}
/// Get the history file name.
......
......@@ -37,6 +37,12 @@ pub trait KeyMap<'a, W: Write, T>: From<T> {
Key::Ctrl('f') if self.editor().is_currently_showing_autosuggestion() => {
self.editor_mut().accept_autosuggestion()?;
}
Key::Ctrl('r') => {
self.editor_mut().search(false)?;
}
Key::Ctrl('s') => {
self.editor_mut().search(true)?;
}
Key::Right if self.editor().is_currently_showing_autosuggestion() &&
self.editor().cursor_is_at_end_of_line() => {
self.editor_mut().accept_autosuggestion()?;
......
......@@ -1202,11 +1202,12 @@ mod tests {
]);
assert_eq!(map.ed.cursor(), 0);
}
#[test]
fn vi_normal_history_cursor_eol() {
let mut context = Context::new();
context.history.push("history".into()).unwrap();
context.history.push("history".into()).unwrap();
context.history.push("data hostory".into()).unwrap();
context.history.push("data history".into()).unwrap();
let out = Vec::new();
let ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut map = Vi::new(ed);
......@@ -1214,11 +1215,69 @@ mod tests {
assert_eq!(map.ed.cursor(), 4);
simulate_keys!(map, [Up]);
assert_eq!(map.ed.cursor(), 7);
assert_eq!(map.ed.cursor(), 12);
// in normal mode, make sure we don't end up past the last char
simulate_keys!(map, [Ctrl('['), Up]);
assert_eq!(map.ed.cursor(), 6);
assert_eq!(map.ed.cursor(), 11);
}
#[test]
fn vi_normal_history() {
let mut context = Context::new();
context.history.push("data second".into()).unwrap();
context.history.push("skip1".into()).unwrap();
context.history.push("data one".into()).unwrap();
context.history.push("skip2".into()).unwrap();
let out = Vec::new();
let ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut map = Vi::new(ed);
map.ed.insert_str_after_cursor("data").unwrap();
assert_eq!(map.ed.cursor(), 4);
simulate_keys!(map, [Up]);
assert_eq!(map.ed.cursor(), 8);
// in normal mode, make sure we don't end up past the last char
simulate_keys!(map, [Ctrl('['), Char('k')]);
assert_eq!(map.ed.cursor(), 10);
}
#[test]
fn vi_search_history() {
// Test incremental search as well as vi binding in search mode.
let mut context = Context::new();
context.history.push("data pat second".into()).unwrap();
context.history.push("skip1".into()).unwrap();
context.history.push("data pat one".into()).unwrap();
context.history.push("skip2".into()).unwrap();
let out = Vec::new();
let ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut map = Vi::new(ed);
map.ed.insert_str_after_cursor("pat").unwrap();
assert_eq!(map.ed.cursor(), 3);
simulate_keys!(map, [Ctrl('r'), Right]);
assert_eq!(map.ed.cursor(), 12);
//simulate_keys!(map, [Ctrl('['), Char('u'), Char('i')]);
map.ed.delete_all_before_cursor().unwrap();
assert_eq!(map.ed.cursor(), 0);
//map.ed.insert_str_after_cursor("pat").unwrap();
//assert_eq!(map.ed.cursor(), 3);
simulate_keys!(map, [Ctrl('r'), Char('p'), Char('a'), Char('t'), Ctrl('['), Char('k'), Ctrl('f')]);
assert_eq!(map.ed.cursor(), 14);
simulate_keys!(map, [Ctrl('['), Char('u'), Char('i')]);
assert_eq!(map.ed.cursor(), 0);
simulate_keys!(map, [Ctrl('s'), Char('p'), Char('a'), Char('t'), Ctrl('f')]);
assert_eq!(map.ed.cursor(), 15);
map.ed.delete_all_before_cursor().unwrap();
assert_eq!(map.ed.cursor(), 0);
map.ed.insert_str_after_cursor("pat").unwrap();
assert_eq!(map.ed.cursor(), 3);
simulate_keys!(map, [Ctrl('s'), Ctrl('['), Char('j'), Right]);
assert_eq!(map.ed.cursor(), 11);
}
#[test]
......@@ -3268,9 +3327,10 @@ mod tests {
Char('u'),
Char('u'),
Char('u'),
Char('2'),
Ctrl('r'),
]);
// Ctrl-r taken by incremental search so do this manually.
map.ed.redo().unwrap();
map.ed.redo().unwrap();
assert_eq!(String::from(map), "abcde");
}
......@@ -3347,7 +3407,7 @@ mod tests {
/// test undo in groups
fn undo_insert_with_history() {
let mut context = Context::new();
context.history.push(Buffer::from("")).unwrap();
context.history.push(Buffer::from("insert something")).unwrap();
let out = Vec::new();
let ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
let mut map = Vi::new(ed);
......
......@@ -126,7 +126,7 @@ fn test_reading_from_file() {
f.write_all(TEXT.as_bytes()).unwrap();
}
let mut h = History::new();
h.set_file_name_and_load_history(tmp_file);
h.set_file_name_and_load_history(tmp_file).unwrap();
assert_eq!(String::from(h.buffers[0].clone()), "a".to_string());
assert_eq!(String::from(h.buffers[1].clone()), "b".to_string());
assert_eq!(String::from(h.buffers[2].clone()), "c".to_string());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment