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