diff --git a/src/line.rs b/src/edit.rs similarity index 98% rename from src/line.rs rename to src/edit.rs index e427e62..0486b1f 100644 --- a/src/line.rs +++ b/src/edit.rs @@ -1,7 +1,7 @@ use crate::log::*; use std::cmp::min; -pub struct Line { +pub struct Editor { /// the current contents of the line chars: Vec, @@ -9,7 +9,7 @@ pub struct Line { cursor: usize, } -impl Line { +impl Editor { pub fn new() -> Self { Self { chars: Vec::new(), diff --git a/src/input.rs b/src/input.rs index 5e86dd5..9ad86c4 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,8 +1,8 @@ use crate::{error::Error, key, log::*}; use anyhow::{Context, Result}; +use macros::escapes; use windows::Win32::Foundation::HANDLE; use windows::Win32::System::Console; -use macros::escapes; #[allow(dead_code)] fn log_input_mode(mode: Console::CONSOLE_MODE) { @@ -214,49 +214,79 @@ impl Reader { ';' => match self.next_escape_char()? { '5' => match self.next_escape_char()? { 'u' => Ok(Event::Drop(String::from("13;5u"))), - e => Err(Error::input_error(format!("[13;5 unexpected escape char: {}", e)).into()), - } - e => Err(Error::input_error(format!("[13; unexpected escape char: {}", e)).into()), - } - e => Err(Error::input_error(format!("[13 unexpected escape char: {}", e)).into()), - } + e => Err(Error::input_error(format!( + "[13;5 unexpected escape char: {}", + e + )) + .into()), + }, + e => Err(Error::input_error(format!( + "[13; unexpected escape char: {}", + e + )) + .into()), + }, + e => Err( + Error::input_error(format!("[13 unexpected escape char: {}", e)).into(), + ), + }, '5' => match self.next_escape_char()? { '~' => Ok(Event::Drop(String::from("[15~ - F5"))), - e => Err(Error::input_error(format!("[15 unexpected escape char: {}", e)).into()), - } + e => Err( + Error::input_error(format!("[15 unexpected escape char: {}", e)).into(), + ), + }, '7' => match self.next_escape_char()? { '~' => Ok(Event::Drop(String::from("[17~ - F6"))), - e => Err(Error::input_error(format!("[17 unexpected escape char: {}", e)).into()), - } + e => Err( + Error::input_error(format!("[17 unexpected escape char: {}", e)).into(), + ), + }, '8' => match self.next_escape_char()? { '~' => Ok(Event::Drop(String::from("[18~ - F7"))), - e => Err(Error::input_error(format!("[18 unexpected escape char: {}", e)).into()), - } + e => Err( + Error::input_error(format!("[18 unexpected escape char: {}", e)).into(), + ), + }, '9' => match self.next_escape_char()? { '~' => Ok(Event::Drop(String::from("[19~ - F8"))), - e => Err(Error::input_error(format!("[19 unexpected escape char: {}", e)).into()), + e => Err( + Error::input_error(format!("[19 unexpected escape char: {}", e)).into(), + ), + }, + e => { + Err(Error::input_error(format!("[1 unexpected escape char: {}", e)).into()) } - e => Err(Error::input_error(format!("[1 unexpected escape char: {}", e)).into()), }, '2' => match self.next_escape_char()? { '0' => match self.next_escape_char()? { '~' => Ok(Event::Drop(String::from("[20~ - F9"))), - e => Err(Error::input_error(format!("[20 unexpected escape char: {}", e)).into()), - } + e => Err( + Error::input_error(format!("[20 unexpected escape char: {}", e)).into(), + ), + }, '1' => match self.next_escape_char()? { '~' => Ok(Event::Drop(String::from("[21~ - F10"))), - e => Err(Error::input_error(format!("[20 unexpected escape char: {}", e)).into()), - } + e => Err( + Error::input_error(format!("[20 unexpected escape char: {}", e)).into(), + ), + }, '3' => match self.next_escape_char()? { '~' => Ok(Event::Drop(String::from("[23~ - F11"))), - e => Err(Error::input_error(format!("[23 unexpected escape char: {}", e)).into()), - } + e => Err( + Error::input_error(format!("[23 unexpected escape char: {}", e)).into(), + ), + }, '4' => match self.next_escape_char()? { '~' => Ok(Event::Drop(String::from("[24~ - F12"))), - e => Err(Error::input_error(format!("[24 unexpected escape char: {}", e)).into()), + e => Err( + Error::input_error(format!("[24 unexpected escape char: {}", e)).into(), + ), + }, + e => { + Err(Error::input_error(format!("[2 unexpected escape char: {}", e)).into()) } - e => Err(Error::input_error(format!("[2 unexpected escape char: {}", e)).into()), - } + }, e => Err(Error::input_error(format!("[ unexpected escape char: {}", e)).into()), }, 'O' => match self.next_escape_char()? { @@ -265,27 +295,11 @@ impl Reader { 'R' => Ok(Event::Drop(String::from("OR - F3"))), 'S' => Ok(Event::Drop(String::from("OS - F4"))), e => Err(Error::input_error(format!("O unexpected escape char: {}", e)).into()), - } + }, e => Err(Error::input_error(format!("unexpected escape char: {}", e)).into()), } } - // fn take_bracket(&mut self) -> Result<()> { - // let rec = self.next_rec()?; - // if rec.EventType as u32 != Console::KEY_EVENT { - // Err(Error::input_error("failed to read escape sequence: not a key event").into()) - // } else { - // unsafe { - // let event = rec.Event.KeyEvent; - // if event.wVirtualKeyCode == 0 && event.uChar.UnicodeChar == 91 { - // Ok(()) - // } else { - // Err(Error::input_error("failed to read escape sequence: not a [").into()) - // } - // } - // } - // } - fn next_escape_char(&mut self) -> Result { let rec = self.next_rec()?; if rec.EventType as u32 != Console::KEY_EVENT { @@ -416,5 +430,3 @@ escapes! { "[24~" F12 "[53;5u" Ctrl_Shift_5 } -/* -*/ diff --git a/src/shell.rs b/src/interactive.rs similarity index 94% rename from src/shell.rs rename to src/interactive.rs index 5ff4863..4d7965a 100644 --- a/src/shell.rs +++ b/src/interactive.rs @@ -1,7 +1,7 @@ use crate::{ + edit::Editor, ext::{Command, Echo, Printenv, Tail, Which}, input, - line::Line, log::*, output, syntax, }; @@ -16,26 +16,26 @@ use std::{ use anyhow::Result; use dirs; -pub struct Shell { +pub struct Session { pub input: input::Reader, pub output: output::Writer, - pub line: Line, + pub editor: Editor, pub state: syntax::State, } -impl Shell { +impl Session { pub fn new() -> Result { Ok(Self { input: input::Reader::new()?, output: output::Writer::stdout()?, - line: Line::new(), + editor: Editor::new(), state: syntax::State::new(), }) } pub fn back(&mut self, n: usize) -> Result<()> { debug!("⛬ ←"); - if self.line.back(n) { + if self.editor.back(n) { self.output.back(n)?; } Ok(()) @@ -43,7 +43,7 @@ impl Shell { pub fn forward(&mut self, n: usize) -> Result<()> { debug!("⛬ →"); - if self.line.forward(n) { + if self.editor.forward(n) { self.output.forward(n)?; } Ok(()) @@ -57,7 +57,7 @@ impl Shell { pub fn seek_right(&mut self) -> Result<()> { info!("»"); - let n = self.line.seek_right(); + let n = self.editor.seek_right(); if n > 0 { // move right by the distance seeked self.output.forward(n)?; @@ -67,7 +67,7 @@ impl Shell { pub fn seek_left(&mut self) -> Result<()> { info!("«"); - let n = self.line.seek_left(); + let n = self.editor.seek_left(); if n > 0 { // move left by the distance seeked self.output.back(n)?; diff --git a/src/main.rs b/src/main.rs index 82e3c94..9b537f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,9 @@ mod ext; /// handles input from terminals mod input; +/// defines the interactive mode +mod interactive; + /// key presses, independent from input sources mod key; @@ -18,7 +21,7 @@ mod key; mod lex; /// a real primitive line editor -mod line; +mod edit; mod log; mod output; @@ -27,7 +30,6 @@ mod output; mod parse; mod prompt; -mod shell; /// syntax and semantic analysis mod syntax; @@ -35,25 +37,22 @@ mod syntax; /// topoglyph is a real word, i promise mod topo; -use crate::log::*; -use prompt::Prompt; -use shell::Shell; -use syntax::Eval; +use crate::{interactive::Session, log::*, prompt::Prompt, syntax::Eval}; use std::io::Write; use anyhow::Result; fn main() -> Result<()> { - let mut shell = Shell::new()?; - shell.enable_logging("~/clyde.log"); + let mut session = Session::new()?; + session.enable_logging("~/clyde.log"); let prompt = Prompt::new(); - prompt.print(&mut shell.output)?; + prompt.print(&mut session.output)?; info!("» shell session start --------"); loop { - match shell.input.next()? { + match session.input.next()? { input::Event::Key(event) => { if event.down { if event.code.val == 0 { @@ -66,18 +65,18 @@ fn main() -> Result<()> { info!(" {}", event); if event.code == key::LEFT { - shell.back(1)?; + session.back(1)?; continue; } if event.code == key::RIGHT { - shell.forward(1)?; + session.forward(1)?; continue; } if event.code == key::ENTER { - shell.output.newline()?; - let s = shell.line.pop(); + session.output.newline()?; + let s = session.editor.pop(); info!("◇ {}", s); if let Ok(tokens) = lex::lex(&s) { for t in tokens { @@ -90,19 +89,19 @@ fn main() -> Result<()> { let mut state = syntax::State::new(); if let Err(e) = tree.eval(&mut state) { error!("{e:?}"); - shell.render_error(e); + _ = session.render_error(e); } } Err(e) => { error!("{e:?}"); - shell.render_error(e); + _ = session.render_error(e); } } // shell.exec(tree.into())?; // Some commands don't leave the terminal in a clean state, so we use reset // to ensure that our input and output modes are what we expect them to be. - shell.reset()?; - prompt.print(&mut shell.output)?; + session.reset()?; + prompt.print(&mut session.output)?; continue; } @@ -111,22 +110,22 @@ fn main() -> Result<()> { } if event.code == key::BACKSPACE { - if shell.line.backspace() { + if session.editor.backspace() { // move cursor back two spaces - shell.output.back(2)?; - let tail = format!("{} ", shell.line.tail()); + session.output.back(2)?; + let tail = format!("{} ", session.editor.tail()); let n = tail.chars().count(); - shell.output.write(tail.as_bytes())?; + session.output.write(tail.as_bytes())?; // after writing out the tail, rewind by the number of characters in // the tail if n > 1 { // let text = format!("\x1b[{}D", n - 1); - shell.output.back(n - 1)?; + session.output.back(n - 1)?; // output.write(text.as_bytes())?; } else { // honestly I can't remember how I figured this out - shell.output.write(b" \x1b[1D")?; + session.output.write(b" \x1b[1D")?; } } continue; @@ -135,7 +134,7 @@ fn main() -> Result<()> { // CTRL-D to exit if event.ctrl && event.code == key::D { info!("» exit"); - shell.output.close()?; + session.output.close()?; return Ok(()); } @@ -143,7 +142,7 @@ fn main() -> Result<()> { if event.ctrl && event.code == key::J { debug!("⎈ j: dot"); // red bullet - shell + session .output .write(String::from("\x1b[31m\u{2022}\x1b[0m").as_bytes())?; continue; @@ -152,69 +151,71 @@ fn main() -> Result<()> { // CTRL-L to clear the screen if event.ctrl && event.code == key::L { info!("» clear"); - shell.output.clear()?; - prompt.print(&mut shell.output)?; - shell.output.write(shell.line.show().as_bytes())?; - shell.output.back(shell.line.len() - shell.line.pos())?; - shell.reset()?; + session.output.clear()?; + prompt.print(&mut session.output)?; + session.output.write(session.editor.show().as_bytes())?; + session + .output + .back(session.editor.len() - session.editor.pos())?; + session.reset()?; continue; } // CTRL-U to erase to the beginning of the line if event.ctrl && event.code == key::U { info!("» clear left"); - let n = shell.line.clear_left(); + let n = session.editor.clear_left(); if n > 0 { // move left by the number of elements removed - shell.output.back(n)?; + session.output.back(n)?; // draw the elements remaining, followed by a space for each removed // element - let kept = shell.line.show(); + let kept = session.editor.show(); let text = format!("{}{:width$}", kept, "", width = n); - shell.output.write(text.as_bytes())?; - shell.output.back(n + kept.chars().count())?; + session.output.write(text.as_bytes())?; + session.output.back(n + kept.chars().count())?; } continue; } // CTRL-A to move to the beginning of the line if event.ctrl && event.code == key::A { - shell.seek_left()?; + session.seek_left()?; continue; } // CTRL-E to move to the end of the line if event.ctrl && event.code == key::E { - shell.seek_right()?; + session.seek_right()?; continue; } // TODO: something better here, this is crappy. I should be checking characters // based on their unicode categories, not this garbo if !event.char.is_control() { - shell.line.insert(event.char); + session.editor.insert(event.char); - let tail = shell.line.tail(); + let tail = session.editor.tail(); let n = tail.chars().count(); // write everything from the current line cursor out to the output buffer. - shell.output.write(tail.as_bytes())?; + session.output.write(tail.as_bytes())?; if n > 1 { // if we wrote more than one character, because we weren't at the end, we // need to rewind the terminal cursor to where it was. - shell.output.back(n - 1)?; + session.output.back(n - 1)?; } continue; } warn!("‽ {}", event); } - input::Event::Left => shell.back(1)?, - input::Event::Right => shell.forward(1)?, + input::Event::Left => session.back(1)?, + input::Event::Right => session.forward(1)?, input::Event::Up => debug!("⛬ ↑"), input::Event::Down => debug!("⛬ ↓"), - input::Event::Home => shell.seek_left()?, - input::Event::End => shell.seek_right()?, + input::Event::Home => session.seek_left()?, + input::Event::End => session.seek_right()?, input::Event::Focus(true) => {} input::Event::Focus(false) => {} input::Event::Menu(_command_id) => {}