repline: Refactor for fewer redraws and more user control.
Definitely has bugs, but eh!
This commit is contained in:
parent
ae026420f1
commit
6ba62ac1c4
42
repline/examples/continue.rs
Normal file
42
repline/examples/continue.rs
Normal file
@ -0,0 +1,42 @@
|
||||
//! Demonstrates the use of [read_and()]:
|
||||
//!
|
||||
//! The provided closure:
|
||||
//! 1. Takes a line of input (a [String])
|
||||
//! 2. Performs some calculation (using [FromStr])
|
||||
//! 3. Returns a [Result] containing a [Response] or an [Err]
|
||||
|
||||
use repline::{error::Error as RlError, Repline, Response};
|
||||
use std::error::Error;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut rl = Repline::with_input(
|
||||
"fn main {\r\n\tprintln(\"Foo!\")\r\n}\r\n".as_bytes(),
|
||||
"\x1b[33m",
|
||||
" >",
|
||||
" ?>",
|
||||
);
|
||||
while rl.read().is_ok() {}
|
||||
|
||||
let mut rl = rl.swap_input(std::io::stdin());
|
||||
loop {
|
||||
let f = |_line| -> Result<_, RlError> { Ok(Response::Continue) };
|
||||
let line = match rl.read() {
|
||||
Err(RlError::CtrlC(_)) => break,
|
||||
Err(RlError::CtrlD(line)) => {
|
||||
rl.deny();
|
||||
line
|
||||
}
|
||||
Ok(line) => line,
|
||||
Err(e) => Err(e)?,
|
||||
};
|
||||
print!("\x1b[G\x1b[J");
|
||||
match f(&line) {
|
||||
Ok(Response::Accept) => rl.accept(),
|
||||
Ok(Response::Deny) => rl.deny(),
|
||||
Ok(Response::Break) => break,
|
||||
Ok(Response::Continue) => continue,
|
||||
Err(e) => print!("\x1b[40G\x1b[A\x1bJ\x1b[91m{e}\x1b[0m\x1b[B"),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
//! The [Editor] is a multi-line buffer of [`char`]s which operates on an ANSI-compatible terminal.
|
||||
|
||||
use crossterm::{cursor::*, execute, queue, style::*, terminal::*};
|
||||
use crossterm::{cursor::*, queue, style::*, terminal::*};
|
||||
use std::{collections::VecDeque, fmt::Display, io::Write};
|
||||
|
||||
use super::error::{Error, ReplResult};
|
||||
use super::error::ReplResult;
|
||||
|
||||
fn is_newline(c: &char) -> bool {
|
||||
*c == '\n'
|
||||
@ -14,7 +14,7 @@ fn write_chars<'a, W: Write>(
|
||||
w: &mut W,
|
||||
) -> std::io::Result<()> {
|
||||
for c in c {
|
||||
write!(w, "{c}")?;
|
||||
queue!(w, Print(c))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -42,84 +42,63 @@ impl<'a> Editor<'a> {
|
||||
head.iter().chain(tail.iter())
|
||||
}
|
||||
|
||||
/// Moves up to the first line of the editor, and clears the screen.
|
||||
///
|
||||
/// This assumes the screen hasn't moved since the last draw.
|
||||
pub fn undraw<W: Write>(&self, w: &mut W) -> ReplResult<()> {
|
||||
let Self { head, .. } = self;
|
||||
match head.iter().copied().filter(is_newline).count() {
|
||||
0 => write!(w, "\x1b[0G"),
|
||||
lines => write!(w, "\x1b[{}F", lines),
|
||||
fn putchar<W: Write>(&self, c: char, w: &mut W) -> ReplResult<()> {
|
||||
let Self { color, again, .. } = self;
|
||||
match c {
|
||||
'\n' => queue!(
|
||||
w,
|
||||
Print('\n'),
|
||||
MoveToColumn(0),
|
||||
Print(color),
|
||||
Print(again),
|
||||
Print(ResetColor),
|
||||
Print(' ')
|
||||
),
|
||||
c => queue!(w, Print(c)),
|
||||
}?;
|
||||
queue!(w, Clear(ClearType::FromCursorDown))?;
|
||||
// write!(w, "\x1b[0J")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Redraws the entire editor
|
||||
pub fn redraw<W: Write>(&self, w: &mut W) -> ReplResult<()> {
|
||||
let Self { head, tail, color, begin, again } = self;
|
||||
write!(w, "{color}{begin}\x1b[0m ")?;
|
||||
// draw head
|
||||
pub fn redraw_head<W: Write>(&self, w: &mut W) -> ReplResult<()> {
|
||||
let Self { head, color, begin, .. } = self;
|
||||
match head.iter().copied().filter(is_newline).count() {
|
||||
0 => queue!(w, MoveToColumn(0)),
|
||||
n => queue!(w, MoveUp(n as u16)),
|
||||
}?;
|
||||
|
||||
queue!(w, Print(color), Print(begin), Print(ResetColor), Print(' '))?;
|
||||
for c in head {
|
||||
match c {
|
||||
'\n' => write!(w, "\r\n{color}{again}\x1b[0m "),
|
||||
_ => w.write_all({ *c as u32 }.to_le_bytes().as_slice()),
|
||||
}?
|
||||
self.putchar(*c, w)?;
|
||||
}
|
||||
// save cursor
|
||||
execute!(w, SavePosition)?;
|
||||
// draw tail
|
||||
for c in tail {
|
||||
match c {
|
||||
'\n' => write!(w, "\r\n{color}{again}\x1b[0m "),
|
||||
_ => write!(w, "{c}"),
|
||||
}?
|
||||
}
|
||||
// restore cursor
|
||||
execute!(w, RestorePosition)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prints a context-sensitive prompt (either `begin` if this is the first line,
|
||||
/// or `again` for subsequent lines)
|
||||
pub fn prompt<W: Write>(&self, w: &mut W) -> ReplResult<()> {
|
||||
let Self { head, color, begin, again, .. } = self;
|
||||
queue!(
|
||||
w,
|
||||
MoveToColumn(0),
|
||||
Print(color),
|
||||
Print(if head.is_empty() { begin } else { again }),
|
||||
ResetColor,
|
||||
Print(' '),
|
||||
)?;
|
||||
pub fn redraw_tail<W: Write>(&self, w: &mut W) -> ReplResult<()> {
|
||||
let Self { tail, .. } = self;
|
||||
queue!(w, SavePosition, Clear(ClearType::FromCursorDown))?;
|
||||
for c in tail {
|
||||
self.putchar(*c, w)?;
|
||||
}
|
||||
queue!(w, RestorePosition)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prints the characters before the cursor on the current line.
|
||||
pub fn print_head<W: Write>(&self, w: &mut W) -> ReplResult<()> {
|
||||
self.prompt(w)?;
|
||||
write_chars(
|
||||
self.head.iter().skip(
|
||||
self.head
|
||||
.iter()
|
||||
.rposition(is_newline)
|
||||
.unwrap_or(self.head.len())
|
||||
+ 1,
|
||||
),
|
||||
w,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
let Self { head, color, begin, again, .. } = self;
|
||||
let nl = self.head.iter().rposition(is_newline).map(|n| n + 1);
|
||||
let prompt = if nl.is_some() { again } else { begin };
|
||||
|
||||
pub fn print_err<W: Write>(&self, w: &mut W, err: impl Display) -> ReplResult<()> {
|
||||
queue!(
|
||||
w,
|
||||
SavePosition,
|
||||
Clear(ClearType::UntilNewLine),
|
||||
Print(err),
|
||||
RestorePosition
|
||||
MoveToColumn(0),
|
||||
Print(color),
|
||||
Print(prompt),
|
||||
ResetColor,
|
||||
Print(' '),
|
||||
)?;
|
||||
|
||||
write_chars(head.iter().skip(nl.unwrap_or(0)), w)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -132,53 +111,53 @@ impl<'a> Editor<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_err<W: Write>(&self, err: impl Display, w: &mut W) -> ReplResult<()> {
|
||||
queue!(
|
||||
w,
|
||||
SavePosition,
|
||||
Clear(ClearType::UntilNewLine),
|
||||
Print(err),
|
||||
RestorePosition
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes a character at the cursor, shifting the text around as necessary.
|
||||
pub fn push<W: Write>(&mut self, c: char, w: &mut W) -> ReplResult<()> {
|
||||
// Tail optimization: if the tail is empty,
|
||||
//we don't have to undraw and redraw on newline
|
||||
if self.tail.is_empty() {
|
||||
self.head.push_back(c);
|
||||
match c {
|
||||
'\n' => {
|
||||
write!(w, "\r\n")?;
|
||||
self.print_head(w)?;
|
||||
}
|
||||
c => {
|
||||
queue!(w, Print(c))?;
|
||||
}
|
||||
};
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if '\n' == c {
|
||||
self.undraw(w)?;
|
||||
}
|
||||
self.head.push_back(c);
|
||||
self.putchar(c, w)?;
|
||||
match c {
|
||||
'\n' => self.redraw(w)?,
|
||||
_ => {
|
||||
write!(w, "{c}")?;
|
||||
self.print_tail(w)?;
|
||||
}
|
||||
'\n' => self.redraw_tail(w),
|
||||
_ => self.print_tail(w),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Erases a character at the cursor, shifting the text around as necessary.
|
||||
pub fn pop<W: Write>(&mut self, w: &mut W) -> ReplResult<Option<char>> {
|
||||
if let Some('\n') = self.head.back() {
|
||||
self.undraw(w)?;
|
||||
}
|
||||
let c = self.head.pop_back();
|
||||
// if the character was a newline, we need to go back a line
|
||||
|
||||
match c {
|
||||
Some('\n') => self.redraw(w)?,
|
||||
None => return Ok(None),
|
||||
Some('\n') => {
|
||||
queue!(w, MoveToPreviousLine(1))?;
|
||||
self.print_head(w)?;
|
||||
self.redraw_tail(w)?;
|
||||
}
|
||||
Some(_) => {
|
||||
// go back a char
|
||||
queue!(w, MoveLeft(1), Print(' '), MoveLeft(1))?;
|
||||
queue!(w, MoveLeft(1), Clear(ClearType::UntilNewLine))?;
|
||||
self.print_tail(w)?;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
/// Pops the character after the cursor, redrawing if necessary
|
||||
pub fn delete<W: Write>(&mut self, w: &mut W) -> ReplResult<Option<char>> {
|
||||
let c = self.tail.pop_front();
|
||||
match c {
|
||||
Some('\n') => self.redraw_tail(w)?,
|
||||
_ => self.print_tail(w)?,
|
||||
}
|
||||
Ok(c)
|
||||
}
|
||||
@ -196,9 +175,14 @@ impl<'a> Editor<'a> {
|
||||
}
|
||||
|
||||
/// Sets the editor to the contents of a string, placing the cursor at the end.
|
||||
pub fn restore(&mut self, s: &str) {
|
||||
pub fn restore<W: Write>(&mut self, s: &str, w: &mut W) -> ReplResult<()> {
|
||||
match self.head.iter().copied().filter(is_newline).count() {
|
||||
0 => queue!(w, MoveToColumn(0), Clear(ClearType::FromCursorDown))?,
|
||||
n => queue!(w, MoveUp(n as u16), Clear(ClearType::FromCursorDown))?,
|
||||
};
|
||||
self.clear();
|
||||
self.head.extend(s.chars())
|
||||
self.print_head(w)?;
|
||||
self.extend(s.chars(), w)
|
||||
}
|
||||
|
||||
/// Clears the editor, removing all characters.
|
||||
@ -207,24 +191,6 @@ impl<'a> Editor<'a> {
|
||||
self.tail.clear();
|
||||
}
|
||||
|
||||
/// Pops the character after the cursor, redrawing if necessary
|
||||
pub fn delete<W: Write>(&mut self, w: &mut W) -> ReplResult<char> {
|
||||
match self.tail.front() {
|
||||
Some('\n') => {
|
||||
self.undraw(w)?;
|
||||
let out = self.tail.pop_front();
|
||||
self.redraw(w)?;
|
||||
out
|
||||
}
|
||||
_ => {
|
||||
let out = self.tail.pop_front();
|
||||
self.print_tail(w)?;
|
||||
out
|
||||
}
|
||||
}
|
||||
.ok_or(Error::EndOfInput)
|
||||
}
|
||||
|
||||
/// Erases a word from the buffer, where a word is any non-whitespace characters
|
||||
/// preceded by a single whitespace character
|
||||
pub fn erase_word<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
@ -237,15 +203,25 @@ impl<'a> Editor<'a> {
|
||||
self.head.len() + self.tail.len()
|
||||
}
|
||||
|
||||
/// Returns true if the cursor is at the beginning
|
||||
/// Returns true if the cursor is at the start of the buffer
|
||||
pub fn at_start(&self) -> bool {
|
||||
self.head.is_empty()
|
||||
}
|
||||
/// Returns true if the cursor is at the end
|
||||
/// Returns true if the cursor is at the end of the buffer
|
||||
pub fn at_end(&self) -> bool {
|
||||
self.tail.is_empty()
|
||||
}
|
||||
|
||||
/// Returns true if the cursor is at the start of a line
|
||||
pub fn at_line_start(&self) -> bool {
|
||||
matches!(self.head.back(), None | Some('\n'))
|
||||
}
|
||||
|
||||
/// Returns true if the cursor is at the end of a line
|
||||
pub fn at_line_end(&self) -> bool {
|
||||
matches!(self.tail.front(), None | Some('\n'))
|
||||
}
|
||||
|
||||
/// Returns true if the buffer is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.at_start() && self.at_end()
|
||||
@ -266,59 +242,102 @@ impl<'a> Editor<'a> {
|
||||
}
|
||||
|
||||
/// Moves the cursor back `steps` steps
|
||||
pub fn cursor_back<W: Write>(&mut self, steps: usize, w: &mut W) -> ReplResult<()> {
|
||||
for _ in 0..steps {
|
||||
if let Some('\n') = self.head.back() {
|
||||
self.undraw(w)?;
|
||||
}
|
||||
let Some(c) = self.head.pop_back() else {
|
||||
return Ok(());
|
||||
};
|
||||
self.tail.push_front(c);
|
||||
match c {
|
||||
'\n' => self.redraw(w)?,
|
||||
_ => queue!(w, MoveLeft(1))?,
|
||||
pub fn cursor_back<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
let Some(c) = self.head.pop_back() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.tail.push_front(c);
|
||||
match c {
|
||||
'\n' => {
|
||||
queue!(w, MoveToPreviousLine(1))?;
|
||||
self.print_head(w)
|
||||
}
|
||||
_ => queue!(w, MoveLeft(1)).map_err(Into::into),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Moves the cursor forward `steps` steps
|
||||
pub fn cursor_forward<W: Write>(&mut self, steps: usize, w: &mut W) -> ReplResult<()> {
|
||||
for _ in 0..steps {
|
||||
if let Some('\n') = self.tail.front() {
|
||||
self.undraw(w)?
|
||||
}
|
||||
let Some(c) = self.tail.pop_front() else {
|
||||
return Ok(());
|
||||
};
|
||||
self.head.push_back(c);
|
||||
match c {
|
||||
'\n' => self.redraw(w)?,
|
||||
_ => queue!(w, MoveRight(1))?,
|
||||
pub fn cursor_forward<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
let Some(c) = self.tail.pop_front() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.head.push_back(c);
|
||||
match c {
|
||||
'\n' => {
|
||||
queue!(w, MoveToNextLine(1))?;
|
||||
self.print_head(w)
|
||||
}
|
||||
_ => queue!(w, MoveRight(1)).map_err(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the cursor up to the previous line, attempting to preserve relative offset
|
||||
pub fn cursor_up<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
// Calculates length of the current line
|
||||
let mut len = self.head.len();
|
||||
self.cursor_line_start(w)?;
|
||||
len -= self.head.len();
|
||||
|
||||
if self.at_start() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.cursor_back(w)?;
|
||||
self.cursor_line_start(w)?;
|
||||
|
||||
while 0 < len && !self.at_line_end() {
|
||||
self.cursor_forward(w)?;
|
||||
len -= 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Moves the cursor down to the next line, attempting to preserve relative offset
|
||||
pub fn cursor_down<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
let mut len = self.head.iter().rev().take_while(|&&c| c != '\n').count();
|
||||
|
||||
self.cursor_line_end(w)?;
|
||||
self.cursor_forward(w)?;
|
||||
|
||||
while 0 < len && !self.at_line_end() {
|
||||
self.cursor_forward(w)?;
|
||||
len -= 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Moves the cursor to the beginning of the current line
|
||||
pub fn home<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
loop {
|
||||
match self.head.back() {
|
||||
Some('\n') | None => break Ok(()),
|
||||
Some(_) => self.cursor_back(1, w)?,
|
||||
}
|
||||
pub fn cursor_line_start<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
while !self.at_line_start() {
|
||||
self.cursor_back(w)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Moves the cursor to the end of the current line
|
||||
pub fn end<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
loop {
|
||||
match self.tail.front() {
|
||||
Some('\n') | None => break Ok(()),
|
||||
Some(_) => self.cursor_forward(1, w)?,
|
||||
}
|
||||
pub fn cursor_line_end<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
while !self.at_line_end() {
|
||||
self.cursor_forward(w)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cursor_start<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
while !self.at_start() {
|
||||
self.cursor_back(w)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cursor_end<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
while !self.at_end() {
|
||||
self.cursor_forward(w)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! Prompts the user, reads the lines. Not much more to it than that.
|
||||
//!
|
||||
//! This module is in charge of parsing keyboard input and interpreting it for the line editor.
|
||||
#![allow(clippy::unbuffered_bytes)]
|
||||
|
||||
use crate::{editor::Editor, error::*, iter::*, raw::raw};
|
||||
use std::{
|
||||
@ -28,7 +29,6 @@ impl<'a> Repline<'a, std::io::Stdin> {
|
||||
impl<'a, R: Read> Repline<'a, R> {
|
||||
/// Constructs a [Repline] with the given [Reader](Read), color, begin, and again prompts.
|
||||
pub fn with_input(input: R, color: &'a str, begin: &'a str, again: &'a str) -> Self {
|
||||
#[allow(clippy::unbuffered_bytes)]
|
||||
Self {
|
||||
input: Chars(Flatten(input.bytes())),
|
||||
history: Default::default(),
|
||||
@ -36,6 +36,14 @@ impl<'a, R: Read> Repline<'a, R> {
|
||||
ed: Editor::new(color, begin, again),
|
||||
}
|
||||
}
|
||||
pub fn swap_input<S: Read>(self, new_input: S) -> Repline<'a, S> {
|
||||
Repline {
|
||||
input: Chars(Flatten(new_input.bytes())),
|
||||
history: self.history,
|
||||
hindex: self.hindex,
|
||||
ed: self.ed,
|
||||
}
|
||||
}
|
||||
/// Set the terminal prompt color
|
||||
pub fn set_color(&mut self, color: &'a str) {
|
||||
self.ed.color = color
|
||||
@ -56,8 +64,7 @@ impl<'a, R: Read> Repline<'a, R> {
|
||||
let mut stdout = stdout().lock();
|
||||
let stdout = &mut stdout;
|
||||
let _make_raw = raw();
|
||||
// self.ed.begin_frame(stdout)?;
|
||||
// self.ed.redraw_frame(stdout)?;
|
||||
|
||||
self.ed.print_head(stdout)?;
|
||||
loop {
|
||||
stdout.flush()?;
|
||||
@ -75,24 +82,19 @@ impl<'a, R: Read> Repline<'a, R> {
|
||||
return Err(Error::CtrlD(self.ed.to_string()));
|
||||
}
|
||||
// Tab: extend line by 4 spaces
|
||||
'\t' => {
|
||||
self.ed.extend(INDENT.chars(), stdout)?;
|
||||
}
|
||||
'\t' => self.ed.extend(INDENT.chars(), stdout)?,
|
||||
// ignore newlines, process line feeds. Not sure how cross-platform this is.
|
||||
'\n' => {}
|
||||
'\r' => {
|
||||
if self.ed.at_end() {
|
||||
self.ed.push('\n', stdout)?;
|
||||
} else {
|
||||
self.ed.end(stdout)?;
|
||||
writeln!(stdout)?;
|
||||
self.ed.cursor_line_end(stdout)?;
|
||||
}
|
||||
return Ok(self.ed.to_string());
|
||||
}
|
||||
// Ctrl+Backspace in my terminal
|
||||
'\x17' => {
|
||||
self.ed.erase_word(stdout)?;
|
||||
}
|
||||
'\x17' => self.ed.erase_word(stdout)?,
|
||||
// Escape sequence
|
||||
'\x1b' => self.escape(stdout)?,
|
||||
// backspace
|
||||
@ -123,8 +125,12 @@ impl<'a, R: Read> Repline<'a, R> {
|
||||
self.print_err(&mut stdout, value)
|
||||
}
|
||||
/// Prints a message (ideally an error) without moving the cursor
|
||||
fn print_err<W: Write>(&mut self, w: &mut W, value: impl std::fmt::Display) -> ReplResult<()> {
|
||||
self.ed.print_err(w, value)
|
||||
fn print_err<W: Write>(&self, w: &mut W, value: impl std::fmt::Display) -> ReplResult<()> {
|
||||
self.ed.print_err(value, w)
|
||||
}
|
||||
// Prints some debug info into the editor's buffer and the provided writer
|
||||
pub fn put<D: std::fmt::Display, W: Write>(&mut self, disp: D, w: &mut W) -> ReplResult<()> {
|
||||
self.ed.extend(format!("{disp}").chars(), w)
|
||||
}
|
||||
/// Handle ANSI Escape
|
||||
fn escape<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
@ -138,26 +144,39 @@ impl<'a, R: Read> Repline<'a, R> {
|
||||
/// Handle ANSI Control Sequence Introducer
|
||||
fn csi<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
match self.input.next().ok_or(Error::EndOfInput)?? {
|
||||
'A' => {
|
||||
self.hindex = self.hindex.saturating_sub(1);
|
||||
self.restore_history(w)?
|
||||
'A' if self.ed.at_start() && self.hindex > 0 => {
|
||||
self.hindex -= 1;
|
||||
self.restore_history(w)?;
|
||||
self.ed.cursor_start(w)?;
|
||||
}
|
||||
'B' => {
|
||||
self.hindex = self.hindex.saturating_add(1).min(self.history.len());
|
||||
self.restore_history(w)?
|
||||
'A' => self.ed.cursor_up(w)?,
|
||||
'B' if self.ed.at_end() && self.hindex < self.history.len() => {
|
||||
self.restore_history(w)?;
|
||||
self.hindex += 1;
|
||||
}
|
||||
'C' => self.ed.cursor_forward(1, w)?,
|
||||
'D' => self.ed.cursor_back(1, w)?,
|
||||
'H' => self.ed.home(w)?,
|
||||
'F' => self.ed.end(w)?,
|
||||
'B' => self.ed.cursor_down(w)?,
|
||||
'C' => self.ed.cursor_forward(w)?,
|
||||
'D' => self.ed.cursor_back(w)?,
|
||||
'H' => self.ed.cursor_line_start(w)?,
|
||||
'F' => self.ed.cursor_line_end(w)?,
|
||||
'3' => {
|
||||
if let '~' = self.input.next().ok_or(Error::EndOfInput)?? {
|
||||
let _ = self.ed.delete(w);
|
||||
self.ed.delete(w)?;
|
||||
}
|
||||
}
|
||||
'5' => {
|
||||
if let '~' = self.input.next().ok_or(Error::EndOfInput)?? {
|
||||
self.ed.cursor_start(w)?
|
||||
}
|
||||
}
|
||||
'6' => {
|
||||
if let '~' = self.input.next().ok_or(Error::EndOfInput)?? {
|
||||
self.ed.cursor_end(w)?
|
||||
}
|
||||
}
|
||||
other => {
|
||||
if cfg!(debug_assertions) {
|
||||
self.ed.extend(other.escape_debug(), w)?;
|
||||
self.print_err(w, other.escape_debug())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -166,11 +185,9 @@ impl<'a, R: Read> Repline<'a, R> {
|
||||
/// Restores the currently selected history
|
||||
fn restore_history<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
|
||||
let Self { history, hindex, ed, .. } = self;
|
||||
ed.undraw(w)?;
|
||||
ed.clear();
|
||||
ed.print_head(w)?;
|
||||
if let Some(history) = history.get(*hindex) {
|
||||
ed.extend(history.chars(), w)?
|
||||
ed.restore(history, w)?;
|
||||
ed.print_err(format_args!(" History {hindex} restored!"), w)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user