|
|
|
@ -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 => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|