Newer
Older
//! This implementation is based on [Antirez's
//! Linenoise](https://github.com/antirez/linenoise)
//! Ok(line) => println!("Line: {:?}",line),
//! Err(_) => println!("No input"),
//! }
#[cfg(windows)]
extern crate winapi;
pub mod completion;
use std::path::Path;
use unicode_segmentation::UnicodeSegmentation;
use history::{Direction, History};
pub use keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
use keymap::EditState;
pub use config::{CompletionType, Config, EditMode, HistoryDuplicates};
/// The error type for I/O and Linux Syscalls (Errno)
pub type Result<T> = result::Result<T, error::ReadlineError>;
/// Represent the state during line editing.
/// Implement rendering.
prompt: &'prompt str, // Prompt to display
prompt_size: Position, // Prompt Unicode/visible width and height
line: LineBuffer, // Edited line buffer
cursor: Position, /* Cursor position (relative to the start of the prompt
* for `row`) */
old_rows: usize, // Number of rows used so far (from start of prompt to end of input)
history_index: usize, // The history index we are currently editing
saved_line_for_history: LineBuffer, // Current edited line before history browsing
fn new(
out: &'out mut Renderer,
config: &Config,
prompt: &'prompt str,
history_index: usize,
custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>,
let prompt_size = out.calculate_position(prompt, Position::default());
out: out,
prompt: prompt,
prompt_size: prompt_size,
line: LineBuffer::with_capacity(capacity),
cursor: prompt_size,
old_rows: prompt_size.row,
edit_state: EditState::new(config, custom_bindings),
fn next_cmd<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> {
if rc.is_err() && self.out.sigwinch() {
self.out.update_size();
try!(self.refresh_line());
continue;
}
return rc;
self.saved_line_for_history
.update(self.line.as_str(), self.line.pos());
self.line.update(
self.saved_line_for_history.as_str(),
self.saved_line_for_history.pos(),
);
fn move_cursor(&mut self) -> Result<()> {
// calculate the desired position of the cursor
let cursor = self.out
.calculate_position(&self.line[..self.line.pos()], self.prompt_size);
if self.cursor == cursor {
return Ok(());
}
try!(self.out.move_cursor(self.cursor, cursor));
self.cursor = cursor;
Ok(())
}
/// Rewrite the currently edited line accordingly to the buffer content,
/// cursor position, and number of columns of the terminal.
fn refresh_line(&mut self) -> Result<()> {
let hint = self.hint();
self.refresh(self.prompt, prompt_size, hint)
}
fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> {
let prompt_size = self.out.calculate_position(prompt, Position::default());
let hint = self.hint();
self.refresh(prompt, prompt_size, hint)
fn refresh(&mut self, prompt: &str, prompt_size: Position, hint: Option<String>) -> Result<()> {
let (cursor, end_pos) = try!(self.out.refresh_line(
prompt,
prompt_size,
&self.line,
fn hint(&self) -> Option<String> {
if let Some(ref hinter) = self.hinter {
hinter
.borrow_mut()
.hint(self.line.as_str(), self.line.pos())
impl<'out, 'prompt> fmt::Debug for State<'out, 'prompt> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("State")
.field("prompt", &self.prompt)
.field("prompt_size", &self.prompt_size)
.field("buf", &self.line)
.field("cursor", &self.cursor)
.field("old_rows", &self.old_rows)
/// Insert the character `ch` at cursor current position.
fn edit_insert(s: &mut State, ch: char, n: RepeatCount) -> Result<()> {
let prompt_size = s.prompt_size;
let hint = s.hint();
if n == 1 && s.cursor.col + ch.width().unwrap_or(0) < s.out.get_columns() &&
hint.is_none()
{
// Avoid a full update of the line in the trivial case.
let cursor = s.out
.calculate_position(&s.line[..s.line.pos()], s.prompt_size);
let bits = ch.encode_utf8(&mut s.byte_buffer);
let bits = bits.as_bytes();
}
} else {
Ok(())
}
}
/// Replace a single (or n) character(s) under the cursor (Vi mode)
fn edit_replace_char(s: &mut State, ch: char, n: RepeatCount) -> Result<()> {
s.changes.borrow_mut().begin();
let succeed = if let Some(chars) = s.line.delete(n) {
let count = chars.graphemes(true).count();
/// Overwrite the character under the cursor (Vi mode)
fn edit_overwrite_char(s: &mut State, ch: char) -> Result<()> {
if let Some(end) = s.line.next_pos(1) {
{
let text = ch.encode_utf8(&mut s.byte_buffer);
let start = s.line.pos();
s.line.replace(start..end, text);
}
s.refresh_line()
} else {
Ok(())
}
}
fn edit_yank(s: &mut State, text: &str, anchor: Anchor, n: RepeatCount) -> Result<()> {
if let Anchor::After = anchor {
s.line.move_forward(1);
}
if s.line.yank(text, n).is_some() {
// Delete previously yanked text and yank/paste `text` at current position.
fn edit_yank_pop(s: &mut State, yank_size: usize, text: &str) -> Result<()> {
let result = edit_yank(s, text, Anchor::Before, 1);
s.changes.borrow_mut().end();
result
fn edit_move_backward(s: &mut State, n: RepeatCount) -> Result<()> {
if s.line.move_backward(n) {
} else {
Ok(())
}
}
/// Move cursor on the right.
fn edit_move_forward(s: &mut State, n: RepeatCount) -> Result<()> {
if s.line.move_forward(n) {
}
}
/// Move cursor to the start of the line.
fn edit_move_home(s: &mut State) -> Result<()> {
} else {
Ok(())
}
}
/// Move cursor to the end of the line.
fn edit_move_end(s: &mut State) -> Result<()> {
}
}
/// Delete the character at the right of the cursor without altering the cursor
/// position. Basically this is what happens with the "Delete" keyboard key.
fn edit_delete(s: &mut State, n: RepeatCount) -> Result<()> {
} else {
Ok(())
}
}
/// Backspace implementation.
fn edit_backspace(s: &mut State, n: RepeatCount) -> Result<()> {
if s.line.backspace(n) {
} else {
Ok(())
}
}
/// Kill the text from point to the end of the line.
fn edit_kill_line(s: &mut State) -> Result<()> {
if s.line.kill_line() {
s.refresh_line()
}
}
/// Kill backward from point to the beginning of the line.
fn edit_discard_line(s: &mut State) -> Result<()> {
if s.line.discard_line() {
s.refresh_line()
}
}
/// Exchange the char before cursor with the character at cursor.
fn edit_transpose_chars(s: &mut State) -> Result<()> {
s.changes.borrow_mut().begin();
let succeed = s.line.transpose_chars();
s.changes.borrow_mut().end();
fn edit_move_to_prev_word(s: &mut State, word_def: Word, n: RepeatCount) -> Result<()> {
} else {
Ok(())
}
}
/// Delete the previous word, maintaining the cursor at the start of the
/// current word.
fn edit_delete_prev_word(s: &mut State, word_def: Word, n: RepeatCount) -> Result<()> {
if s.line.delete_prev_word(word_def, n) {
s.refresh_line()
fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word, n: RepeatCount) -> Result<()> {
fn edit_move_to(s: &mut State, cs: CharSearch, n: RepeatCount) -> Result<()> {
/// Kill from the cursor to the end of the current word, or, if between words,
/// to the end of the next word.
fn edit_delete_word(s: &mut State, at: At, word_def: Word, n: RepeatCount) -> Result<()> {
if s.line.delete_word(at, word_def, n) {
s.refresh_line()
fn edit_delete_to(s: &mut State, cs: CharSearch, n: RepeatCount) -> Result<()> {
if s.line.delete_to(cs, n) {
s.refresh_line()
s.changes.borrow_mut().begin();
let succeed = s.line.edit_word(a);
s.changes.borrow_mut().end();
fn edit_transpose_words(s: &mut State, n: RepeatCount) -> Result<()> {
s.changes.borrow_mut().begin();
let succeed = s.line.transpose_words(n);
s.changes.borrow_mut().end();
/// Substitute the currently edited line with the next or previous history
/// entry.
fn edit_history_next(s: &mut State, history: &History, prev: bool) -> Result<()> {
if history.is_empty() {
return Ok(());
}
if s.history_index == history.len() {
} else if s.history_index == 0 && prev {
return Ok(());
}
if prev {
s.history_index -= 1;
if s.history_index < history.len() {
let buf = history.get(s.history_index).unwrap();
fn edit_history_search(s: &mut State, history: &History, dir: Direction) -> Result<()> {
if history.is_empty() {
if s.history_index == history.len() && dir == Direction::Forward {
} else if s.history_index == 0 && dir == Direction::Reverse {
}
if dir == Direction::Reverse {
s.history_index -= 1;
} else {
s.history_index += 1;
}
if let Some(history_index) =
history.starts_with(&s.line.as_str()[..s.line.pos()], s.history_index, dir)
{
s.history_index = history_index;
let buf = history.get(history_index).unwrap();
/// Substitute the currently edited line with the first/last history entry.
fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> {
if history.is_empty() {
return Ok(());
}
if s.history_index == history.len() {
if first {
} else {
return Ok(());
}
} else if s.history_index == 0 && first {
return Ok(());
}
if first {
s.history_index = 0;
let buf = history.get(s.history_index).unwrap();
} else {
s.history_index = history.len();
// Restore current edited line
let (start, candidates) = try!(completer.complete(&s.line, s.line.pos()));
} else if CompletionType::Circular == config.completion_type() {
// Save the current edited line before overwriting it
let backup = s.line.as_str().to_owned();
let backup_pos = s.line.pos();
let mut i = 0;
loop {
// Show completion or original buffer
if i < candidates.len() {
completer.update(&mut s.line, start, &candidates[i]);
} else if CompletionType::List == config.completion_type() {
// beep if ambiguous
if candidates.len() > 1 {
}
if let Some(lcp) = longest_common_prefix(&candidates) {
// if we can extend the item, extend it and return to main loop
if lcp.len() > s.line.pos() - start {
try!(s.refresh_line());
return Ok(None);
}
}
// if any character other than tab, pass it to the main loop
// move cursor to EOL to avoid overwriting the command line
let save_pos = s.line.pos();
try!(edit_move_end(s));
s.line.set_pos(save_pos);
// we got a second tab, maybe show list of possible completions
let show_completions = if candidates.len() > config.completion_prompt_limit() {
let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len());
while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1, 'Y') &&
cmd != Cmd::SelfInsert(1, 'n') &&
cmd != Cmd::SelfInsert(1, 'N') &&
cmd != Cmd::Kill(Movement::BackwardChar(1))
{
fn page_completions<R: RawReader>(
rdr: &mut R,
s: &mut State,
candidates: &[String],
) -> Result<Option<Cmd>> {
let max_width = cmp::min(
cols,
candidates
.into_iter()
.map(|s| s.as_str().width())
.max()
.unwrap() + min_col_pad,
);
let num_rows = (candidates.len() + num_cols - 1) / num_cols;
let mut ab = String::new();
for row in 0..num_rows {
if row == pause_row {
while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1, 'Y') &&
cmd != Cmd::SelfInsert(1, 'q') &&
cmd != Cmd::SelfInsert(1, 'Q') &&
cmd != Cmd::SelfInsert(1, ' ') &&
cmd != Cmd::Kill(Movement::BackwardChar(1)) &&
cmd != Cmd::AcceptLine
{
Cmd::SelfInsert(1, 'y') | Cmd::SelfInsert(1, 'Y') | Cmd::SelfInsert(1, ' ') => {
}
ab.clear();
for col in 0..num_cols {
let i = (col * num_rows) + row;
if i < candidates.len() {
let candidate = &candidates[i];
ab.push_str(candidate);
if ((col + 1) * num_rows) + row < candidates.len() {
for _ in width..max_width {
ab.push(' ');
}
}
}
}
fn reverse_incremental_search<R: RawReader>(
rdr: &mut R,
s: &mut State,
history: &History,
) -> Result<Option<Cmd>> {
if history.is_empty() {
return Ok(None);
}
let mark = s.changes.borrow_mut().begin();
// Save the current edited line (and cursor position) before overwriting it
let backup = s.line.as_str().to_owned();
let backup_pos = s.line.pos();
let mut search_buf = String::new();
let mut history_idx = history.len() - 1;
let mut direction = Direction::Reverse;
// Display the reverse-i-search prompt and process chars
loop {
let prompt = if success {
format!("(reverse-i-search)`{}': ", search_buf)
} else {
format!("(failed reverse-i-search)`{}': ", search_buf)
};
try!(s.refresh_prompt_and_line(&prompt));
direction = Direction::Reverse;
if history_idx > 0 {
history_idx -= 1;
} else {
success = false;
continue;
}
direction = Direction::Forward;
if history_idx < history.len() - 1 {
history_idx += 1;
} else {
success = false;
continue;
}
}
success = match history.search(&search_buf, history_idx, direction) {
Some(idx) => {
history_idx = idx;
let entry = history.get(idx).unwrap();
/// Handles reading and editting the readline buffer.
/// It will also handle special inputs in an appropriate fashion
/// (e.g., C-c will exit readline)
let completer = editor.completer.as_ref();
let mut stdout = editor.term.create_writer();
editor.reset_kill_ring();
let mut s = State::new(
&mut stdout,
&editor.config,
prompt,
editor.history.len(),
editor.custom_bindings.clone(),
s.line.set_delete_listener(editor.kill_ring.clone());
s.line.set_change_listener(s.changes.clone());
s.line
.update((left.to_owned() + right).as_ref(), left.len());
let mut rdr = try!(editor.term.create_reader(&editor.config));
Main
committed
loop {
if cmd.should_reset_kill_ring() {
editor.reset_kill_ring();
}
completer.unwrap().borrow_mut().deref_mut(),
if let Cmd::SelfInsert(n, c) = cmd {
try!(edit_insert(&mut s, c, n));
} else if let Cmd::Insert(n, text) = cmd {
try!(edit_yank(&mut s, &text, Anchor::Before, n));
continue;
let next = try!(reverse_incremental_search(
&mut rdr,
&mut s,
&editor.history,
));
// Move to the beginning of line.
try!(edit_move_home(&mut s))
}
try!(edit_move_home(&mut s));
try!(edit_move_to_next_word(&mut s, At::Start, Word::Big, 1))
}
try!(edit_move_backward(&mut s, n))
Cmd::Replace(n, c) => {
try!(edit_replace_char(&mut s, c, n));
Cmd::Overwrite(c) => {
try!(edit_overwrite_char(&mut s, c));
}
Cmd::EndOfFile => if !s.edit_state.is_emacs_mode() && !s.line.is_empty() {
try!(edit_move_end(&mut s));
break;
} else if s.line.is_empty() {
return Err(error::ReadlineError::Eof);
} else {
try!(edit_delete(&mut s, 1))
},
try!(edit_move_forward(&mut s, n))
editor.kill_ring.borrow_mut().start_killing();
editor.kill_ring.borrow_mut().stop_killing();
editor.kill_ring.borrow_mut().start_killing();
editor.kill_ring.borrow_mut().stop_killing();
try!(edit_history_next(&mut s, &editor.history, false))
try!(edit_history_next(&mut s, &editor.history, true))
Cmd::HistorySearchBackward => try!(edit_history_search(
&mut s,
&editor.history,
Direction::Reverse,
)),
Cmd::HistorySearchForward => try!(edit_history_search(
&mut s,
&editor.history,
Direction::Forward,
)),
// Exchange the char before cursor with the character at cursor.
try!(edit_transpose_chars(&mut s))
}
editor.kill_ring.borrow_mut().start_killing();
editor.kill_ring.borrow_mut().stop_killing();
if let Some(text) = editor.kill_ring.borrow_mut().yank() {
Cmd::ViYankTo(mvt) => if let Some(text) = s.line.copy(mvt) {
editor.kill_ring.borrow_mut().kill(&text, Mode::Append)
},
// Accept the line regardless of where the cursor is.
try!(edit_move_end(&mut s));
if s.hinter.is_some() {
// Force a refresh without hints to leave the previous
// line as the user typed it after a newline.
s.hinter = None;
try!(s.refresh_line());
}