Commit 2aa8516e authored by Michael Aaron Murphy's avatar Michael Aaron Murphy

Optimize via Clippy & Thread-Local Buffer

parent 29e4434e
......@@ -158,7 +158,7 @@ impl Buffer {
}
pub fn revert(&mut self) -> bool {
if self.actions.len() == 0 {
if self.actions.is_empty() {
return false;
}
......@@ -228,7 +228,7 @@ impl Buffer {
}
pub fn range_chars(&self, start: usize, end: usize) -> Vec<char> {
self.data[start..end].iter().cloned().collect()
self.data[start..end].to_owned()
}
pub fn width(&self) -> Vec<usize> {
......@@ -292,13 +292,12 @@ impl Buffer {
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 {
if ! other.data.is_empty() && self_len != other_len {
let match_let = self.data
.iter()
.zip(&other.data)
.take_while(|&(s, o)| *s == *o)
.collect::<Vec<_>>()
.len();
.count();
match_let == other_len
} else {
false
......
......@@ -40,7 +40,7 @@ impl Completer for FilenameCompleter {
let start_owned: String = if start.starts_with('\"') || start.starts_with('\'') {
start = &start[1..];
if start.len() >= 1 {
if ! start.is_empty() {
start = &start[..start.len() - 1];
}
start.into()
......@@ -64,7 +64,7 @@ impl Completer for FilenameCompleter {
let completing_dir;
match full_path.parent() {
// XXX non-unix separaor
Some(parent) if !start.is_empty() && !start_owned.ends_with("/") &&
Some(parent) if !start.is_empty() && !start_owned.ends_with('/') &&
!full_path.ends_with("..") => {
p = parent;
start_name = full_path
......@@ -76,7 +76,7 @@ impl Completer for FilenameCompleter {
_ => {
p = full_path.as_path();
start_name = "".into();
completing_dir = start.is_empty() || start.ends_with("/") || full_path.ends_with("..");
completing_dir = start.is_empty() || start.ends_with('/') || full_path.ends_with("..");
}
}
......
......@@ -25,10 +25,8 @@ pub fn get_buffer_words(buf: &Buffer) -> Vec<(usize, usize)> {
res.push((start, i));
word_start = None;
}
} else {
if c != ' ' {
word_start = Some(i);
}
} else if c != ' ' {
word_start = Some(i);
}
just_had_backslash = false;
......
use std::cell::RefCell;
use std::cmp;
use std::io::{self, Write};
use termion::{self, clear, color, cursor};
......@@ -8,6 +9,37 @@ use Buffer;
use event::*;
use util;
/// Buffer for prompt writes, meant to be shared between prompt creations.
struct LocalBuffer(pub RefCell<Vec<u8>>);
impl LocalBuffer {
pub fn new() -> Self {
LocalBuffer(RefCell::new(Vec::with_capacity(512)))
}
pub fn append(&self, bytes: &[u8]) {
self.0.borrow_mut().extend_from_slice(bytes);
}
pub fn push(&self, byte: u8) {
self.0.borrow_mut().push(byte);
}
pub fn pop(&self) -> Option<u8> {
self.0.borrow_mut().pop()
}
pub fn extract<T>(&self, mut func: impl FnMut(&[u8]) -> T) -> T {
let result = func(&self.0.borrow());
self.0.borrow_mut().clear();
result
}
}
thread_local! {
static BUFFER: LocalBuffer = LocalBuffer::new();
}
/// Represents the position of the cursor relative to words in the buffer.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorPosition {
......@@ -73,9 +105,6 @@ pub struct Editor<'a, W: Write> {
// Buffer for the new line (ie. not from editing history)
new_buf: Buffer,
// Store the line to be written here, avoiding allocations & formatting.
output_buf: Vec<u8>,
// None if we're on the new buffer, else the index of history
cur_history_loc: Option<usize>,
......@@ -138,7 +167,6 @@ impl<'a, W: Write> Editor<'a, W> {
out: out,
closure: f,
new_buf: buffer.into(),
output_buf: Vec::new(),
cur_history_loc: None,
context: context,
show_completions_hint: None,
......@@ -195,7 +223,7 @@ impl<'a, W: Write> Editor<'a, W> {
self.cursor = cur_buf!(self).num_chars();
self.no_newline = true;
self._display(false)?;
self.out.write(b"\r\n")?;
self.out.write_all(b"\r\n")?;
self.show_completions_hint = None;
Ok(true)
}
......@@ -242,7 +270,7 @@ impl<'a, W: Write> Editor<'a, W> {
Ok(did)
}
fn print_completion_list(output_buf: &mut Vec<u8>, completions: &[String], highlighted: Option<usize>) -> io::Result<usize> {
fn print_completion_list(completions: &[String], highlighted: Option<usize>) -> io::Result<usize> {
use std::cmp::max;
let (w, _) = termion::terminal_size()?;
......@@ -255,30 +283,33 @@ impl<'a, W: Write> Editor<'a, W> {
let mut lines = 0;
let mut i = 0;
for (index, com) in completions.iter().enumerate() {
if i == cols {
output_buf.write_all(b"\r\n")?;
lines += 1;
i = 0;
} else if i > cols {
unreachable!()
}
BUFFER.with(|output_buf| {
let mut i = 0;
for (index, com) in completions.iter().enumerate() {
if i == cols {
output_buf.append(b"\r\n");
lines += 1;
i = 0;
} else if i > cols {
unreachable!()
}
if Some(index) == highlighted {
output_buf.extend_from_slice(color::Black.fg_str().as_bytes());
output_buf.extend_from_slice(color::White.bg_str().as_bytes());
}
write!(output_buf, "{:<1$}", com, col_width)?;
if Some(index) == highlighted {
output_buf.extend_from_slice(color::Reset.bg_str().as_bytes());
output_buf.extend_from_slice(color::Reset.fg_str().as_bytes());
if Some(index) == highlighted {
output_buf.append(color::Black.fg_str().as_bytes());
output_buf.append(color::White.bg_str().as_bytes());
}
write!(output_buf.0.borrow_mut(), "{:<1$}", com, col_width)?;
if Some(index) == highlighted {
output_buf.append(color::Reset.bg_str().as_bytes());
output_buf.append(color::Reset.fg_str().as_bytes());
}
i += 1;
}
i += 1;
}
Ok(lines)
})
Ok(lines)
}
pub fn skip_completions_hint(&mut self) {
......@@ -392,8 +423,11 @@ impl<'a, W: Write> Editor<'a, W> {
/// Clears the screen then prints the prompt and current buffer.
pub fn clear(&mut self) -> io::Result<()> {
self.output_buf.extend_from_slice(clear::All.as_ref());
self.output_buf.extend_from_slice(String::from(cursor::Goto(1,1)).as_bytes());
BUFFER.with(|output_buf| {
output_buf.append(clear::All.as_ref());
output_buf.append(String::from(cursor::Goto(1,1)).as_bytes());
});
self.term_cursor_line = 1;
self.no_newline = true;
self.display()
......@@ -658,169 +692,173 @@ impl<'a, W: Write> Editor<'a, W> {
}
fn _display(&mut self, show_autosuggest: bool) -> io::Result<()> {
fn calc_width(prompt_width: usize, buf_widths: &[usize], terminal_width: usize) -> usize {
let mut total = 0;
BUFFER.with(|output_buf| {
fn calc_width(prompt_width: usize, buf_widths: &[usize], terminal_width: usize) -> usize {
let mut total = 0;
for line in buf_widths {
if total % terminal_width != 0 {
total = ((total / terminal_width) + 1) * terminal_width;
for line in buf_widths {
if total % terminal_width != 0 {
total = ((total / terminal_width) + 1) * terminal_width;
}
total += prompt_width + line;
}
total += prompt_width + line;
total
}
total
}
let terminal_width = util::terminal_width()?;
let prompt_width = util::last_prompt_line_width(&self.prompt);
let buf = cur_buf!(self);
let buf_width = buf.width();
let terminal_width = util::terminal_width()?;
let prompt_width = util::last_prompt_line_width(&self.prompt);
// Don't let the cursor go over the end!
let buf_num_chars = buf.num_chars();
if buf_num_chars < self.cursor {
self.cursor = buf_num_chars;
}
let buf = cur_buf!(self);
let buf_width = buf.width();
// Can't move past the last character in vi normal mode
if self.no_eol && self.cursor != 0 && self.cursor == buf_num_chars {
self.cursor -= 1;
}
// Don't let the cursor go over the end!
let buf_num_chars = buf.num_chars();
if buf_num_chars < self.cursor {
self.cursor = buf_num_chars;
}
// Width of the current buffer lines (including autosuggestion)
let buf_widths = match self.current_autosuggestion() {
Some(suggestion) => suggestion.width(),
None => buf_width,
};
// Width of the current buffer lines (including autosuggestion) from the start to the cursor
let buf_widths_to_cursor = match self.current_autosuggestion() {
Some(suggestion) => suggestion.range_width(0, self.cursor),
None => buf.range_width(0, self.cursor),
};
// Can't move past the last character in vi normal mode
if self.no_eol && self.cursor != 0 && self.cursor == buf_num_chars {
self.cursor -= 1;
}
// Total number of terminal spaces taken up by prompt and buffer
let new_total_width = calc_width(prompt_width, &buf_widths, terminal_width);
let new_total_width_to_cursor = calc_width(prompt_width, &buf_widths_to_cursor, terminal_width);
// Width of the current buffer lines (including autosuggestion)
let buf_widths = match self.current_autosuggestion() {
Some(suggestion) => suggestion.width(),
None => buf_width,
};
// Width of the current buffer lines (including autosuggestion) from the start to the cursor
let buf_widths_to_cursor = match self.current_autosuggestion() {
Some(suggestion) => suggestion.range_width(0, self.cursor),
None => buf.range_width(0, self.cursor),
};
let new_num_lines = (new_total_width + terminal_width) / terminal_width;
// Total number of terminal spaces taken up by prompt and buffer
let new_total_width = calc_width(prompt_width, &buf_widths, terminal_width);
let new_total_width_to_cursor = calc_width(prompt_width, &buf_widths_to_cursor, terminal_width);
// Move the term cursor to the same line as the prompt.
if self.term_cursor_line > 1 {
self.output_buf.extend_from_slice(cursor::Up(self.term_cursor_line as u16 - 1).to_string().as_bytes());
}
let new_num_lines = (new_total_width + terminal_width) / terminal_width;
if ! self.no_newline {
self.output_buf.extend_from_slice("⏎".as_bytes());
for _ in 0..(terminal_width - 1) {
self.output_buf.push(b' ');
// Move the term cursor to the same line as the prompt.
if self.term_cursor_line > 1 {
output_buf.append(cursor::Up(self.term_cursor_line as u16 - 1).to_string().as_bytes());
}
}
self.output_buf.push(b'\r');
self.output_buf.extend_from_slice(clear::AfterCursor.as_ref());
// If we're cycling through completions, show those
let mut completion_lines = 0;
if let Some((completions, i)) = self.show_completions_hint.as_ref() {
completion_lines = 1 + Self::print_completion_list(&mut self.output_buf, completions, *i)?;
self.output_buf.extend_from_slice(b"\r\n");
}
// Write the prompt
if ! self.no_newline {
for line in self.prompt.split('\n') {
self.output_buf.extend_from_slice(line.as_bytes());
self.output_buf.extend_from_slice(b"\r\n");
if ! self.no_newline {
output_buf.append("⏎".as_bytes());
for _ in 0..(terminal_width - 1) {
output_buf.push(b' ');
}
}
self.output_buf.pop(); // pop the '\n'
self.output_buf.pop(); // pop the '\r'
} else {
self.output_buf.extend_from_slice(util::handle_prompt(&self.prompt).as_bytes());
}
// If we have an autosuggestion, we make the autosuggestion the buffer we print out.
// We get the number of bytes in the buffer (but NOT the autosuggestion).
// Then, we loop and subtract from that number until it's 0, in which case we are printing
// the autosuggestion from here on (in a different color).
let lines = if show_autosuggest {
match self.current_autosuggestion() {
Some(suggestion) => suggestion.lines(),
None => buf.lines(),
}
} else {
buf.lines()
};
let mut buf_num_remaining_bytes = buf.num_bytes();
output_buf.push(b'\r');
output_buf.append(clear::AfterCursor.as_ref());
let lines_len = lines.len();
for (i, line) in lines.into_iter().enumerate() {
if i > 0 {
self.output_buf.extend_from_slice(cursor::Right(prompt_width as u16).to_string().as_bytes());
// If we're cycling through completions, show those
let mut completion_lines = 0;
if let Some((completions, i)) = self.show_completions_hint.as_ref() {
completion_lines = 1 + Self::print_completion_list(completions, *i)?;
output_buf.append(b"\r\n");
}
if buf_num_remaining_bytes == 0 {
self.output_buf.extend_from_slice(line.as_bytes());
} else if line.len() > buf_num_remaining_bytes {
let start = &line[..buf_num_remaining_bytes];
let start = match self.closure {
Some(ref f) => f(start),
None => start.to_owned(),
};
self.output_buf.extend_from_slice(start.as_bytes());
self.output_buf.extend_from_slice(color::Yellow.fg_str().as_bytes());
self.output_buf.extend_from_slice(line[buf_num_remaining_bytes..].as_bytes());
buf_num_remaining_bytes = 0;
// Write the prompt
if ! self.no_newline {
for line in self.prompt.split('\n') {
output_buf.append(line.as_bytes());
output_buf.append(b"\r\n");
}
output_buf.pop(); // pop the '\n'
output_buf.pop(); // pop the '\r'
} else {
buf_num_remaining_bytes -= line.len();
let written_line = match self.closure {
Some(ref f) => f(&line),
None => line,
};
self.output_buf.extend_from_slice(written_line.as_bytes());
output_buf.append(util::handle_prompt(&self.prompt).as_bytes());
}
if i + 1 < lines_len {
self.output_buf.extend_from_slice(b"\r\n");
// If we have an autosuggestion, we make the autosuggestion the buffer we print out.
// We get the number of bytes in the buffer (but NOT the autosuggestion).
// Then, we loop and subtract from that number until it's 0, in which case we are printing
// the autosuggestion from here on (in a different color).
let lines = if show_autosuggest {
match self.current_autosuggestion() {
Some(suggestion) => suggestion.lines(),
None => buf.lines(),
}
} else {
buf.lines()
};
let mut buf_num_remaining_bytes = buf.num_bytes();
let lines_len = lines.len();
for (i, line) in lines.into_iter().enumerate() {
if i > 0 {
output_buf.append(cursor::Right(prompt_width as u16).to_string().as_bytes());
}
if buf_num_remaining_bytes == 0 {
output_buf.append(line.as_bytes());
} else if line.len() > buf_num_remaining_bytes {
let start = &line[..buf_num_remaining_bytes];
let start = match self.closure {
Some(ref f) => f(start),
None => start.to_owned(),
};
output_buf.append(start.as_bytes());
output_buf.append(color::Yellow.fg_str().as_bytes());
output_buf.append(line[buf_num_remaining_bytes..].as_bytes());
buf_num_remaining_bytes = 0;
} else {
buf_num_remaining_bytes -= line.len();
let written_line = match self.closure {
Some(ref f) => f(&line),
None => line,
};
output_buf.append(written_line.as_bytes());
}
if i + 1 < lines_len {
output_buf.append(b"\r\n");
}
}
}
if self.is_currently_showing_autosuggestion() {
self.output_buf.extend_from_slice(color::Reset.fg_str().as_bytes());
}
if self.is_currently_showing_autosuggestion() {
output_buf.append(color::Reset.fg_str().as_bytes());
}
// at the end of the line, move the cursor down a line
if new_total_width % terminal_width == 0 {
self.output_buf.extend_from_slice(b"\r\n");
}
// at the end of the line, move the cursor down a line
if new_total_width % terminal_width == 0 {
output_buf.append(b"\r\n");
}
self.term_cursor_line = (new_total_width_to_cursor + terminal_width) / terminal_width;
self.term_cursor_line = (new_total_width_to_cursor + terminal_width) / terminal_width;
// The term cursor is now on the bottom line. We may need to move the term cursor up
// to the line where the true cursor is.
let cursor_line_diff = new_num_lines as isize - self.term_cursor_line as isize;
if cursor_line_diff > 0 {
self.output_buf.extend_from_slice(cursor::Up(cursor_line_diff as u16).to_string().as_bytes());
} else if cursor_line_diff < 0 {
unreachable!();
}
// The term cursor is now on the bottom line. We may need to move the term cursor up
// to the line where the true cursor is.
let cursor_line_diff = new_num_lines as isize - self.term_cursor_line as isize;
if cursor_line_diff > 0 {
output_buf.append(cursor::Up(cursor_line_diff as u16).to_string().as_bytes());
} else if cursor_line_diff < 0 {
unreachable!();
}
// Now that we are on the right line, we must move the term cursor left or right
// to match the true cursor.
let cursor_col_diff = new_total_width as isize - new_total_width_to_cursor as isize -
cursor_line_diff * terminal_width as isize;
if cursor_col_diff > 0 {
self.output_buf.extend_from_slice(cursor::Left(cursor_col_diff as u16).to_string().as_bytes());
} else if cursor_col_diff < 0 {
self.output_buf.extend_from_slice(cursor::Right((-cursor_col_diff) as u16).to_string().as_bytes());
}
// Now that we are on the right line, we must move the term cursor left or right
// to match the true cursor.
let cursor_col_diff = new_total_width as isize - new_total_width_to_cursor as isize -
cursor_line_diff * terminal_width as isize;
if cursor_col_diff > 0 {
output_buf.append(cursor::Left(cursor_col_diff as u16).to_string().as_bytes());
} else if cursor_col_diff < 0 {
output_buf.append(cursor::Right((-cursor_col_diff) as u16).to_string().as_bytes());
}
self.term_cursor_line += completion_lines;
self.term_cursor_line += completion_lines;
self.out.write_all(&self.output_buf)?;
self.output_buf.clear();
self.out.flush()
{
let out = &mut self.out;
output_buf.extract(|b| out.write_all(b))?;
out.flush()
}
})
}
/// Deletes the displayed prompt and buffer, replacing them with the current prompt and buffer
......@@ -859,7 +897,6 @@ mod tests {
#[test]
fn move_cursor_left() {
let mut context = Context::new();
let closure = |s: &str| {String::from(s)};
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), None, &mut context).unwrap();
ed.insert_str_after_cursor("let").unwrap();
......
......@@ -4,7 +4,7 @@ use std::{
collections::{vec_deque, VecDeque},
io::{BufRead, BufReader, BufWriter},
fs::File,
io::{self, Seek, SeekFrom, Write},
io::{self, Write},
iter::IntoIterator,
ops::Index,
ops::IndexMut,
......@@ -115,7 +115,7 @@ impl History {
curr_position: Option<usize>,
new_buff: &'b Buffer,
) -> Option<&'a Buffer> {
let pos = curr_position.unwrap_or(self.buffers.len());
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) {
......
......@@ -45,7 +45,7 @@ impl ModeStack {
/// If the stack is empty, we are in normal mode.
fn mode(&self) -> Mode {
self.0.last()
.map(|&m| m)
.cloned()
.unwrap_or(Mode::Normal)
}
......@@ -92,18 +92,18 @@ enum ViMoveDir {
}
impl ViMoveDir {
pub fn advance(&self, cursor: &mut usize, max: usize) -> bool {
self.move_cursor(cursor, max, *self)
pub fn advance(self, cursor: &mut usize, max: usize) -> bool {
self.move_cursor(cursor, max, self)
}
pub fn go_back(&self, cursor: &mut usize, max: usize) -> bool {
match *self {
pub fn go_back(self, cursor: &mut usize, max: usize) -> bool {
match self {
ViMoveDir::Right => self.move_cursor(cursor, max, ViMoveDir::Left),
ViMoveDir::Left => self.move_cursor(cursor, max, ViMoveDir::Right),
}
}
fn move_cursor(&self, cursor: &mut usize, max: usize, dir: ViMoveDir) -> bool {
fn move_cursor(self, cursor: &mut usize, max: usize, dir: ViMoveDir) -> bool {
if dir == ViMoveDir::Right && *cursor == max {
return false;
}
......@@ -272,8 +272,7 @@ fn find_char(buf: &::buffer::Buffer, start: usize, ch: char, count: usize) -> Op
.enumerate()
.skip(start)
.filter(|&(_, &c)| c == ch)
.skip(count - 1)
.next()
.nth(count - 1)
.map(|(i, _)| i)
}
......@@ -285,8 +284,7 @@ fn find_char_rev(buf: &::buffer::Buffer, start: usize, ch: char, count: usize) -
.rev()
.skip(rstart)
.filter(|&(_, &c)| c == ch)
.skip(count - 1)
.next()
.nth(count - 1)
.map(|(i, _)| i)
}
......
......@@ -10,14 +10,14 @@ pub fn last_prompt_line_width<S: AsRef<str>>(s: S) -> usize {
}
pub fn find_longest_common_prefix<T: Clone + Eq>(among: &[Vec<T>]) -> Option<Vec<T>> {
if among.len() == 0 {
if among.is_empty() {
return None;
} else if among.len() == 1 {
return Some(among[0].clone());
}
for s in among {