|
|
@ -5,11 +5,12 @@ use crate::{
|
|
|
|
prompt::Prompt,
|
|
|
|
prompt::Prompt,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
use std::{cmp::min, io::Write};
|
|
|
|
use std::{cmp::min, io::Write};
|
|
|
|
|
|
|
|
|
|
|
|
/// The text buffer of a text editor.
|
|
|
|
/// The text buffer of a text editor.
|
|
|
|
pub struct Buffer {
|
|
|
|
pub struct Buffer {
|
|
|
|
/// the current contents of the buffer. This is the least efficient way possible to do this.
|
|
|
|
/// the current text of the edit buffer
|
|
|
|
chars: Vec<char>,
|
|
|
|
chars: Vec<char>,
|
|
|
|
|
|
|
|
|
|
|
|
/// the position of our edit cursor within [Self::chars]
|
|
|
|
/// the position of our edit cursor within [Self::chars]
|
|
|
@ -17,6 +18,7 @@ pub struct Buffer {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Buffer {
|
|
|
|
impl Buffer {
|
|
|
|
|
|
|
|
/// an empty buffer
|
|
|
|
pub fn new() -> Self {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
Self {
|
|
|
|
chars: Vec::new(),
|
|
|
|
chars: Vec::new(),
|
|
|
@ -31,18 +33,21 @@ impl Buffer {
|
|
|
|
self.show_debug();
|
|
|
|
self.show_debug();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn pop(&mut self) -> String {
|
|
|
|
/// takes the text out of the editor, leaving an empty buffer in its place
|
|
|
|
|
|
|
|
fn pop(&mut self) -> String {
|
|
|
|
let s: String = self.chars.iter().collect();
|
|
|
|
let s: String = self.chars.iter().collect();
|
|
|
|
self.clear();
|
|
|
|
self.clear();
|
|
|
|
self.show_debug();
|
|
|
|
self.show_debug();
|
|
|
|
s
|
|
|
|
s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn show(&self) -> String {
|
|
|
|
fn show(&self) -> String {
|
|
|
|
self.chars.iter().collect()
|
|
|
|
self.chars.iter().collect()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn back(&mut self, n: usize) -> bool {
|
|
|
|
/// moves the edit cursor back by a fixed number of characters without modifying the contents
|
|
|
|
|
|
|
|
/// of the buffer
|
|
|
|
|
|
|
|
fn back(&mut self, n: usize) -> bool {
|
|
|
|
if self.cursor > 0 {
|
|
|
|
if self.cursor > 0 {
|
|
|
|
if n > self.cursor {
|
|
|
|
if n > self.cursor {
|
|
|
|
self.cursor = n;
|
|
|
|
self.cursor = n;
|
|
|
@ -56,7 +61,8 @@ impl Buffer {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn backspace(&mut self) -> bool {
|
|
|
|
/// drops a character from the text buffer at the cursor's location
|
|
|
|
|
|
|
|
fn backspace(&mut self) -> bool {
|
|
|
|
if self.chars.len() > 0 {
|
|
|
|
if self.chars.len() > 0 {
|
|
|
|
if self.cursor > 0 {
|
|
|
|
if self.cursor > 0 {
|
|
|
|
self.cursor -= 1;
|
|
|
|
self.cursor -= 1;
|
|
|
@ -70,7 +76,7 @@ impl Buffer {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// removes the elements left of the cursor, returning the count of removed elements
|
|
|
|
/// removes the elements left of the cursor, returning the count of removed elements
|
|
|
|
pub fn clear_left(&mut self) -> usize {
|
|
|
|
fn clear_left(&mut self) -> usize {
|
|
|
|
if self.cursor > 0 {
|
|
|
|
if self.cursor > 0 {
|
|
|
|
let n = self.chars.drain(..self.cursor).count();
|
|
|
|
let n = self.chars.drain(..self.cursor).count();
|
|
|
|
self.cursor = 0;
|
|
|
|
self.cursor = 0;
|
|
|
@ -83,7 +89,7 @@ impl Buffer {
|
|
|
|
|
|
|
|
|
|
|
|
/// moves the edit cursor to the beginning of the buffer. The returned value is the index of
|
|
|
|
/// moves the edit cursor to the beginning of the buffer. The returned value is the index of
|
|
|
|
/// the edit cursor before the move.
|
|
|
|
/// the edit cursor before the move.
|
|
|
|
pub fn seek_left(&mut self) -> usize {
|
|
|
|
fn seek_left(&mut self) -> usize {
|
|
|
|
let n = self.cursor;
|
|
|
|
let n = self.cursor;
|
|
|
|
self.cursor = 0;
|
|
|
|
self.cursor = 0;
|
|
|
|
self.show_debug();
|
|
|
|
self.show_debug();
|
|
|
@ -92,7 +98,7 @@ impl Buffer {
|
|
|
|
|
|
|
|
|
|
|
|
/// moves the edit cursor to the end of the buffer. The returned value is the index of th eedit
|
|
|
|
/// moves the edit cursor to the end of the buffer. The returned value is the index of th eedit
|
|
|
|
/// cursor before the move.
|
|
|
|
/// cursor before the move.
|
|
|
|
pub fn seek_right(&mut self) -> usize {
|
|
|
|
fn seek_right(&mut self) -> usize {
|
|
|
|
let n = self.chars.len() - self.cursor;
|
|
|
|
let n = self.chars.len() - self.cursor;
|
|
|
|
self.cursor = self.chars.len();
|
|
|
|
self.cursor = self.chars.len();
|
|
|
|
self.show_debug();
|
|
|
|
self.show_debug();
|
|
|
@ -100,7 +106,7 @@ impl Buffer {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// moves the edit cursor forward by n positions
|
|
|
|
/// moves the edit cursor forward by n positions
|
|
|
|
pub fn forward(&mut self, n: usize) -> bool {
|
|
|
|
fn forward(&mut self, n: usize) -> bool {
|
|
|
|
if self.cursor < self.chars.len() {
|
|
|
|
if self.cursor < self.chars.len() {
|
|
|
|
self.cursor = min(self.chars.len(), self.cursor + n);
|
|
|
|
self.cursor = min(self.chars.len(), self.cursor + n);
|
|
|
|
self.show_debug();
|
|
|
|
self.show_debug();
|
|
|
@ -110,13 +116,15 @@ impl Buffer {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn insert(&mut self, c: char) {
|
|
|
|
/// inserts a character into the edit buffer at the cursor's current location
|
|
|
|
|
|
|
|
fn insert(&mut self, c: char) {
|
|
|
|
self.chars.insert(self.cursor, c);
|
|
|
|
self.chars.insert(self.cursor, c);
|
|
|
|
self.cursor += 1;
|
|
|
|
self.cursor += 1;
|
|
|
|
self.show_debug();
|
|
|
|
self.show_debug();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn tail(&self) -> String {
|
|
|
|
/// the portion of the text buffer from the edit cursor to the end of the buffer
|
|
|
|
|
|
|
|
fn tail(&self) -> String {
|
|
|
|
let mut start = self.cursor;
|
|
|
|
let mut start = self.cursor;
|
|
|
|
if start > 0 {
|
|
|
|
if start > 0 {
|
|
|
|
start -= 1;
|
|
|
|
start -= 1;
|
|
|
@ -126,11 +134,13 @@ impl Buffer {
|
|
|
|
chars.iter().collect()
|
|
|
|
chars.iter().collect()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn pos(&self) -> usize {
|
|
|
|
/// the current position of the edit cursor
|
|
|
|
|
|
|
|
fn pos(&self) -> usize {
|
|
|
|
self.cursor
|
|
|
|
self.cursor
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
/// the length of the text buffer
|
|
|
|
|
|
|
|
fn len(&self) -> usize {
|
|
|
|
self.chars.len()
|
|
|
|
self.chars.len()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -157,16 +167,22 @@ impl Buffer {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Describes how we handle an action from the editor
|
|
|
|
enum Status {
|
|
|
|
enum Status {
|
|
|
|
|
|
|
|
/// Editing is still in progress
|
|
|
|
Ongoing,
|
|
|
|
Ongoing,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// A command has been entered in the editor and should be sent to the interpreter
|
|
|
|
Submit(String),
|
|
|
|
Submit(String),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// The edit session has ended and the shell should shut down
|
|
|
|
Done,
|
|
|
|
Done,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// A primitive terminal-based text editor
|
|
|
|
/// A primitive terminal-based text editor
|
|
|
|
pub struct Editor {
|
|
|
|
pub struct Editor {
|
|
|
|
/// the incoming terminal events
|
|
|
|
/// the incoming terminal events
|
|
|
|
pub(crate) input: input::Reader,
|
|
|
|
input: input::Reader,
|
|
|
|
|
|
|
|
|
|
|
|
/// the in-memory representation of the command that we're currently editing
|
|
|
|
/// the in-memory representation of the command that we're currently editing
|
|
|
|
buffer: Buffer,
|
|
|
|
buffer: Buffer,
|
|
|
@ -174,11 +190,12 @@ pub struct Editor {
|
|
|
|
/// our outgoing terminal events, which is used as the display portion of the editor
|
|
|
|
/// our outgoing terminal events, which is used as the display portion of the editor
|
|
|
|
display: output::Writer,
|
|
|
|
display: output::Writer,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// this thing draws our prompt
|
|
|
|
prompt: Prompt,
|
|
|
|
prompt: Prompt,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Editor {
|
|
|
|
impl Editor {
|
|
|
|
pub fn new() -> anyhow::Result<Self> {
|
|
|
|
pub fn new() -> Result<Self> {
|
|
|
|
Ok(Self {
|
|
|
|
Ok(Self {
|
|
|
|
input: input::Reader::new()?,
|
|
|
|
input: input::Reader::new()?,
|
|
|
|
buffer: Buffer::new(),
|
|
|
|
buffer: Buffer::new(),
|
|
|
@ -187,7 +204,9 @@ impl Editor {
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn read_command(&mut self) -> anyhow::Result<Option<String>> {
|
|
|
|
/// reads one command from the editor. This function blocks until the user has entered an
|
|
|
|
|
|
|
|
/// entire command.
|
|
|
|
|
|
|
|
pub fn read_command(&mut self) -> Result<Option<String>> {
|
|
|
|
use input::Event::*;
|
|
|
|
use input::Event::*;
|
|
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
loop {
|
|
|
@ -225,7 +244,7 @@ impl Editor {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn handle_key_press(&mut self, event: crate::key::Event) -> anyhow::Result<Status> {
|
|
|
|
fn handle_key_press(&mut self, event: crate::key::Event) -> Result<Status> {
|
|
|
|
use crate::key::codes::*;
|
|
|
|
use crate::key::codes::*;
|
|
|
|
match event {
|
|
|
|
match event {
|
|
|
|
_ if event.code == ENTER || event.char == '\r' => return self.submit(),
|
|
|
|
_ if event.code == ENTER || event.char == '\r' => return self.submit(),
|
|
|
@ -241,7 +260,7 @@ impl Editor {
|
|
|
|
Ok(Status::Ongoing)
|
|
|
|
Ok(Status::Ongoing)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn handle_escape(&mut self, esc: Escape) -> anyhow::Result<Status> {
|
|
|
|
fn handle_escape(&mut self, esc: Escape) -> Result<Status> {
|
|
|
|
use Escape::*;
|
|
|
|
use Escape::*;
|
|
|
|
match esc {
|
|
|
|
match esc {
|
|
|
|
Left => self.back(1)?,
|
|
|
|
Left => self.back(1)?,
|
|
|
@ -253,7 +272,7 @@ impl Editor {
|
|
|
|
Ok(Status::Ongoing)
|
|
|
|
Ok(Status::Ongoing)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn handle_control(&mut self, cc: ControlCharacter) -> anyhow::Result<Status> {
|
|
|
|
fn handle_control(&mut self, cc: ControlCharacter) -> Result<Status> {
|
|
|
|
use ControlCharacter::*;
|
|
|
|
use ControlCharacter::*;
|
|
|
|
match cc {
|
|
|
|
match cc {
|
|
|
|
StartOfHeading => self.seek_left()?,
|
|
|
|
StartOfHeading => self.seek_left()?,
|
|
|
@ -267,12 +286,12 @@ impl Editor {
|
|
|
|
Ok(Status::Ongoing)
|
|
|
|
Ok(Status::Ongoing)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn insert_dot(&mut self) -> anyhow::Result<()> {
|
|
|
|
fn insert_dot(&mut self) -> Result<()> {
|
|
|
|
self.insert('\u{2022}')?;
|
|
|
|
self.insert('\u{2022}')?;
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn submit(&mut self) -> anyhow::Result<Status> {
|
|
|
|
fn submit(&mut self) -> Result<Status> {
|
|
|
|
self.display.newline()?;
|
|
|
|
self.display.newline()?;
|
|
|
|
let text = self.buffer.pop();
|
|
|
|
let text = self.buffer.pop();
|
|
|
|
info!("◇ {}", text);
|
|
|
|
info!("◇ {}", text);
|
|
|
@ -284,7 +303,7 @@ impl Editor {
|
|
|
|
fn focus_end(&mut self) {}
|
|
|
|
fn focus_end(&mut self) {}
|
|
|
|
|
|
|
|
|
|
|
|
/// moves the edit cursor one character to the left
|
|
|
|
/// moves the edit cursor one character to the left
|
|
|
|
pub fn back(&mut self, n: usize) -> anyhow::Result<()> {
|
|
|
|
pub fn back(&mut self, n: usize) -> Result<()> {
|
|
|
|
debug!("⛬ ←");
|
|
|
|
debug!("⛬ ←");
|
|
|
|
if self.buffer.back(n) {
|
|
|
|
if self.buffer.back(n) {
|
|
|
|
self.display.back(n)?;
|
|
|
|
self.display.back(n)?;
|
|
|
@ -293,7 +312,7 @@ impl Editor {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// moves the edit cursor one character to the right
|
|
|
|
/// moves the edit cursor one character to the right
|
|
|
|
pub fn forward(&mut self, n: usize) -> anyhow::Result<()> {
|
|
|
|
pub fn forward(&mut self, n: usize) -> Result<()> {
|
|
|
|
debug!("⛬ →");
|
|
|
|
debug!("⛬ →");
|
|
|
|
if self.buffer.forward(n) {
|
|
|
|
if self.buffer.forward(n) {
|
|
|
|
self.display.forward(n)?;
|
|
|
|
self.display.forward(n)?;
|
|
|
@ -302,7 +321,7 @@ impl Editor {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// moves the cursor position to the end of the line
|
|
|
|
/// moves the cursor position to the end of the line
|
|
|
|
pub fn seek_right(&mut self) -> anyhow::Result<()> {
|
|
|
|
pub fn seek_right(&mut self) -> Result<()> {
|
|
|
|
info!("»");
|
|
|
|
info!("»");
|
|
|
|
let n = self.buffer.seek_right();
|
|
|
|
let n = self.buffer.seek_right();
|
|
|
|
if n > 0 {
|
|
|
|
if n > 0 {
|
|
|
@ -313,7 +332,7 @@ impl Editor {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// moves the cursor position to the beginning of the line
|
|
|
|
/// moves the cursor position to the beginning of the line
|
|
|
|
pub fn seek_left(&mut self) -> anyhow::Result<()> {
|
|
|
|
pub fn seek_left(&mut self) -> Result<()> {
|
|
|
|
info!("«");
|
|
|
|
info!("«");
|
|
|
|
let n = self.buffer.seek_left();
|
|
|
|
let n = self.buffer.seek_left();
|
|
|
|
if n > 0 {
|
|
|
|
if n > 0 {
|
|
|
@ -324,7 +343,7 @@ impl Editor {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// clears the line from the current cursor position to the beginning of the line
|
|
|
|
/// clears the line from the current cursor position to the beginning of the line
|
|
|
|
pub fn clear_left(&mut self) -> anyhow::Result<()> {
|
|
|
|
pub fn clear_left(&mut self) -> Result<()> {
|
|
|
|
info!("» clear left");
|
|
|
|
info!("» clear left");
|
|
|
|
let n = self.buffer.clear_left();
|
|
|
|
let n = self.buffer.clear_left();
|
|
|
|
if n > 0 {
|
|
|
|
if n > 0 {
|
|
|
@ -342,7 +361,7 @@ impl Editor {
|
|
|
|
|
|
|
|
|
|
|
|
/// clears the scrollback buffer, moving the current edit line to the top of the screen, but
|
|
|
|
/// clears the scrollback buffer, moving the current edit line to the top of the screen, but
|
|
|
|
/// leaving the edit cursor in place
|
|
|
|
/// leaving the edit cursor in place
|
|
|
|
pub fn clear_screen(&mut self) -> anyhow::Result<()> {
|
|
|
|
pub fn clear_screen(&mut self) -> Result<()> {
|
|
|
|
info!("» clear");
|
|
|
|
info!("» clear");
|
|
|
|
self.display.clear()?;
|
|
|
|
self.display.clear()?;
|
|
|
|
self.prompt.print(&mut self.display)?;
|
|
|
|
self.prompt.print(&mut self.display)?;
|
|
|
@ -352,14 +371,14 @@ impl Editor {
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn show_prompt(&mut self) -> anyhow::Result<()> {
|
|
|
|
pub fn show_prompt(&mut self) -> Result<()> {
|
|
|
|
self.reset()?;
|
|
|
|
self.reset()?;
|
|
|
|
self.prompt.print(&mut self.display)?;
|
|
|
|
self.prompt.print(&mut self.display)?;
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// inserts a character at edit cursor's current position
|
|
|
|
/// inserts a character at edit cursor's current position
|
|
|
|
pub fn insert(&mut self, c: char) -> anyhow::Result<()> {
|
|
|
|
pub fn insert(&mut self, c: char) -> Result<()> {
|
|
|
|
self.buffer.insert(c);
|
|
|
|
self.buffer.insert(c);
|
|
|
|
|
|
|
|
|
|
|
|
let tail = self.buffer.tail();
|
|
|
|
let tail = self.buffer.tail();
|
|
|
@ -375,7 +394,7 @@ impl Editor {
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn backspace(&mut self) -> anyhow::Result<()> {
|
|
|
|
pub fn backspace(&mut self) -> Result<()> {
|
|
|
|
if !self.buffer.backspace() {
|
|
|
|
if !self.buffer.backspace() {
|
|
|
|
return Ok(());
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -397,7 +416,7 @@ impl Editor {
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn reset(&mut self) -> anyhow::Result<()> {
|
|
|
|
pub fn reset(&mut self) -> Result<()> {
|
|
|
|
self.buffer.clear();
|
|
|
|
self.buffer.clear();
|
|
|
|
self.display.reset()?;
|
|
|
|
self.display.reset()?;
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|