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 {
input: HANDLE,
// 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
buf_len: u32,
// the position of our current indexer into the input record buffer
@ -137,13 +106,45 @@ pub struct Reader {
impl Reader {
pub fn new() -> Result<Self> {
setup_stdin()?;
Ok(Self {
buf: [Console::INPUT_RECORD::default(); 100],
let v = Self {
buf: [Console::INPUT_RECORD::default(); 32],
buf_len: 0,
buf_idx: 0,
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> {

@ -1,4 +1,5 @@
use crate::log::*;
use std::cmp::min;
pub struct Line {
/// the current contents of the line
@ -33,9 +34,13 @@ impl Line {
self.chars.iter().collect()
}
pub fn back(&mut self) -> bool {
pub fn back(&mut self, n: usize) -> bool {
if self.cursor > 0 {
self.cursor -= 1;
if n > self.cursor {
self.cursor = n;
} else {
self.cursor -= n;
}
self.show_debug();
true
} else {
@ -83,9 +88,9 @@ impl Line {
n
}
pub fn forward(&mut self) -> bool {
pub fn forward(&mut self, n: usize) -> bool {
if self.cursor < self.chars.len() {
self.cursor += 1;
self.cursor = min(self.chars.len(), self.cursor + n);
self.show_debug();
true
} else {
@ -109,7 +114,33 @@ impl Line {
chars.iter().collect()
}
pub fn pos(&self) -> usize {
self.cursor
}
pub fn len(&self) -> usize {
self.chars.len()
}
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 prompt;
use line::Line;
use prompt::Prompt;
use std::io::Write;
@ -27,16 +26,13 @@ fn main() -> Result<()> {
}
}
let mut line = Line::new();
let prompt = Prompt::new();
let mut input = input::Reader::new()?;
let mut output = output::Writer::new()?;
let shell = Shell::new();
let mut shell = Shell::new()?;
prompt.print(&mut output)?;
prompt.print(&mut shell.output)?;
info!("» enter");
loop {
match input.next()? {
match shell.input.next()? {
input::Event::Key(event) => {
if event.down {
if event.code.val == 0 {
@ -48,9 +44,19 @@ fn main() -> Result<()> {
}
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 {
output.newline()?;
let s = line.pop();
shell.output.newline()?;
let s = shell.line.pop();
let parts: Vec<&str> = s.split_whitespace().collect();
if parts.len() > 0 {
let cmd = parts[0].to_string();
@ -60,7 +66,7 @@ fn main() -> Result<()> {
vec![]
};
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(false) => warn!("▷ {} {}", cmd, args.join(" ")),
Err(e) => {
@ -68,8 +74,11 @@ fn main() -> Result<()> {
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;
}
@ -78,22 +87,22 @@ fn main() -> Result<()> {
}
if event.code == key::BACKSPACE {
if line.backspace() {
if shell.line.backspace() {
// move cursor back two spaces
output.back(2)?;
let tail = format!("{} ", line.tail());
shell.output.back(2)?;
let tail = format!("{} ", shell.line.tail());
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
// the tail
if n > 1 {
// let text = format!("\x1b[{}D", n - 1);
output.back(n-1)?;
shell.output.back(n-1)?;
// output.write(text.as_bytes())?;
} else {
// honestly I can't remember how I figured this out
output.write(b" \x1b[1D")?;
shell.output.write(b" \x1b[1D")?;
}
}
continue;
@ -102,7 +111,7 @@ fn main() -> Result<()> {
// CTRL-D to exit
if event.ctrl && event.code == key::D {
info!("» exit");
output.close()?;
shell.output.close()?;
return Ok(());
}
@ -110,98 +119,80 @@ fn main() -> Result<()> {
if event.ctrl && event.code == key::J {
debug!("⎈ j: dot");
// 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;
}
// CTRL-L to clear the screen
if event.ctrl && event.code == key::L {
info!("» clear");
output.clear()?;
prompt.print(&mut output)?;
output.write(line.show().as_bytes())?;
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()?;
continue;
}
// CTRL-U to erase to the beginning of the line
if event.ctrl && event.code == key::U {
info!("» clear left");
let n = line.clear_left();
let n = shell.line.clear_left();
if n > 0 {
// 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
// element
let kept = line.show();
let kept = shell.line.show();
let text = format!("{}{:width$}", kept, "", width = n);
output.write(text.as_bytes())?;
output.back(n + kept.chars().count())?;
shell.output.write(text.as_bytes())?;
shell.output.back(n + kept.chars().count())?;
}
continue;
}
// CTRL-A to move to the beginning of the line
if event.ctrl && event.code == key::A {
info!("» seek left");
let n = line.seek_left();
if n > 0 {
// move left by the distance seeked
output.back(n)?;
}
shell.seek_left()?;
continue;
}
// CTRL-E to move to the end of the line
if event.ctrl && event.code == key::E {
info!("» seek right");
let n = line.seek_right();
if n > 0 {
// move right by the distance seeked
output.forward(n)?;
}
shell.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() {
line.insert(event.char);
shell.line.insert(event.char);
let tail = line.tail();
let tail = shell.line.tail();
let n = tail.chars().count();
// 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 we wrote more than one character, because we weren't at the end, we
// need to rewind the terminal cursor to where it was.
output.back(n-1)?;
shell.output.back(n-1)?;
}
continue;
}
warn!("‽ {}", event);
}
input::Event::Left => {
debug!("⛬ ←");
if line.back() {
output.back(1)?;
}
}
input::Event::Right => {
debug!("⛬ →");
if line.forward() {
output.forward(1)?;
}
}
input::Event::Left => { shell.back(1)? }
input::Event::Right => { shell.forward(1)? }
input::Event::Up => {
debug!("⛬ ↑");
}
input::Event::Down => {
debug!("⛬ ↓");
}
input::Event::Home => {}
input::Event::End => {}
input::Event::Home => { shell.seek_left()? }
input::Event::End => { shell.seek_right()? }
input::Event::Focus(true) => {}
input::Event::Focus(false) => {}
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 {
let handle = Console::GetStdHandle(Console::STD_OUTPUT_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 {
output: HANDLE,
}
impl Writer {
pub fn new() -> Result<Self> {
setup_stdout()?;
Ok(Self {
let mut v = Self {
output: stdout_handle()?,
})
};
v.reset()?;
Ok(v)
}
pub fn close(&mut self) -> Result<()> {
@ -139,6 +122,22 @@ impl Writer {
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<()> {
self.write(b"\r\n")?;
Ok(())

@ -1,18 +1,70 @@
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use crate::output;
use crate::input;
use crate::line::Line;
use crate::log::*;
use anyhow::Result;
pub struct Shell {
pub input: input::Reader,
pub output: output::Writer,
pub line: Line,
}
impl Shell {
pub fn new() -> Self {
Self {}
pub fn new() -> Result<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() {
"pwd" => {
let pb = std::env::current_dir()?;
@ -78,7 +130,7 @@ impl Shell {
if n == 1 {
buf.push(one_byte[0]);
if let Ok(s) = std::str::from_utf8(&buf) {
output.write(s.as_bytes())?;
self.output.write(s.as_bytes())?;
buf.clear();
}
}

Loading…
Cancel
Save