cleaning up shell state

this sure is tedious
parse-tree
Jordan Orelli 2 years ago
parent a6fa189490
commit 15e3ca93cc

@ -94,41 +94,10 @@ fn stdin_handle() -> Result<HANDLE> {
} }
} }
fn setup_stdin() -> Result<()> {
let mut mode = Console::CONSOLE_MODE(0);
unsafe {
Console::SetConsoleCP(65001);
let handle = stdin_handle()?;
Error::check(Console::GetConsoleMode(handle, &mut mode))?;
// allow terminal input characters
mode |= Console::ENABLE_VIRTUAL_TERMINAL_INPUT;
// disable automatic processing of CTRL+C, we'll handle it ourselves
mode &= !Console::ENABLE_PROCESSED_INPUT;
// disable line mode to get every input as its pressed
mode &= !Console::ENABLE_LINE_INPUT;
// disable automatic echoing of inputs
mode &= !Console::ENABLE_ECHO_INPUT;
// enable mouse input
mode |= Console::ENABLE_MOUSE_INPUT;
Error::check(Console::SetConsoleMode(handle, mode))?;
Error::check(Console::GetConsoleMode(handle, &mut mode))?;
// debug!("Stdin details:");
// log_input_mode(mode);
}
Ok(())
}
pub struct Reader { pub struct Reader {
input: HANDLE, input: HANDLE,
// this area in memory is where the Windows API will write events read off of the keyboard. // this area in memory is where the Windows API will write events read off of the keyboard.
buf: [Console::INPUT_RECORD; 100], buf: [Console::INPUT_RECORD; 32],
// the number of valid input record items in the buf since last read // the number of valid input record items in the buf since last read
buf_len: u32, buf_len: u32,
// the position of our current indexer into the input record buffer // the position of our current indexer into the input record buffer
@ -137,13 +106,45 @@ pub struct Reader {
impl Reader { impl Reader {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
setup_stdin()?; let v = Self {
Ok(Self { buf: [Console::INPUT_RECORD::default(); 32],
buf: [Console::INPUT_RECORD::default(); 100],
buf_len: 0, buf_len: 0,
buf_idx: 0, buf_idx: 0,
input: stdin_handle()?, input: stdin_handle()?,
}) };
v.reset()?;
Ok(v)
}
pub fn reset(&self) -> Result<()> {
let mut mode = Console::CONSOLE_MODE(0);
unsafe {
Console::SetConsoleCP(65001);
let handle = stdin_handle()?;
Error::check(Console::GetConsoleMode(handle, &mut mode))?;
// allow terminal input characters
mode |= Console::ENABLE_VIRTUAL_TERMINAL_INPUT;
// disable automatic processing of CTRL+C, we'll handle it ourselves
mode &= !Console::ENABLE_PROCESSED_INPUT;
// disable line mode to get every input as its pressed
mode &= !Console::ENABLE_LINE_INPUT;
// disable automatic echoing of inputs
mode &= !Console::ENABLE_ECHO_INPUT;
// enable mouse input
mode |= Console::ENABLE_MOUSE_INPUT;
Error::check(Console::SetConsoleMode(handle, mode))?;
Error::check(Console::GetConsoleMode(handle, &mut mode))?;
// debug!("Stdin details:");
// log_input_mode(mode);
}
Ok(())
} }
pub fn next(&mut self) -> Result<Event> { pub fn next(&mut self) -> Result<Event> {

@ -1,4 +1,5 @@
use crate::log::*; use crate::log::*;
use std::cmp::min;
pub struct Line { pub struct Line {
/// the current contents of the line /// the current contents of the line
@ -33,9 +34,13 @@ impl Line {
self.chars.iter().collect() self.chars.iter().collect()
} }
pub fn back(&mut self) -> bool { pub fn back(&mut self, n: usize) -> bool {
if self.cursor > 0 { if self.cursor > 0 {
self.cursor -= 1; if n > self.cursor {
self.cursor = n;
} else {
self.cursor -= n;
}
self.show_debug(); self.show_debug();
true true
} else { } else {
@ -83,9 +88,9 @@ impl Line {
n n
} }
pub fn forward(&mut self) -> bool { pub fn forward(&mut self, n: usize) -> bool {
if self.cursor < self.chars.len() { if self.cursor < self.chars.len() {
self.cursor += 1; self.cursor = min(self.chars.len(), self.cursor + n);
self.show_debug(); self.show_debug();
true true
} else { } else {
@ -109,7 +114,33 @@ impl Line {
chars.iter().collect() chars.iter().collect()
} }
pub fn pos(&self) -> usize {
self.cursor
}
pub fn len(&self) -> usize {
self.chars.len()
}
fn show_debug(&self) { fn show_debug(&self) {
debug!(" {: <5} {: >5} {}", self.cursor, self.chars.len(), self.show()); let before = if self.chars.len() > 0 {
self.chars[0..self.cursor].to_vec().iter().collect()
} else {
String::new()
};
let after = if self.chars.len() > 0 {
self.chars[self.cursor..].to_vec().iter().collect()
} else {
String::new()
};
debug!(
" {: <5} {: >5} {}·{}",
self.cursor,
self.chars.len(),
before,
after
);
} }
} }

@ -7,7 +7,6 @@ mod output;
mod shell; mod shell;
mod prompt; mod prompt;
use line::Line;
use prompt::Prompt; use prompt::Prompt;
use std::io::Write; use std::io::Write;
@ -27,16 +26,13 @@ fn main() -> Result<()> {
} }
} }
let mut line = Line::new();
let prompt = Prompt::new(); let prompt = Prompt::new();
let mut input = input::Reader::new()?; let mut shell = Shell::new()?;
let mut output = output::Writer::new()?;
let shell = Shell::new();
prompt.print(&mut output)?; prompt.print(&mut shell.output)?;
info!("» enter"); info!("» enter");
loop { loop {
match input.next()? { match shell.input.next()? {
input::Event::Key(event) => { input::Event::Key(event) => {
if event.down { if event.down {
if event.code.val == 0 { if event.code.val == 0 {
@ -48,9 +44,19 @@ fn main() -> Result<()> {
} }
info!(" {}", event); info!(" {}", event);
if event.code == key::LEFT {
shell.back(1)?;
continue;
}
if event.code == key::RIGHT {
shell.forward(1)?;
continue;
}
if event.code == key::ENTER { if event.code == key::ENTER {
output.newline()?; shell.output.newline()?;
let s = line.pop(); let s = shell.line.pop();
let parts: Vec<&str> = s.split_whitespace().collect(); let parts: Vec<&str> = s.split_whitespace().collect();
if parts.len() > 0 { if parts.len() > 0 {
let cmd = parts[0].to_string(); let cmd = parts[0].to_string();
@ -60,7 +66,7 @@ fn main() -> Result<()> {
vec![] vec![]
}; };
debug!("◇ {} {}", cmd.clone(), args.join(" ")); debug!("◇ {} {}", cmd.clone(), args.join(" "));
match shell.eval(&mut output, cmd.clone(), args.clone()) { match shell.eval(cmd.clone(), args.clone()) {
Ok(true) => info!("▷ {} {}", cmd, args.join(" ")), Ok(true) => info!("▷ {} {}", cmd, args.join(" ")),
Ok(false) => warn!("▷ {} {}", cmd, args.join(" ")), Ok(false) => warn!("▷ {} {}", cmd, args.join(" ")),
Err(e) => { Err(e) => {
@ -68,8 +74,11 @@ fn main() -> Result<()> {
println!("error: {}", e); println!("error: {}", e);
} }
} }
// 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 output)?; prompt.print(&mut shell.output)?;
continue; continue;
} }
@ -78,22 +87,22 @@ fn main() -> Result<()> {
} }
if event.code == key::BACKSPACE { if event.code == key::BACKSPACE {
if line.backspace() { if shell.line.backspace() {
// move cursor back two spaces // move cursor back two spaces
output.back(2)?; shell.output.back(2)?;
let tail = format!("{} ", line.tail()); let tail = format!("{} ", shell.line.tail());
let n = tail.chars().count(); let n = tail.chars().count();
output.write(tail.as_bytes())?; shell.output.write(tail.as_bytes())?;
// after writing out the tail, rewind by the number of characters in // after writing out the tail, rewind by the number of characters in
// the tail // the tail
if n > 1 { if n > 1 {
// let text = format!("\x1b[{}D", n - 1); // let text = format!("\x1b[{}D", n - 1);
output.back(n-1)?; shell.output.back(n-1)?;
// output.write(text.as_bytes())?; // output.write(text.as_bytes())?;
} else { } else {
// honestly I can't remember how I figured this out // honestly I can't remember how I figured this out
output.write(b" \x1b[1D")?; shell.output.write(b" \x1b[1D")?;
} }
} }
continue; continue;
@ -102,7 +111,7 @@ fn main() -> Result<()> {
// CTRL-D to exit // CTRL-D to exit
if event.ctrl && event.code == key::D { if event.ctrl && event.code == key::D {
info!("» exit"); info!("» exit");
output.close()?; shell.output.close()?;
return Ok(()); return Ok(());
} }
@ -110,98 +119,80 @@ fn main() -> Result<()> {
if event.ctrl && event.code == key::J { if event.ctrl && event.code == key::J {
debug!("⎈ j: dot"); debug!("⎈ j: dot");
// red bullet // red bullet
output.write(String::from("\x1b[31m\u{2022}\x1b[0m").as_bytes())?; shell.output.write(String::from("\x1b[31m\u{2022}\x1b[0m").as_bytes())?;
continue; continue;
} }
// CTRL-L to clear the screen // CTRL-L to clear the screen
if event.ctrl && event.code == key::L { if event.ctrl && event.code == key::L {
info!("» clear"); info!("» clear");
output.clear()?; shell.output.clear()?;
prompt.print(&mut output)?; prompt.print(&mut shell.output)?;
output.write(line.show().as_bytes())?; shell.output.write(shell.line.show().as_bytes())?;
shell.output.back(shell.line.len() - shell.line.pos())?;
shell.reset()?;
continue; continue;
} }
// CTRL-U to erase to the beginning of the line // CTRL-U to erase to the beginning of the line
if event.ctrl && event.code == key::U { if event.ctrl && event.code == key::U {
info!("» clear left"); info!("» clear left");
let n = line.clear_left(); let n = shell.line.clear_left();
if n > 0 { if n > 0 {
// move left by the number of elements removed // move left by the number of elements removed
output.back(n)?; shell.output.back(n)?;
// draw the elements remaining, followed by a space for each removed // draw the elements remaining, followed by a space for each removed
// element // element
let kept = line.show(); let kept = shell.line.show();
let text = format!("{}{:width$}", kept, "", width = n); let text = format!("{}{:width$}", kept, "", width = n);
output.write(text.as_bytes())?; shell.output.write(text.as_bytes())?;
output.back(n + kept.chars().count())?; shell.output.back(n + kept.chars().count())?;
} }
continue; continue;
} }
// CTRL-A to move to the beginning of the line // CTRL-A to move to the beginning of the line
if event.ctrl && event.code == key::A { if event.ctrl && event.code == key::A {
info!("» seek left"); shell.seek_left()?;
let n = line.seek_left();
if n > 0 {
// move left by the distance seeked
output.back(n)?;
}
continue; continue;
} }
// CTRL-E to move to the end of the line // CTRL-E to move to the end of the line
if event.ctrl && event.code == key::E { if event.ctrl && event.code == key::E {
info!("» seek right"); shell.seek_right()?;
let n = line.seek_right();
if n > 0 {
// move right by the distance seeked
output.forward(n)?;
}
continue; continue;
} }
// TODO: something better here, this is crappy. I should be checking characters // TODO: something better here, this is crappy. I should be checking characters
// based on their unicode categories, not this garbo // based on their unicode categories, not this garbo
if !event.char.is_control() { if !event.char.is_control() {
line.insert(event.char); shell.line.insert(event.char);
let tail = line.tail(); let tail = shell.line.tail();
let n = tail.chars().count(); let n = tail.chars().count();
// write everything from the current line cursor out to the output buffer. // write everything from the current line cursor out to the output buffer.
output.write(tail.as_bytes())?; shell.output.write(tail.as_bytes())?;
if n > 1 { if n > 1 {
// if we wrote more than one character, because we weren't at the end, we // 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. // need to rewind the terminal cursor to where it was.
output.back(n-1)?; shell.output.back(n-1)?;
} }
continue; continue;
} }
warn!("‽ {}", event); warn!("‽ {}", event);
} }
input::Event::Left => { input::Event::Left => { shell.back(1)? }
debug!("⛬ ←"); input::Event::Right => { shell.forward(1)? }
if line.back() {
output.back(1)?;
}
}
input::Event::Right => {
debug!("⛬ →");
if line.forward() {
output.forward(1)?;
}
}
input::Event::Up => { input::Event::Up => {
debug!("⛬ ↑"); debug!("⛬ ↑");
} }
input::Event::Down => { input::Event::Down => {
debug!("⛬ ↓"); debug!("⛬ ↓");
} }
input::Event::Home => {} input::Event::Home => { shell.seek_left()? }
input::Event::End => {} input::Event::End => { shell.seek_right()? }
input::Event::Focus(true) => {} input::Event::Focus(true) => {}
input::Event::Focus(false) => {} input::Event::Focus(false) => {}
input::Event::Menu(_command_id) => {} input::Event::Menu(_command_id) => {}

@ -94,7 +94,7 @@ fn log_output_mode(mode: Console::CONSOLE_MODE) {
} }
} }
pub fn stdout_handle() -> Result<HANDLE> { fn stdout_handle() -> Result<HANDLE> {
unsafe { unsafe {
let handle = Console::GetStdHandle(Console::STD_OUTPUT_HANDLE) let handle = Console::GetStdHandle(Console::STD_OUTPUT_HANDLE)
.context("unable to get stdin handle")?; .context("unable to get stdin handle")?;
@ -102,34 +102,17 @@ pub fn stdout_handle() -> Result<HANDLE> {
} }
} }
fn setup_stdout() -> Result<()> {
unsafe {
Console::SetConsoleOutputCP(65001);
}
let mut mode = Console::CONSOLE_MODE(0);
unsafe {
let handle = stdout_handle()?;
if Console::GetConsoleMode(handle, &mut mode).as_bool() {
// debug!("Stdout details:");
// log_output_mode(mode);
} else {
return Err(Error::last_error().into());
}
}
Ok(())
}
pub struct Writer { pub struct Writer {
output: HANDLE, output: HANDLE,
} }
impl Writer { impl Writer {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
setup_stdout()?; let mut v = Self {
Ok(Self {
output: stdout_handle()?, output: stdout_handle()?,
}) };
v.reset()?;
Ok(v)
} }
pub fn close(&mut self) -> Result<()> { pub fn close(&mut self) -> Result<()> {
@ -139,6 +122,22 @@ impl Writer {
Ok(()) Ok(())
} }
pub fn reset(&mut self) -> Result<()> {
unsafe {
Console::SetConsoleOutputCP(65001);
}
// let mut mode = Console::CONSOLE_MODE(0);
// unsafe {
// if Console::GetConsoleMode(self.output, &mut mode).as_bool() {
// // debug!("Stdout details:");
// // log_output_mode(mode);
// } else {
// return Err(Error::last_error().into());
// }
// }
Ok(())
}
pub fn newline(&mut self) -> Result<()> { pub fn newline(&mut self) -> Result<()> {
self.write(b"\r\n")?; self.write(b"\r\n")?;
Ok(()) Ok(())

@ -1,18 +1,70 @@
use std::fs::File; use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write}; use std::io::{Read, Seek, SeekFrom, Write};
use crate::output; use crate::output;
use crate::input;
use crate::line::Line;
use crate::log::*;
use anyhow::Result; use anyhow::Result;
pub struct Shell { pub struct Shell {
pub input: input::Reader,
pub output: output::Writer,
pub line: Line,
} }
impl Shell { impl Shell {
pub fn new() -> Self { pub fn new() -> Result<Self> {
Self {} Ok(Self {
input: input::Reader::new()?,
output: output::Writer::new()?,
line: Line::new(),
})
} }
pub fn eval(&self, output: &mut output::Writer, cmd: String, args: Vec<&str>) -> Result<bool> { pub fn back(&mut self, n: usize) -> Result<()> {
debug!("⛬ ←");
if self.line.back(n) {
self.output.back(n)?;
}
Ok(())
}
pub fn forward(&mut self, n: usize) -> Result<()> {
debug!("⛬ →");
if self.line.forward(n) {
self.output.forward(n)?;
}
Ok(())
}
pub fn reset(&mut self) -> Result<()> {
self.input.reset()?;
self.output.reset()?;
Ok(())
}
pub fn seek_right(&mut self) -> Result<()> {
info!("» seek right");
let n = self.line.seek_right();
if n > 0 {
// move right by the distance seeked
self.output.forward(n)?;
}
Ok(())
}
pub fn seek_left(&mut self) -> Result<()> {
info!("» seek left");
let n = self.line.seek_left();
if n > 0 {
// move left by the distance seeked
self.output.back(n)?;
}
Ok(())
}
pub fn eval(&mut self, cmd: String, args: Vec<&str>) -> Result<bool> {
match cmd.as_str() { match cmd.as_str() {
"pwd" => { "pwd" => {
let pb = std::env::current_dir()?; let pb = std::env::current_dir()?;
@ -78,7 +130,7 @@ impl Shell {
if n == 1 { if n == 1 {
buf.push(one_byte[0]); buf.push(one_byte[0]);
if let Ok(s) = std::str::from_utf8(&buf) { if let Ok(s) = std::str::from_utf8(&buf) {
output.write(s.as_bytes())?; self.output.write(s.as_bytes())?;
buf.clear(); buf.clear();
} }
} }

Loading…
Cancel
Save