more refactoring for great beauty

parse-tree
Jordan Orelli 2 years ago
parent cba1004db8
commit 37224f63c5

@ -43,7 +43,7 @@ impl fmt::Display for Event {
let ctrl = if self.ctrl { '⎈' } else { '·' };
let alt = if self.alt { '⎇' } else { '·' };
let shift = if self.shift { '⇧'} else { '·' };
write!(f, "{} {} {} {} {: >2} {}", down, ctrl, alt, shift, self.code.val, sym)
write!(f, "{} {} {} {} {: >3} {}", down, ctrl, alt, shift, self.code.val, sym)
}
}
@ -168,6 +168,8 @@ keycodes! {
0x28 DOWN '↓'
0xA0 LEFT_SHIFT '⇧'
0xA1 RIGHT_SHIFT '⇧'
0xBE PERIOD '.'
0xDE QUOTE '\''
}
/*

@ -110,6 +110,6 @@ impl Line {
}
fn show_debug(&self) {
debug!(" {: <4} {: >5} {}", self.cursor, self.chars.len(), self.show());
debug!(" {: <5} {: >5} {}", self.cursor, self.chars.len(), self.show());
}
}

@ -1,151 +1,21 @@
mod error;
mod input;
mod output;
mod key;
mod line;
mod log;
mod output;
mod prompt;
use line::Line;
use prompt::Prompt;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use windows::Win32::{
Foundation::HANDLE,
System::Console,
};
use anyhow::{Context, Result};
use crate::{error::Error, log::*};
use anyhow::Result;
#[allow(dead_code)]
fn log_output_mode(mode: Console::CONSOLE_MODE) {
// Characters written by the WriteFile or WriteConsole function or echoed by the ReadFile or
// ReadConsole function are parsed for ASCII control sequences, and the correct action is
// performed. Backspace, tab, bell, carriage return, and line feed characters are processed. It
// should be enabled when using control sequences or when ENABLE_VIRTUAL_TERMINAL_PROCESSING is
// set.
if (mode & Console::ENABLE_PROCESSED_OUTPUT).0 > 0 {
debug!("Processed Output: Enabled");
} else {
debug!("Processed Output: Disabled");
}
// When writing with WriteFile or WriteConsole or echoing with ReadFile or ReadConsole, the
// cursor moves to the beginning of the next row when it reaches the end of the current row.
// This causes the rows displayed in the console window to scroll up automatically when the
// cursor advances beyond the last row in the window. It also causes the contents of the
// console screen buffer to scroll up (../discarding the top row of the console screen buffer)
// when the cursor advances beyond the last row in the console screen buffer. If this mode is
// disabled, the last character in the row is overwritten with any subsequent characters.
if (mode & Console::ENABLE_WRAP_AT_EOL_OUTPUT).0 > 0 {
debug!("Wrap at EOL: Enabled");
} else {
debug!("Wrap at EOL: Disabled");
}
use crate::log::*;
// When writing with WriteFile or WriteConsole, characters are parsed for VT100 and similar
// control character sequences that control cursor movement, color/font mode, and other
// operations that can also be performed via the existing Console APIs. For more information,
// see Console Virtual Terminal Sequences.
//
// Ensure ENABLE_PROCESSED_OUTPUT is set when using this flag.
if (mode & Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING).0 > 0 {
debug!("Terminal Processing: Enabled");
} else {
debug!("Terminal Processing: Disabled");
}
// When writing with WriteFile or WriteConsole, this adds an additional state to end-of-line
// wrapping that can delay the cursor move and buffer scroll operations.
//
// Normally when ENABLE_WRAP_AT_EOL_OUTPUT is set and text reaches the end of the line, the
// cursor will immediately move to the next line and the contents of the buffer will scroll up
// by one line. In contrast with this flag set, the cursor does not move to the next line, and
// the scroll operation is not performed. The written character will be printed in the final
// position on the line and the cursor will remain above this character as if
// ENABLE_WRAP_AT_EOL_OUTPUT was off, but the next printable character will be printed as if
// ENABLE_WRAP_AT_EOL_OUTPUT is on. No overwrite will occur. Specifically, the cursor quickly
// advances down to the following line, a scroll is performed if necessary, the character is
// printed, and the cursor advances one more position.
//
// The typical usage of this flag is intended in conjunction with setting
// ENABLE_VIRTUAL_TERMINAL_PROCESSING to better emulate a terminal emulator where writing the
// final character on the screen (../in the bottom right corner) without triggering an
// immediate scroll is the desired behavior.
if (mode & Console::DISABLE_NEWLINE_AUTO_RETURN).0 > 0 {
debug!("Newline Auto Return: Enabled");
} else {
debug!("Newline Auto Return: Disabled");
}
// The APIs for writing character attributes including WriteConsoleOutput and
// WriteConsoleOutputAttribute allow the usage of flags from character attributes to adjust the
// color of the foreground and background of text. Additionally, a range of DBCS flags was
// specified with the COMMON_LVB prefix. Historically, these flags only functioned in DBCS code
// pages for Chinese, Japanese, and Korean languages.
//
// With exception of the leading byte and trailing byte flags, the remaining flags describing
// line drawing and reverse video (../swap foreground and background colors) can be useful for
// other languages to emphasize portions of output.
//
// Setting this console mode flag will allow these attributes to be used in every code page on
// every language.
//
// It is off by default to maintain compatibility with known applications that have
// historically taken advantage of the console ignoring these flags on non-CJK machines to
// store bits in these fields for their own purposes or by accident.
//
// Note that using the ENABLE_VIRTUAL_TERMINAL_PROCESSING mode can result in LVB grid and
// reverse video flags being set while this flag is still off if the attached application
// requests underlining or inverse video via Console Virtual Terminal Sequences.
if (mode & Console::ENABLE_LVB_GRID_WORLDWIDE).0 > 0 {
debug!("LVB Grid: Enabled");
} else {
debug!("LVB Grid: Disabled");
}
}
pub fn stdout_handle() -> Result<HANDLE> {
unsafe {
let handle = Console::GetStdHandle(Console::STD_OUTPUT_HANDLE)
.context("unable to get stdin handle")?;
Ok(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(())
}
fn newline(stdout: HANDLE) -> Result<()> {
unsafe {
Error::check(Console::WriteConsoleA(
stdout,
"\r\n".as_bytes(),
None,
None,
))?;
}
Ok(())
}
fn eval(cmd: String, args: Vec<&str>) -> Result<bool> {
fn eval(output: &mut output::Writer, cmd: String, args: Vec<&str>) -> Result<bool> {
match cmd.as_str() {
"pwd" => {
let pb = std::env::current_dir()?;
@ -200,11 +70,10 @@ fn eval(cmd: String, args: Vec<&str>) -> Result<bool> {
"tail" => {
if args.len() > 0 {
let fname = args[0];
let stdout = stdout_handle()?;
match File::options().read(true).open(fname) {
Ok(mut f) => {
_ = f.seek(SeekFrom::End(0));
let mut one_byte: [u8; 1] = [0 ; 1];
let mut one_byte: [u8; 1] = [0; 1];
let mut buf: Vec<u8> = vec![];
loop {
match f.read(&mut one_byte) {
@ -212,14 +81,12 @@ fn eval(cmd: String, args: Vec<&str>) -> Result<bool> {
if n == 1 {
buf.push(one_byte[0]);
if let Ok(s) = std::str::from_utf8(&buf) {
unsafe {
Error::check(Console::WriteConsoleA(stdout, s.as_bytes(), None, None))?;
}
output.write(s.as_bytes())?;
buf.clear();
}
}
},
Err(_) => {},
}
Err(_) => {}
}
}
}
@ -270,15 +137,12 @@ fn main() -> Result<()> {
}
}
setup_stdout()?;
let stdout = stdout_handle()?;
let mut line = Line::new();
let prompt = Prompt::new();
let mut input = input::Reader::new()?;
let mut output = output::Writer::new()?;
prompt.print()?;
prompt.print(&mut output)?;
info!("» enter");
loop {
match input.next()? {
@ -294,23 +158,27 @@ fn main() -> Result<()> {
info!(" {}", event);
if event.code == key::ENTER {
newline(stdout)?;
output.newline()?;
let s = line.pop();
let parts: Vec<&str> = s.split_whitespace().collect();
if parts.len() > 0 {
let cmd = parts[0].to_string();
let args = if parts.len() > 1 { parts[1..].to_vec() } else { vec![] };
let args = if parts.len() > 1 {
parts[1..].to_vec()
} else {
vec![]
};
debug!("◇ {} {}", cmd.clone(), args.join(" "));
match eval(cmd.clone(), args.clone()) {
match eval(&mut output, cmd.clone(), args.clone()) {
Ok(true) => info!("▷ {} {}", cmd, args.join(" ")),
Ok(false) => warn!("▷ {} {}", cmd, args.join(" ")),
Err(e) => {
error!("▷ {} {} ● {}", cmd, args.join(" "), e);
println!("error: {}", e);
},
}
}
}
prompt.print()?;
prompt.print(&mut output)?;
continue;
}
@ -321,20 +189,20 @@ fn main() -> Result<()> {
if event.code == key::BACKSPACE {
if line.backspace() {
// move cursor back two spaces
output.write("\x1b[2D")?;
output.write(b"\x1b[2D")?;
// output.back(2)?;
let tail = format!("{} ", line.tail());
let n = tail.chars().count();
output.write(&tail)?;
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.write(&text)?;
let text = format!("\x1b[{}D", n - 1);
output.write(text.as_bytes())?;
} else {
// honestly I can't remember how I figured this out
output.write(" \x1b[1D")?;
output.write(b" \x1b[1D")?;
}
}
continue;
@ -351,16 +219,16 @@ fn main() -> Result<()> {
if event.ctrl && event.code == key::J {
debug!("⎈ j: dot");
// red bullet
output.write("\x1b[31m\u{2022}\x1b[0m")?;
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.write("\x1b[2J\x1b[0;0H")?;
prompt.print()?;
output.write(&line.show())?;
output.write(b"\x1b[2J\x1b[0;0H")?;
prompt.print(&mut output)?;
output.write(line.show().as_bytes())?;
continue;
}
@ -371,20 +239,14 @@ fn main() -> Result<()> {
if n > 0 {
// move left by the number of elements removed
let text = format!("\x1b[{}D", n);
unsafe {
Error::check(Console::WriteConsoleA(stdout, text.as_bytes(), None, None))?;
}
output.write(text.as_bytes())?;
// draw the elements remaining, followed by a space for each removed
// element
let kept = line.show();
let text = format!("{}{:width$}", kept, "", width = n);
unsafe {
Error::check(Console::WriteConsoleA(stdout, text.as_bytes(), None, None))?;
}
output.write(text.as_bytes())?;
let text = format!("\x1b[{}D", n + kept.chars().count());
unsafe {
Error::check(Console::WriteConsoleA(stdout, text.as_bytes(), None, None))?;
}
output.write(text.as_bytes())?;
}
continue;
}
@ -396,9 +258,7 @@ fn main() -> Result<()> {
if n > 0 {
// move left by the distance seeked
let text = format!("\x1b[{}D", n);
unsafe {
Error::check(Console::WriteConsoleA(stdout, text.as_bytes(), None, None))?;
}
output.write(text.as_bytes())?;
}
continue;
}
@ -410,9 +270,7 @@ fn main() -> Result<()> {
if n > 0 {
// move right by the distance seeked
let text = format!("\x1b[{}C", n);
unsafe {
Error::check(Console::WriteConsoleA(stdout, text.as_bytes(), None, None))?;
}
output.write(text.as_bytes())?;
}
continue;
}
@ -426,20 +284,12 @@ fn main() -> Result<()> {
let n = tail.chars().count();
// write everything from the current line cursor out to the output buffer.
unsafe {
Error::check(Console::WriteConsoleA(
stdout,
tail.as_bytes(),
None,
None,
))?;
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.
if n > 1 {
let text = format!("\x1b[{}D", n-1);
Error::check(Console::WriteConsoleA(stdout, text.as_bytes(), None, None))?;
}
let text = format!("\x1b[{}D", n - 1);
output.write(text.as_bytes())?;
}
continue;
}
@ -449,34 +299,28 @@ fn main() -> Result<()> {
input::Event::Left => {
debug!("⛬ ←");
if line.back() {
unsafe {
let text = "\x1b[D"; // lol this sucks
Error::check(Console::WriteConsoleA(stdout, text.as_bytes(), None, None))?;
}
output.write(b"\x1b[D")?; // lol this sucks
}
},
}
input::Event::Right => {
debug!("⛬ →");
if line.forward() {
unsafe {
let text = "\x1b[C"; // lol this sucks
Error::check(Console::WriteConsoleA(stdout, text.as_bytes(), None, None))?;
}
output.write(b"\x1b[C")?; // lol this sucks
}
}
input::Event::Up => {
debug!("⛬ ↑");
},
}
input::Event::Down => {
debug!("⛬ ↓");
},
input::Event::Home => {},
input::Event::End => {},
input::Event::Focus(true) => {},
input::Event::Focus(false) => {},
input::Event::Menu(_command_id) => {},
input::Event::Mouse{..} => {},
input::Event::Size => {},
}
input::Event::Home => {}
input::Event::End => {}
input::Event::Focus(true) => {}
input::Event::Focus(false) => {}
input::Event::Menu(_command_id) => {}
input::Event::Mouse { .. } => {}
input::Event::Size => {}
}
}
}

@ -1,8 +1,98 @@
use crate::{error::Error, log::*};
use anyhow::{Context, Result};
use crate::error::Error;
use windows::Win32::{Foundation::{CloseHandle, HANDLE}, System::Console};
// use std::io;
use std::io::{self, Write};
use windows::Win32::{
Foundation::{CloseHandle, HANDLE},
System::Console,
};
#[allow(dead_code)]
fn log_output_mode(mode: Console::CONSOLE_MODE) {
// Characters written by the WriteFile or WriteConsole function or echoed by the ReadFile or
// ReadConsole function are parsed for ASCII control sequences, and the correct action is
// performed. Backspace, tab, bell, carriage return, and line feed characters are processed. It
// should be enabled when using control sequences or when ENABLE_VIRTUAL_TERMINAL_PROCESSING is
// set.
if (mode & Console::ENABLE_PROCESSED_OUTPUT).0 > 0 {
debug!("Processed Output: Enabled");
} else {
debug!("Processed Output: Disabled");
}
// When writing with WriteFile or WriteConsole or echoing with ReadFile or ReadConsole, the
// cursor moves to the beginning of the next row when it reaches the end of the current row.
// This causes the rows displayed in the console window to scroll up automatically when the
// cursor advances beyond the last row in the window. It also causes the contents of the
// console screen buffer to scroll up (../discarding the top row of the console screen buffer)
// when the cursor advances beyond the last row in the console screen buffer. If this mode is
// disabled, the last character in the row is overwritten with any subsequent characters.
if (mode & Console::ENABLE_WRAP_AT_EOL_OUTPUT).0 > 0 {
debug!("Wrap at EOL: Enabled");
} else {
debug!("Wrap at EOL: Disabled");
}
// When writing with WriteFile or WriteConsole, characters are parsed for VT100 and similar
// control character sequences that control cursor movement, color/font mode, and other
// operations that can also be performed via the existing Console APIs. For more information,
// see Console Virtual Terminal Sequences.
//
// Ensure ENABLE_PROCESSED_OUTPUT is set when using this flag.
if (mode & Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING).0 > 0 {
debug!("Terminal Processing: Enabled");
} else {
debug!("Terminal Processing: Disabled");
}
// When writing with WriteFile or WriteConsole, this adds an additional state to end-of-line
// wrapping that can delay the cursor move and buffer scroll operations.
//
// Normally when ENABLE_WRAP_AT_EOL_OUTPUT is set and text reaches the end of the line, the
// cursor will immediately move to the next line and the contents of the buffer will scroll up
// by one line. In contrast with this flag set, the cursor does not move to the next line, and
// the scroll operation is not performed. The written character will be printed in the final
// position on the line and the cursor will remain above this character as if
// ENABLE_WRAP_AT_EOL_OUTPUT was off, but the next printable character will be printed as if
// ENABLE_WRAP_AT_EOL_OUTPUT is on. No overwrite will occur. Specifically, the cursor quickly
// advances down to the following line, a scroll is performed if necessary, the character is
// printed, and the cursor advances one more position.
//
// The typical usage of this flag is intended in conjunction with setting
// ENABLE_VIRTUAL_TERMINAL_PROCESSING to better emulate a terminal emulator where writing the
// final character on the screen (../in the bottom right corner) without triggering an
// immediate scroll is the desired behavior.
if (mode & Console::DISABLE_NEWLINE_AUTO_RETURN).0 > 0 {
debug!("Newline Auto Return: Enabled");
} else {
debug!("Newline Auto Return: Disabled");
}
// The APIs for writing character attributes including WriteConsoleOutput and
// WriteConsoleOutputAttribute allow the usage of flags from character attributes to adjust the
// color of the foreground and background of text. Additionally, a range of DBCS flags was
// specified with the COMMON_LVB prefix. Historically, these flags only functioned in DBCS code
// pages for Chinese, Japanese, and Korean languages.
//
// With exception of the leading byte and trailing byte flags, the remaining flags describing
// line drawing and reverse video (../swap foreground and background colors) can be useful for
// other languages to emphasize portions of output.
//
// Setting this console mode flag will allow these attributes to be used in every code page on
// every language.
//
// It is off by default to maintain compatibility with known applications that have
// historically taken advantage of the console ignoring these flags on non-CJK machines to
// store bits in these fields for their own purposes or by accident.
//
// Note that using the ENABLE_VIRTUAL_TERMINAL_PROCESSING mode can result in LVB grid and
// reverse video flags being set while this flag is still off if the attached application
// requests underlining or inverse video via Console Virtual Terminal Sequences.
if (mode & Console::ENABLE_LVB_GRID_WORLDWIDE).0 > 0 {
debug!("LVB Grid: Enabled");
} else {
debug!("LVB Grid: Disabled");
}
}
pub fn stdout_handle() -> Result<HANDLE> {
unsafe {
@ -12,41 +102,58 @@ 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> {
Ok(Self{
setup_stdout()?;
Ok(Self {
output: stdout_handle()?,
})
}
pub fn write(&mut self, s: &str) -> Result<()> {
pub fn close(&mut self) -> Result<()> {
unsafe {
Error::check(Console::WriteConsoleA(self.output, s.as_bytes(), None, None))?;
CloseHandle(self.output);
}
Ok(())
}
pub fn close(&mut self) -> Result<()> {
pub fn newline(&mut self) -> Result<()> {
self.write(b"\r\n")?;
Ok(())
}
}
impl Write for Writer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
unsafe {
CloseHandle(self.output);
Error::check(Console::WriteConsoleA(self.output, buf, None, None))?;
}
Ok(0)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
// impl std::io::Write for Writer {
// fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
// unsafe {
// Error::check(Console::WriteConsoleA(self.output, buf, None, None))?;
// }
// Ok(0)
// }
//
// fn flush(&mut self) -> io::Result<()> {
// Ok(())
// }
// }

@ -1,6 +1,6 @@
use crate::{error::Error, stdout_handle};
use crate::output;
use std::io::Write;
use anyhow::Result;
use windows::Win32::System::Console;
pub struct Prompt {
s: String,
@ -13,24 +13,14 @@ impl Prompt {
}
}
pub fn print(&self) -> Result<()> {
pub fn print(&self, output: &mut output::Writer) -> Result<()> {
match std::env::current_dir() {
Ok(d) => unsafe {
Ok(d) => {
let text = d.to_str().unwrap().to_owned() + " " + &self.s;
Error::check(Console::WriteConsoleA(
stdout_handle()?,
text.as_bytes(),
None,
None,
))?;
output.write(text.as_bytes())?;
}
Err(_) => unsafe {
Error::check(Console::WriteConsoleA(
stdout_handle()?,
self.s.as_bytes(),
None,
None,
))?;
Err(_) => {
output.write(self.s.as_bytes())?;
},
}
Ok(())

Loading…
Cancel
Save