repline: Sync with old repline
This commit is contained in:
parent
d1df62533e
commit
760cf725f2
16
.rustfmt.toml
Normal file
16
.rustfmt.toml
Normal file
@ -0,0 +1,16 @@
|
||||
unstable_features = true
|
||||
max_width = 100
|
||||
wrap_comments = true
|
||||
comment_width = 100
|
||||
struct_lit_width = 100
|
||||
|
||||
imports_granularity = "Crate"
|
||||
# Allow structs to fill an entire line
|
||||
# use_small_heuristics = "Max"
|
||||
# Allow small functions on single line
|
||||
# fn_single_line = true
|
||||
|
||||
# Alignment
|
||||
enum_discrim_align_threshold = 12
|
||||
#struct_field_align_threshold = 12
|
||||
where_single_line = true
|
42
examples/continue.rs
Normal file
42
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(())
|
||||
}
|
4
examples/error.rs
Normal file
4
examples/error.rs
Normal file
@ -0,0 +1,4 @@
|
||||
fn main() {
|
||||
let mut rl = repline::Repline::with_input([255, b'\r', b'\n'].as_slice(), "", "", "");
|
||||
eprintln!("{:?}", rl.read())
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
//! Demonstrates the use of [read_and()]:
|
||||
//!
|
||||
//!
|
||||
//! The provided closure:
|
||||
//! 1. Takes a line of input (a [String])
|
||||
//! 2. Performs some calculation (using [FromStr])
|
||||
|
334
src/editor.rs
334
src/editor.rs
@ -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,73 +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,
|
||||
),
|
||||
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 };
|
||||
|
||||
queue!(
|
||||
w,
|
||||
MoveToColumn(0),
|
||||
Print(color),
|
||||
Print(prompt),
|
||||
ResetColor,
|
||||
Print(' '),
|
||||
)?;
|
||||
|
||||
write_chars(head.iter().skip(nl.unwrap_or(0)), w)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -121,53 +111,54 @@ 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);
|
||||
queue!(w, Clear(ClearType::UntilNewLine))?;
|
||||
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)
|
||||
}
|
||||
@ -185,9 +176,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.
|
||||
@ -196,24 +192,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<()> {
|
||||
@ -226,9 +204,28 @@ impl<'a> Editor<'a> {
|
||||
self.head.len() + self.tail.len()
|
||||
}
|
||||
|
||||
/// 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 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.head.is_empty() && self.tail.is_empty()
|
||||
self.at_start() && self.at_end()
|
||||
}
|
||||
|
||||
/// Returns true if the buffer ends with a given pattern
|
||||
@ -246,63 +243,106 @@ 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(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'e> IntoIterator for &'e Editor<'a> {
|
||||
impl<'e> IntoIterator for &'e Editor<'_> {
|
||||
type Item = &'e char;
|
||||
type IntoIter = std::iter::Chain<
|
||||
std::collections::vec_deque::Iter<'e, char>,
|
||||
@ -313,7 +353,7 @@ impl<'a, 'e> IntoIterator for &'e Editor<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Editor<'a> {
|
||||
impl Display for Editor<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use std::fmt::Write;
|
||||
for c in self.iter() {
|
||||
|
@ -39,7 +39,7 @@ pub mod chars {
|
||||
if cont & 0xc0 != 0x80 {
|
||||
return None;
|
||||
}
|
||||
out = out << 6 | (cont & 0x3f);
|
||||
out = (out << 6) | (cont & 0x3f);
|
||||
}
|
||||
Some(char::from_u32(out).ok_or(BadUnicode(out)))
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ where F: FnMut(&str) -> Result<Response, Box<dyn Error>> {
|
||||
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"),
|
||||
Err(e) => rl.print_inline(format_args!("\x1b[40G\x1b[91m{e}\x1b[0m"))?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -8,8 +8,7 @@ struct Raw();
|
||||
impl Default for Raw {
|
||||
fn default() -> Self {
|
||||
std::thread::yield_now();
|
||||
crossterm::terminal::enable_raw_mode()
|
||||
.expect("should be able to transition into raw mode");
|
||||
crossterm::terminal::enable_raw_mode().expect("should be able to transition into raw mode");
|
||||
Raw()
|
||||
}
|
||||
}
|
||||
|
@ -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::{
|
||||
@ -35,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
|
||||
@ -55,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()?;
|
||||
@ -74,19 +82,17 @@ 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' => {
|
||||
self.ed.push('\n', stdout)?;
|
||||
return Ok(self.ed.to_string());
|
||||
if self.ed.at_end() {
|
||||
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
|
||||
@ -111,6 +117,19 @@ impl<'a, R: Read> Repline<'a, R> {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Prints a message without moving the cursor
|
||||
pub fn print_inline(&mut self, value: impl std::fmt::Display) -> ReplResult<()> {
|
||||
let mut stdout = stdout().lock();
|
||||
self.print_err(&mut stdout, value)
|
||||
}
|
||||
/// Prints a message (ideally an error) without moving the cursor
|
||||
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<()> {
|
||||
match self.input.next().ok_or(Error::EndOfInput)?? {
|
||||
@ -123,26 +142,38 @@ 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)?;
|
||||
}
|
||||
'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().saturating_sub(1) => {
|
||||
self.hindex += 1;
|
||||
self.restore_history(w)?;
|
||||
}
|
||||
'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())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -151,11 +182,12 @@ 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!("\t\x1b[30mHistory {hindex} restored!\x1b[0m"),
|
||||
w,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -165,9 +197,12 @@ impl<'a, R: Read> Repline<'a, R> {
|
||||
while buf.ends_with(char::is_whitespace) {
|
||||
buf.pop();
|
||||
}
|
||||
if !self.history.contains(&buf) {
|
||||
self.history.push_back(buf)
|
||||
}
|
||||
if let Some(idx) = self.history.iter().position(|v| *v == buf) {
|
||||
self.history
|
||||
.remove(idx)
|
||||
.expect("should have just found this");
|
||||
};
|
||||
self.history.push_back(buf);
|
||||
while self.history.len() > 20 {
|
||||
self.history.pop_front();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user