Commit ce3351b7 authored by AdminXVII's avatar AdminXVII

Merge branch 'feature/complete_bugs' into 'master'

Feature/complete bugs

See merge request redox-os/liner!12
parents ede738c0 32059cc3
use unicode_width::UnicodeWidthStr;
use std::fmt::{self, Write as FmtWrite};
use std::io::{self, Write};
use std::iter::FromIterator;
use std::fmt::{self, Write as FmtWrite};
use unicode_width::UnicodeWidthStr;
/// A modification performed on a `Buffer`. These are used for the purpose of undo/redo.
#[derive(Debug,Clone)]
#[derive(Debug, Clone)]
pub enum Action {
Insert { start: usize, text: Vec<char> },
Remove { start: usize, text: Vec<char> },
......@@ -44,6 +44,13 @@ pub struct Buffer {
undone_actions: Vec<Action>,
}
impl PartialEq for Buffer {
fn eq(&self, other: &Self) -> bool {
self.data == other.data
}
}
impl Eq for Buffer {}
impl From<Buffer> for String {
fn from(buf: Buffer) -> Self {
String::from_iter(buf.data)
......@@ -172,7 +179,10 @@ impl Buffer {
}
pub fn last_arg(&self) -> Option<&[char]> {
self.data.split(|&c| c == ' ').filter(|s| !s.is_empty()).last()
self.data
.split(|&c| c == ' ')
.filter(|s| !s.is_empty())
.last()
}
pub fn num_chars(&self) -> usize {
......@@ -242,11 +252,17 @@ impl Buffer {
}
pub fn range_width(&self, start: usize, end: usize) -> Vec<usize> {
self.range(start, end).split('\n').map(|s| s.width()).collect()
self.range(start, end)
.split('\n')
.map(|s| s.width())
.collect()
}
pub fn lines(&self) -> Vec<String> {
self.data.split(|&c| c == '\n').map(|s| s.iter().cloned().collect()).collect()
self.data
.split(|&c| c == '\n')
.map(|s| s.iter().cloned().collect())
.collect()
}
pub fn chars(&self) -> ::std::slice::Iter<char> {
......@@ -259,7 +275,8 @@ impl Buffer {
}
pub fn print<W>(&self, out: &mut W) -> io::Result<()>
where W: Write
where
W: Write,
{
let string: String = self.data.iter().cloned().collect();
out.write_all(string.as_bytes())
......@@ -275,7 +292,8 @@ impl Buffer {
/// the other stopped.
/// Used to implement autosuggestions.
pub fn print_rest<W>(&self, out: &mut W, after: usize) -> io::Result<usize>
where W: Write
where
W: Write,
{
let string: String = self.data.iter().skip(after).cloned().collect();
out.write_all(string.as_bytes())?;
......@@ -293,18 +311,14 @@ 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 {
let other_len = other.data.len();
let self_len = self.data.len();
if ! other.data.is_empty() && self_len != other_len {
let match_let = self.data
if !other.data.is_empty() && self_len != other_len {
let match_let = self
.data
.iter()
.zip(&other.data)
.take_while(|&(s, o)| *s == *o)
......@@ -322,7 +336,9 @@ impl Buffer {
if search_term.is_empty() {
return false;
}
self.data.windows(search_term.len()).any(|window| window == search_term)
self.data
.windows(search_term.len())
.any(|window| window == search_term)
}
/// Return true if the buffer is empty.
......
......@@ -31,12 +31,24 @@ impl Completer for BasicCompleter {
pub struct FilenameCompleter {
working_dir: Option<PathBuf>,
case_sensitive: bool,
}
impl FilenameCompleter {
pub fn new<T: Into<PathBuf>>(working_dir: Option<T>) -> Self {
FilenameCompleter {
working_dir: working_dir.map(|p| p.into()),
case_sensitive: true,
}
}
pub fn with_case_sensitivity<T: Into<PathBuf>>(
working_dir: Option<T>,
case_sensitive: bool,
) -> Self {
FilenameCompleter {
working_dir: working_dir.map(|p| p.into()),
case_sensitive,
}
}
}
......@@ -77,7 +89,13 @@ impl Completer for FilenameCompleter {
&& !full_path.ends_with("..") =>
{
p = parent;
start_name = full_path.file_name().unwrap().to_string_lossy();
start_name = if self.case_sensitive {
full_path.file_name().unwrap().to_string_lossy()
} else {
let sn = full_path.file_name().unwrap().to_string_lossy();
sn.to_lowercase();
sn
};
completing_dir = false;
}
_ => {
......@@ -100,7 +118,11 @@ impl Completer for FilenameCompleter {
Err(_) => continue,
};
let file_name = dir.file_name();
let file_name = file_name.to_string_lossy();
let file_name = if self.case_sensitive {
file_name.to_string_lossy().to_string()
} else {
file_name.to_string_lossy().to_lowercase()
};
if start_name.is_empty() || file_name.starts_with(&*start_name) {
let mut a = start_path.clone();
......
......@@ -106,6 +106,10 @@ pub struct Editor<'a, W: Write> {
// Buffer for the new line (ie. not from editing history)
new_buf: Buffer,
// Buffer to use when editing history so we do not overwrite it.
hist_buf: Buffer,
hist_buf_valid: bool,
// None if we're on the new buffer, else the index of history
cur_history_loc: Option<usize>,
......@@ -134,26 +138,30 @@ pub struct Editor<'a, W: Write> {
history_subset_loc: Option<usize>,
autosuggestion: Option<Buffer>,
history_fresh: bool,
}
macro_rules! cur_buf_mut {
($s:expr) => {
($s:expr) => {{
$s.buffer_changed = true;
match $s.cur_history_loc {
Some(i) => {
$s.buffer_changed = true;
&mut $s.context.history[i]
}
_ => {
$s.buffer_changed = true;
&mut $s.new_buf
if !$s.hist_buf_valid {
$s.hist_buf.copy_buffer(&$s.context.history[i]);
$s.hist_buf_valid = true;
}
&mut $s.hist_buf
}
_ => &mut $s.new_buf,
}
};
}};
}
macro_rules! cur_buf {
($s:expr) => {
match $s.cur_history_loc {
Some(_) if $s.hist_buf_valid => &$s.hist_buf,
Some(i) => &$s.context.history[i],
_ => &$s.new_buf,
}
......@@ -183,6 +191,8 @@ impl<'a, W: Write> Editor<'a, W> {
out: out,
closure: f,
new_buf: buffer.into(),
hist_buf: Buffer::new(),
hist_buf_valid: false,
cur_history_loc: None,
context: context,
show_completions_hint: None,
......@@ -196,6 +206,7 @@ impl<'a, W: Write> Editor<'a, W> {
history_subset_index: vec![],
history_subset_loc: None,
autosuggestion: None,
history_fresh: false,
};
if !ed.new_buf.is_empty() {
......@@ -242,6 +253,7 @@ impl<'a, W: Write> Editor<'a, W> {
// XXX: Returning a bool to indicate doneness is a bit awkward, maybe change it
pub fn handle_newline(&mut self) -> io::Result<bool> {
self.history_fresh = false;
if self.is_search() {
self.accept_autosuggestion()?;
}
......@@ -275,6 +287,13 @@ impl<'a, W: Write> Editor<'a, W> {
}
}
fn freshen_history(&mut self) {
if self.context.history.share && !self.history_fresh {
let _ = self.context.history.load_history(false);
self.history_fresh = true;
}
}
/// Refresh incremental search, either when started or when the buffer changes.
fn refresh_search(&mut self, forward: bool) {
let search_history_loc = self.search_history_loc();
......@@ -304,6 +323,7 @@ impl<'a, W: Write> Editor<'a, W> {
self.reverse_search = !forward;
self.forward_search = forward;
self.cur_history_loc = None;
self.hist_buf_valid = false;
self.no_newline = true;
self.buffer_changed = false;
}
......@@ -314,6 +334,7 @@ impl<'a, W: Write> Editor<'a, W> {
/// search with forward changed (i.e. reverse search direction for one result).
pub fn search(&mut self, forward: bool) -> io::Result<()> {
if !self.is_search() {
self.freshen_history();
self.refresh_search(forward);
} else if self.history_subset_index.len() > 0 {
self.history_subset_loc = if let Some(p) = self.history_subset_loc {
......@@ -430,10 +451,16 @@ impl<'a, W: Write> Editor<'a, W> {
pub fn complete<T: Completer>(&mut self, handler: &mut T) -> io::Result<()> {
handler.on_event(Event::new(self, EventKind::BeforeComplete));
if let Some((completions, i)) = self.show_completions_hint.take() {
let i = i.map_or(0, |i| (i + 1) % completions.len());
if let Some((completions, i_in)) = self.show_completions_hint.take() {
let i = i_in.map_or(0, |i| (i + 1) % completions.len());
self.delete_word_before_cursor(false)?;
match i_in {
Some(x) if cur_buf!(self) == &Buffer::from(&completions[x][..]) => {
cur_buf_mut!(self).truncate(0);
self.cursor = 0;
}
_ => self.delete_word_before_cursor(false)?,
}
self.insert_str_after_cursor(&completions[i])?;
self.show_completions_hint = Some((completions, Some(i)));
......@@ -550,6 +577,8 @@ impl<'a, W: Write> Editor<'a, W> {
if self.is_search() {
self.search(false)
} else {
self.hist_buf_valid = false;
self.freshen_history();
if self.new_buf.num_chars() > 0 {
match self.history_subset_loc {
Some(i) if i > 0 => {
......@@ -586,6 +615,7 @@ impl<'a, W: Write> Editor<'a, W> {
if self.is_search() {
self.search(true)
} else {
self.hist_buf_valid = false;
if self.new_buf.num_chars() > 0 {
if let Some(i) = self.history_subset_loc {
if i < self.history_subset_index.len() - 1 {
......@@ -595,6 +625,7 @@ impl<'a, W: Write> Editor<'a, W> {
self.cur_history_loc = None;
self.history_subset_loc = None;
self.history_subset_index.clear();
self.history_fresh = false;
}
}
} else {
......@@ -602,7 +633,7 @@ impl<'a, W: Write> Editor<'a, W> {
Some(i) if i < self.context.history.len() - 1 => {
self.cur_history_loc = Some(i + 1)
}
_ => (),
_ => self.history_fresh = false,
}
}
self.move_cursor_to_end_of_line()
......@@ -611,6 +642,7 @@ impl<'a, W: Write> Editor<'a, W> {
/// Moves to the start of history (ie. the earliest history entry).
pub fn move_to_start_of_history(&mut self) -> io::Result<()> {
self.hist_buf_valid = false;
if self.context.history.len() > 0 {
self.cur_history_loc = Some(0);
self.move_cursor_to_end_of_line()
......@@ -623,6 +655,7 @@ impl<'a, W: Write> Editor<'a, W> {
/// Moves to the end of history (ie. the new buffer).
pub fn move_to_end_of_history(&mut self) -> io::Result<()> {
self.hist_buf_valid = false;
if self.cur_history_loc.is_some() {
self.cur_history_loc = None;
self.move_cursor_to_end_of_line()
......@@ -779,7 +812,6 @@ impl<'a, W: Write> Editor<'a, W> {
/// Moves the cursor to the end of the line.
pub fn move_cursor_to_end_of_line(&mut self) -> io::Result<()> {
//self.clear_search();
self.cursor = cur_buf!(self).num_chars();
self.no_newline = true;
self.display()
......@@ -828,6 +860,10 @@ impl<'a, W: Write> Editor<'a, W> {
/// searching the first history entry to start with current text (reverse order).
/// Return None if nothing found.
fn current_autosuggestion(&mut self) -> Option<Buffer> {
// If we are editing a previous history item no autosuggestion.
if self.hist_buf_valid {
return None;
}
let context_history = &self.context.history;
let autosuggestion = if self.is_search() {
self.search_history_loc().map(|i| &context_history[i])
......@@ -1088,7 +1124,13 @@ impl<'a, W: Write> Editor<'a, W> {
impl<'a, W: Write> From<Editor<'a, W>> for String {
fn from(ed: Editor<'a, W>) -> String {
match ed.cur_history_loc {
Some(i) => ed.context.history[i].clone(),
Some(i) => {
if ed.hist_buf_valid {
ed.hist_buf
} else {
ed.context.history[i].clone()
}
}
_ => ed.new_buf,
}
.into()
......
This diff is collapsed.
......@@ -3,33 +3,36 @@ use context;
use std::env;
use std::fs;
use std::io::{BufReader, BufRead, Write};
use std::io::{BufRead, BufReader, Write};
fn assert_cursor_pos(s: &str, cursor: usize, expected_pos: CursorPosition) {
let buf = Buffer::from(s.to_owned());
let words = context::get_buffer_words(&buf);
let pos = CursorPosition::get(cursor, &words[..]);
assert!(expected_pos == pos,
format!("buffer: {:?}, cursor: {}, expected pos: {:?}, pos: {:?}",
s,
cursor,
expected_pos,
pos));
assert!(
expected_pos == pos,
format!(
"buffer: {:?}, cursor: {}, expected pos: {:?}, pos: {:?}",
s, cursor, expected_pos, pos
)
);
}
#[test]
fn test_get_cursor_position() {
use CursorPosition::*;
let tests = &[("hi", 0, OnWordLeftEdge(0)),
("hi", 1, InWord(0)),
("hi", 2, OnWordRightEdge(0)),
("abc abc", 4, InSpace(Some(0), Some(1))),
("abc abc", 5, OnWordLeftEdge(1)),
("abc abc", 6, InWord(1)),
("abc abc", 8, OnWordRightEdge(1)),
(" a", 0, InSpace(None, Some(0))),
("a ", 2, InSpace(Some(0), None))];
let tests = &[
("hi", 0, OnWordLeftEdge(0)),
("hi", 1, InWord(0)),
("hi", 2, OnWordRightEdge(0)),
("abc abc", 4, InSpace(Some(0), Some(1))),
("abc abc", 5, OnWordLeftEdge(1)),
("abc abc", 6, InWord(1)),
("abc abc", 8, OnWordRightEdge(1)),
(" a", 0, InSpace(None, Some(0))),
("a ", 2, InSpace(Some(0), None)),
];
for t in tests {
assert_cursor_pos(t.0, t.1, t.2);
......@@ -47,16 +50,20 @@ fn assert_buffer_actions(start: &str, expected: &str, actions: &[Action]) {
#[test]
fn test_buffer_actions() {
assert_buffer_actions("",
"h",
&[Action::Insert {
start: 0,
text: "hi".chars().collect(),
},
Action::Remove {
start: 1,
text: ".".chars().collect(),
}]);
assert_buffer_actions(
"",
"h",
&[
Action::Insert {
start: 0,
text: "hi".chars().collect(),
},
Action::Remove {
start: 1,
text: ".".chars().collect(),
},
],
);
}
#[test]
......
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