Commit db4b81f5 authored by Liam's avatar Liam Committed by GitHub
Browse files

Merge pull request #36 from msehnout/master

fish-like autosuggestions
parents a81a6386 4c41dc42
......@@ -213,6 +213,11 @@ impl Buffer {
self.push_action(act);
}
pub fn insert_from_buffer(&mut self, other: &Buffer) {
let start = self.data.len();
self.insert(start, &other.data[start..])
}
pub fn range(&self, start: usize, end: usize) -> String {
self.data[start..end].iter().cloned().collect()
}
......@@ -243,6 +248,18 @@ impl Buffer {
Ok(())
}
/// Takes other buffer, measures its length and prints this buffer from the point where
/// the other stopped.
/// Used to implement autosuggestions.
pub fn print_rest<W>(&self, out: &mut W, after: usize) -> io::Result<usize>
where W: Write
{
let string: String = self.data.iter().skip(after).cloned().collect();
out.write(string.as_bytes())?;
Ok(string.len())
}
fn remove_raw(&mut self, start: usize, end: usize) -> Vec<char> {
self.data.drain(start..end).collect()
}
......@@ -252,6 +269,24 @@ impl Buffer {
self.data.insert(start + i, c)
}
}
/// 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 {
let other_len = other.data.len();
let self_len = self.data.len();
if other.data.len() != 0 && self_len != other_len {
let match_let = self.data
.iter()
.zip(&other.data)
.take_while(|&(s, o)| *s == *o)
.collect::<Vec<_>>()
.len();
match_let == other_len
} else {
false
}
}
}
#[cfg(test)]
......@@ -390,4 +425,42 @@ mod tests {
assert_eq!(buf.redo(), true);
assert_eq!(String::from(buf), "defg");
}
#[test]
fn test_starts_with() {
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.starts_with(&buf2), true);
}
#[test]
fn test_does_not_start_with() {
let mut buf = Buffer::new();
buf.insert(0, &['a', 'b', 'c']);
let mut buf2 = Buffer::new();
buf2.insert(0, &['a', 'b', 'c']);
assert_eq!(buf.starts_with(&buf2), false);
}
#[test]
fn test_is_not_match2() {
let mut buf = Buffer::new();
buf.insert(0, &['a', 'b', 'c', 'd', 'e', 'f', 'g']);
let mut buf2 = Buffer::new();
buf2.insert(0, &['x', 'y', 'z']);
assert_eq!(buf.starts_with(&buf2), false);
}
#[test]
fn test_print_rest() {
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']);
let mut out: Vec<u8> = vec![];
buf.print_rest(&mut out, buf2.data.len()).unwrap();
assert_eq!(out.len(), 4);
}
}
use std::cmp;
use std::io::{self, Write};
use termion::{self, clear, cursor};
use termion::{self, clear, cursor, color};
use unicode_width::*;
use Context;
......@@ -82,6 +82,9 @@ pub struct Editor<'a, W: Write> {
// If this is true, on the next tab we print the completion list.
show_completions_hint: bool,
// Show autosuggestions based on history
show_autosuggestions: bool,
// if set, the cursor will not be allow to move one past the end of the line, this is necessary
// for Vi's normal mode.
pub no_eol: bool,
......@@ -126,6 +129,7 @@ impl<'a, W: Write> Editor<'a, W> {
cur_history_loc: None,
context: context,
show_completions_hint: false,
show_autosuggestions: true,
prompt_width: prompt_width,
term_cursor_line: 1,
no_eol: false,
......@@ -237,6 +241,7 @@ impl<'a, W: Write> Editor<'a, W> {
if let Some(ref completer) = self.context.completer {
let mut completions = completer.completions(word.as_ref());
completions.sort();
completions.dedup();
(word, completions)
} else {
return Ok(());
......@@ -527,6 +532,20 @@ impl<'a, W: Write> Editor<'a, W> {
cur_buf_mut!(self)
}
/// Accept autosuggestion and copy its content into current buffer
pub fn accept_autosuggestion(&mut self) -> io::Result<()> {
{
let hist_match = self.context.history
.get_first_match(self.cur_history_loc, self.current_buffer())
.cloned();
let mut buf = self.current_buffer_mut();
if let Some(hm) = hist_match {
buf.insert_from_buffer(&hm);
}
}
self.print_current_buffer(true)
}
/// Deletes the displayed prompt and buffer, replacing them with the current prompt and buffer
pub fn print_current_buffer(&mut self, move_cursor_to_end_of_line: bool) -> io::Result<()> {
let buf = cur_buf!(self);
......@@ -549,6 +568,17 @@ impl<'a, W: Write> Editor<'a, W> {
try!(write!(self.out, "\r{}{}", clear::AfterCursor, self.prompt));
try!(buf.print(&mut self.out));
// Display autosuggestion
if self.show_autosuggestions {
if let Some(hist_match) = self.context.history.get_first_match(self.cur_history_loc, buf) {
write!(self.out, "{}", color::Fg(color::Yellow))?;
let len = hist_match.print_rest(&mut self.out, buf.chars().len())?;
write!(self.out, "{}", color::Fg(color::Reset))?;
write!(self.out, "{}", cursor::Left(len as u16))?;
}
}
if new_prompt_and_buffer_width % w == 0 {
// at the end of the line, move the cursor down a line
try!(write!(self.out, "\r\n"));
......
......@@ -59,6 +59,20 @@ impl History {
ret
}
/// 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_first_match<'a, 'b>(&'a self, curr_position: Option<usize>, new_buff: &'b Buffer) -> Option<&'a Buffer> {
let pos = curr_position.unwrap_or(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)
}
}
}
None
}
/// Get the history file name.
pub fn file_name(&self) -> Option<&str> {
match self.file_name {
......
......@@ -18,6 +18,9 @@ pub trait KeyMap<'a, W: Write, T>: From<T> {
try!(self.editor().handle_newline());
done = true;
}
Key::Ctrl('f') => {
try!(self.editor().accept_autosuggestion());
}
_ => {
try!(self.handle_key_core(key));
self.editor().skip_completions_hint();
......
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