main
Jordan Orelli 7 months ago
parent 11088138af
commit 5676c6ddcc

@ -1,7 +1,16 @@
/// changes directory
mod cd;
/// prints stuff
mod echo;
/// prints environment variables
mod printenv;
/// the butt of a file
mod tail;
/// locates a binary on our PATH
mod which;
use crate::{
@ -10,6 +19,7 @@ use crate::{
};
use std::collections::HashMap;
/// All of the builtin commands that exist
#[derive(Clone, Copy)]
pub enum Builtin {
Changedir,
@ -38,6 +48,7 @@ impl Call for Builtin {
}
}
/// A mapping of all of the builtin command values to their names
pub fn all() -> HashMap<&'static str, Builtin> {
use Builtin::*;
HashMap::from([

@ -5,11 +5,12 @@ use crate::{
prompt::Prompt,
};
use anyhow::Result;
use std::{cmp::min, io::Write};
/// The text buffer of a text editor.
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>,
/// the position of our edit cursor within [Self::chars]
@ -17,6 +18,7 @@ pub struct Buffer {
}
impl Buffer {
/// an empty buffer
pub fn new() -> Self {
Self {
chars: Vec::new(),
@ -31,18 +33,21 @@ impl Buffer {
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();
self.clear();
self.show_debug();
s
}
pub fn show(&self) -> String {
fn show(&self) -> String {
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 n > self.cursor {
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.cursor > 0 {
self.cursor -= 1;
@ -70,7 +76,7 @@ impl Buffer {
}
/// 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 {
let n = self.chars.drain(..self.cursor).count();
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
/// the edit cursor before the move.
pub fn seek_left(&mut self) -> usize {
fn seek_left(&mut self) -> usize {
let n = self.cursor;
self.cursor = 0;
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
/// cursor before the move.
pub fn seek_right(&mut self) -> usize {
fn seek_right(&mut self) -> usize {
let n = self.chars.len() - self.cursor;
self.cursor = self.chars.len();
self.show_debug();
@ -100,7 +106,7 @@ impl Buffer {
}
/// 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() {
self.cursor = min(self.chars.len(), self.cursor + n);
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.cursor += 1;
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;
if start > 0 {
start -= 1;
@ -126,11 +134,13 @@ impl Buffer {
chars.iter().collect()
}
pub fn pos(&self) -> usize {
/// the current position of the edit cursor
fn pos(&self) -> usize {
self.cursor
}
pub fn len(&self) -> usize {
/// the length of the text buffer
fn len(&self) -> usize {
self.chars.len()
}
@ -157,16 +167,22 @@ impl Buffer {
}
}
/// Describes how we handle an action from the editor
enum Status {
/// Editing is still in progress
Ongoing,
/// A command has been entered in the editor and should be sent to the interpreter
Submit(String),
/// The edit session has ended and the shell should shut down
Done,
}
/// A primitive terminal-based text editor
pub struct Editor {
/// the incoming terminal events
pub(crate) input: input::Reader,
input: input::Reader,
/// the in-memory representation of the command that we're currently editing
buffer: Buffer,
@ -174,11 +190,12 @@ pub struct Editor {
/// our outgoing terminal events, which is used as the display portion of the editor
display: output::Writer,
/// this thing draws our prompt
prompt: Prompt,
}
impl Editor {
pub fn new() -> anyhow::Result<Self> {
pub fn new() -> Result<Self> {
Ok(Self {
input: input::Reader::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::*;
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::*;
match event {
_ if event.code == ENTER || event.char == '\r' => return self.submit(),
@ -241,7 +260,7 @@ impl Editor {
Ok(Status::Ongoing)
}
fn handle_escape(&mut self, esc: Escape) -> anyhow::Result<Status> {
fn handle_escape(&mut self, esc: Escape) -> Result<Status> {
use Escape::*;
match esc {
Left => self.back(1)?,
@ -253,7 +272,7 @@ impl Editor {
Ok(Status::Ongoing)
}
fn handle_control(&mut self, cc: ControlCharacter) -> anyhow::Result<Status> {
fn handle_control(&mut self, cc: ControlCharacter) -> Result<Status> {
use ControlCharacter::*;
match cc {
StartOfHeading => self.seek_left()?,
@ -267,12 +286,12 @@ impl Editor {
Ok(Status::Ongoing)
}
fn insert_dot(&mut self) -> anyhow::Result<()> {
fn insert_dot(&mut self) -> Result<()> {
self.insert('\u{2022}')?;
Ok(())
}
fn submit(&mut self) -> anyhow::Result<Status> {
fn submit(&mut self) -> Result<Status> {
self.display.newline()?;
let text = self.buffer.pop();
info!("◇ {}", text);
@ -284,7 +303,7 @@ impl Editor {
fn focus_end(&mut self) {}
/// 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!("⛬ ←");
if self.buffer.back(n) {
self.display.back(n)?;
@ -293,7 +312,7 @@ impl Editor {
}
/// 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!("⛬ →");
if self.buffer.forward(n) {
self.display.forward(n)?;
@ -302,7 +321,7 @@ impl Editor {
}
/// 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!("»");
let n = self.buffer.seek_right();
if n > 0 {
@ -313,7 +332,7 @@ impl Editor {
}
/// 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!("«");
let n = self.buffer.seek_left();
if n > 0 {
@ -324,7 +343,7 @@ impl Editor {
}
/// 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");
let n = self.buffer.clear_left();
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
/// leaving the edit cursor in place
pub fn clear_screen(&mut self) -> anyhow::Result<()> {
pub fn clear_screen(&mut self) -> Result<()> {
info!("» clear");
self.display.clear()?;
self.prompt.print(&mut self.display)?;
@ -352,14 +371,14 @@ impl Editor {
Ok(())
}
pub fn show_prompt(&mut self) -> anyhow::Result<()> {
pub fn show_prompt(&mut self) -> Result<()> {
self.reset()?;
self.prompt.print(&mut self.display)?;
Ok(())
}
/// 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);
let tail = self.buffer.tail();
@ -375,7 +394,7 @@ impl Editor {
Ok(())
}
pub fn backspace(&mut self) -> anyhow::Result<()> {
pub fn backspace(&mut self) -> Result<()> {
if !self.buffer.backspace() {
return Ok(());
}
@ -397,7 +416,7 @@ impl Editor {
Ok(())
}
pub fn reset(&mut self) -> anyhow::Result<()> {
pub fn reset(&mut self) -> Result<()> {
self.buffer.clear();
self.display.reset()?;
Ok(())

@ -329,12 +329,14 @@ impl From<Console::INPUT_RECORD> for Event {
}
}
/// A reference to a target node in a prefix tree, used for manipulating the prefix tree
#[derive(Debug)]
pub struct EscapeCursor {
target: Rc<EscapeNode>,
root: Rc<EscapeNode>,
}
/// A node in a prefix tree. This prefix tree is used to parse strings into escape sequences
#[derive(Debug)]
pub enum EscapeNode {
Root {
@ -436,10 +438,12 @@ impl EscapeCursor {
}
}
/// adds a terminal node into the tree as a child of the current node
fn add_terminal(&mut self, c: char, v: Escape) {
self.target.add_child_terminal(c, v);
}
/// resets the cursor back to the top of the tree
fn reset(&mut self) {
self.target = Rc::clone(&self.root);
}
@ -448,6 +452,7 @@ impl EscapeCursor {
Rc::ptr_eq(&self.target, &self.root)
}
/// inserts a sequence -> Escape mapping into the prefix tree
fn insert(&mut self, sequence: &str, v: Escape) {
self.reset();
let mut chars = sequence.chars().peekable();
@ -462,8 +467,10 @@ impl EscapeCursor {
}
}
/// generates a prefix tree used for parsing escape sequences
macro_rules! escapes {
($($sequence:literal $variant:tt)*) => {
/// a parsed escape sequence
#[derive(Debug, Clone, Copy)]
pub enum Escape {
Empty,
@ -472,7 +479,10 @@ macro_rules! escapes {
)*
}
pub fn build_prefix_tree() -> EscapeCursor {
/// assembles our prefix tree. Generally only needs to be called once. I think this could
/// actually be done in a proc macro that builds the prefix tree at compile time instead of
/// at run time.
fn build_prefix_tree() -> EscapeCursor {
let mut tree = EscapeNode::new();
$(
let v = Escape::$variant;
@ -521,7 +531,8 @@ escapes! {
"[53;5u" CtrlShift5
}
/// a control character
/// a control character. Control characters are single individual ascii characters that represent
/// out of band signalling.
#[derive(Debug)]
pub enum ControlCharacter {
Null,

@ -1,15 +1,18 @@
use std::fmt;
use windows::Win32::System::Console;
/// an abstract keycode
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Code {
/// The integer value of the keycode
/// The integer value of the keycode, as defined by the Windows api. These associate to windows
/// virtual key codes.
pub val: u16,
/// The unicode symbol that represents the keycode, if any
pub sym: Option<char>,
}
/// An individual keypress event
#[derive(Debug)]
pub struct Event {
/// True for key press events, false for key release events
@ -31,6 +34,7 @@ pub struct Event {
/// relationship to the user's chosen layout.
pub scancode: u16,
/// the untranslated windows virtual keycode
pub keycode: u16,
/// The unicode character of the Event. Note this these correspond to virtual terminal
@ -107,6 +111,7 @@ impl fmt::Display for Event {
macro_rules! codes {
($($val:literal $name:ident $sym:literal)*) => {
/// stores our key code constants
pub mod codes {
use super::Code;
$(

@ -60,6 +60,8 @@ impl From<Vec<Glyph>> for Lexeme {
}
}
/// The smallest lexical unit of our language. A token represents a single element of program
/// source text, such as an identifier or a piece of punctuation.
#[allow(dead_code)]
#[derive(Debug, PartialEq, Clone)]
pub enum Token {
@ -103,8 +105,9 @@ impl Token {
}
}
/// Tokenizer splits some input [Glyphs] into [Token] values
pub struct Tokenizer<'text> {
/// Tokenizer splits some input [Glyphs] into [Token] values. The Tokenize has no lookahead or
/// buffering.
struct Tokenizer<'text> {
source: Glyphs<'text>,
}
@ -199,6 +202,7 @@ impl<'text> Iterator for Tokenizer<'text> {
}
}
/// A Lexer is responsible for converting a piece of source text into a sequence of tokens.
pub struct Lexer<'text> {
source: Tokenizer<'text>,
lookahead: VecDeque<Token>,
@ -223,7 +227,7 @@ impl<'text> Lexer<'text> {
Ok(true)
}
pub fn peek_at(&mut self, idx: usize) -> Result<Option<&Token>, LexError> {
fn peek_at(&mut self, idx: usize) -> Result<Option<&Token>, LexError> {
self.fill_lookahead(idx + 1)?;
Ok(self.lookahead.get(idx))
}

@ -19,7 +19,10 @@ mod lex;
/// a real primitive line editor
mod edit;
/// logging configuration
mod log;
/// stdout control, output more generally
mod output;
/// turns our tokens into parse trees
@ -43,28 +46,5 @@ fn main() -> anyhow::Result<()> {
#[cfg(debug_assertions)]
session.enable_logging("~/clyde.log");
session.run()?;
Ok(())
/*
loop {
match session.input.next()? {
input::Event::Key(event) => {
// CTRL-J to draw a cool little dot
if event.ctrl && event.code == key::J {
debug!("⎈ j: dot");
// red bullet
session
.stdout
.write(String::from("\x1b[31m\u{2022}\x1b[0m").as_bytes())?;
continue;
}
warn!("‽ {}", event);
}
input::Event::Up => debug!("⛬ ↑"),
input::Event::Down => debug!("⛬ ↓"),
}
}
*/
session.run()
}

@ -72,6 +72,7 @@ impl PartialEq for Node {
}
}
/// Iterates through the children of a cursor node, producing new cursor positions.
pub struct ChildIter {
target: Rc<Node>,
root: Rc<Node>,
@ -179,6 +180,7 @@ impl Cursor {
}
}
/// The Parser is responsible for turning a sequence of Tokens into a Parse Tree.
pub struct Parser<'text> {
input: Lexer<'text>,
output: Cursor,

@ -1,7 +1,8 @@
use crate::output;
use std::io::Write;
use anyhow::Result;
use std::io::Write;
/// draws the prompt that appears before a user enters a command
pub struct Prompt {
s: String,
}
@ -21,12 +22,13 @@ impl Prompt {
pub fn print(&self, output: &mut output::Writer) -> Result<()> {
match std::env::current_dir() {
Ok(d) => {
let text = d.to_str().unwrap().to_owned() + &self.s;
output.write(text.as_bytes())?;
_ = write!(output, "{cwd}{arrow}", cwd = d.display(), arrow = self.s);
// let text = d.to_str().unwrap().to_owned() + &self.s;
// output.write(text.as_bytes())?;
}
Err(_) => {
output.write(self.s.as_bytes())?;
},
_ = write!(output, "{}", self.s);
}
}
Ok(())
}

@ -106,6 +106,8 @@ impl Eval for Command {
}
}
/// the TreeBuilder is responsible for converting a parse tree into an abstract syntax tree. This
/// process leaves us with a tree that can be executed directly
struct TreeBuilder {
visited: HashSet<usize>,
}

Loading…
Cancel
Save