Skip to content

Commit

Permalink
Merge branch '55213-error-msg' into 'main'
Browse files Browse the repository at this point in the history
New ANSI parser

See merge request repositories/wash!14
  • Loading branch information
GPlaczek committed Mar 1, 2024
2 parents a473cfc + 1b6d86c commit c0864b5
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 303 deletions.
30 changes: 29 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
author_email='[email protected]'
edition = "2018"
name = "wash"
version = "0.1.0"
version = "0.1.3"
license = "Apache-2.0"

[dependencies]
Expand All @@ -12,6 +12,7 @@ color-eyre = "0.5"
lazy_static = "1"
regex = "1"
glob = "0.3"
vte = "0.13.0"

[target.'cfg(target_os = "wasi")'.dependencies]
wasi_ext_lib = { git = "https://github.com/antmicro/wasi_ext_lib.git", branch = "main", features = ["hterm"] }
Expand Down
268 changes: 268 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
/*
* Copyright (c) 2022-2024 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/

use std::io;
use std::io::Write;

use vte::{Params, Perform};

pub struct Cli {
pub history: Vec<Vec<char>>,
pub should_echo: bool,
pub cursor_position: usize,
pub input: Vec<char>,

history_entry_to_display: i32,
input_ready: bool,
input_stash: Vec<char>,
insert_mode: bool,
}

impl Cli {
pub fn new(should_echo: bool) -> Self {
Cli {
cursor_position: 0,
history: Vec::new(),
history_entry_to_display: -1,
input: Vec::new(),
input_ready: false,
input_stash: Vec::new(),
insert_mode: true,
should_echo,
}
}

pub fn is_input_ready(&self) -> bool {
self.input_ready
}

pub fn reset(&mut self) {
self.cursor_position = 0;
self.history_entry_to_display = -1;
self.input.clear();
self.input_ready = false;
self.input_stash.clear();

if !self.insert_mode {
self.insert_mode = true;
}
}

fn echo(&self, output: &str) {
if self.should_echo {
// TODO: should this maybe use OutputDevice too?
print!("{output}");
} else if output.contains('\n') {
println!();
}
}

fn get_cursor_to_beginning(&mut self) {
if self.cursor_position > 0 {
// bring cursor to the beggining with `ESC[nD` escape sequence
self.echo(&format!("\x1b[{}D", self.cursor_position));
}
self.cursor_position = 0;
}

fn get_cursor_to_end(&mut self) {
let to_end = self.input.len() - self.cursor_position;
if self.input.len() - self.cursor_position > 0 {
// bring cursor to the end with `ESC[nC` escape sequence
self.echo(&format!("\x1b[{}C", to_end));
}
self.cursor_position = self.input.len();
}

fn erase_input(&mut self) {
// bring cursor to the beginning and clear line to the right with `ESC[0K`
self.get_cursor_to_beginning();
self.echo("\x1b[0K");
}
}

impl Perform for Cli {
fn print(&mut self, c: char) {
let byte = c as u16;
match byte {
// backspace
0x7f => {
if !self.input.is_empty() && self.cursor_position > 0 {
self.echo("\x1b[D\x1b[P");
self.input.remove(self.cursor_position - 1);
self.cursor_position -= 1;
}
}
// regular characters
_ => {
if self.cursor_position == self.input.len() {
self.input.push(c);
self.echo(&c.to_string());
} else if self.insert_mode {
// in insert mode, when cursor is in the middle, new character expand CLI
// instead of replacing character under cursor

self.input.insert(self.cursor_position, c);

// for wasi target, we assume that hterm has enabled insert mode
#[cfg(target_os = "wasi")]
self.echo(&c.to_string());

#[cfg(not(target_os = "wasi"))]
self.echo(&format!("\x1b[@{}", c));
} else {
self.input[self.cursor_position] = c;

#[cfg(target_os = "wasi")]
self.echo(&format!("\x1b[P{}", c));

#[cfg(not(target_os = "wasi"))]
self.echo(&c.to_string());
}

self.cursor_position += 1;
}
}

io::stdout().flush().unwrap();
}

fn execute(&mut self, byte: u8) {
// C0 and C1 control functions
match byte {
// enter
0xa | 0xd => {
self.echo("\n");
self.cursor_position = 0;
self.input_ready = true;
}
_ => { /* ignore for now */ }
}
io::stdout().flush().unwrap();
}

fn hook(&mut self, _params: &Params, _intermediates: &[u8], _ignore: bool, _c: char) {
/* ignore for now */
}

fn put(&mut self, _byte: u8) {
/* ignore for now */
}

fn unhook(&mut self) {
/* ignore for now */
}

fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {
/* ignore for now */
}

fn csi_dispatch(&mut self, params: &Params, _intermediates: &[u8], _ignore: bool, c: char) {
if params.len() == 1 {
let param = params.iter().next().unwrap();
match (param[0], c) {
// UpArrow
(_, 'A') => {
if !self.history.is_empty() && self.history_entry_to_display != 0 {
if self.history_entry_to_display == -1 {
self.history_entry_to_display = (self.history.len() - 1) as i32;
self.input_stash = self.input.clone();
} else if self.history_entry_to_display > 0 {
self.history_entry_to_display -= 1;
}

self.erase_input();
self.input = self.history[self.history_entry_to_display as usize].clone();
self.cursor_position = self.input.len();
self.echo(&self.input.iter().collect::<String>());
}
}
// DownArrow
(_, 'B') => {
if self.history_entry_to_display != -1 {
self.erase_input();
if self.history.len() - 1 > (self.history_entry_to_display as usize) {
self.history_entry_to_display += 1;
self.input =
self.history[self.history_entry_to_display as usize].clone();
} else {
self.input = self.input_stash.clone();
self.history_entry_to_display = -1;
}
self.cursor_position = self.input.len();
self.echo(&self.input.iter().collect::<String>());
}
}
// RightArrow
(_, 'C') => {
if self.cursor_position < self.input.len() {
// move cursor right with `ESC[C`
self.echo("\x1b[C");
self.cursor_position += 1;
}
}
// LeftArrow
(_, 'D') => {
if self.cursor_position > 0 {
// move cursor left with `ESC[D`
self.echo("\x1b[D");
self.cursor_position -= 1;
}
}
// End
(_, 'F') => {
self.get_cursor_to_end();
}
// Home
(_, 'H') => {
self.get_cursor_to_beginning();
}
// Insert
(2, '~') => {
self.insert_mode = !self.insert_mode;
}
// Del
(3, '~') => {
if self.input.len() - self.cursor_position > 0 {
self.echo("\x1b[P");
self.input.remove(self.cursor_position);
}
}
// PageUp
(5, '~') => {
if !self.history.is_empty() && self.history_entry_to_display != 0 {
if self.history_entry_to_display == -1 {
self.input_stash = self.input.clone();
}
self.history_entry_to_display = 0;
self.erase_input();
self.input = self.history[0].clone();
self.cursor_position = self.input.len();
self.echo(&self.input.iter().collect::<String>());
}
}
// PageDown
(6, '~') => {
if self.history_entry_to_display != -1 {
self.erase_input();
self.input = self.input_stash.clone();
self.history_entry_to_display = -1;
self.cursor_position = self.input.len();
self.echo(&self.input.iter().collect::<String>());
}
}
(_, _) => { /* ignore for now */ }
}
} else {
/* ignore for now */
}
io::stdout().flush().unwrap();
}

fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {
/* ignore for now */
}
}
8 changes: 6 additions & 2 deletions src/internals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,12 @@ fn history(
_args: &mut [String],
output_device: &mut OutputDevice,
) -> Result<i32, Report> {
for (i, history_entry) in shell.history.iter().enumerate() {
output_device.println(&format!("{}: {}", i + 1, history_entry));
for (i, history_entry) in shell.cli.history.iter().enumerate() {
output_device.println(&format!(
"{}: {}",
i + 1,
history_entry.iter().collect::<String>()
));
}
Ok(EXIT_SUCCESS)
}
Expand Down
10 changes: 5 additions & 5 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ impl<'a> InputInterpreter<'a> {
self.handle_compound_command(shell, cmd, background, redirects)
}
ast::PipeableCommand::FunctionDef(_name, _cmds) => {
eprintln!("FunctionDef not yet handled (but it would be cool)");
eprintln!("FunctionDef not handled");
EXIT_FAILURE
}
}
Expand Down Expand Up @@ -380,7 +380,7 @@ impl<'a> InputInterpreter<'a> {
self.handle_compound_case(shell, word, arms, background)
}
any => {
eprintln!("CompoundCommandKind not yet handled: {any:#?}");
eprintln!("CompoundCommandKind not handled: {any:#?}");
EXIT_FAILURE
}
};
Expand Down Expand Up @@ -823,7 +823,7 @@ impl<'a> InputInterpreter<'a> {
}
// TODO: Heredoc (multiline command parsing) implementation
any => {
eprintln!("Redirect not yet handled: {any:?}");
eprintln!("Redirect not handled: {any:?}");
None
}
}
Expand Down Expand Up @@ -914,7 +914,7 @@ impl<'a> InputInterpreter<'a> {
},
)),
any => {
eprintln!("parameter not yet handled: {any:?}");
eprintln!("parameter not handled: {any:?}");
None
}
},
Expand All @@ -923,7 +923,7 @@ impl<'a> InputInterpreter<'a> {
ast::SimpleWord::SquareOpen => Some("[".to_string()),
ast::SimpleWord::SquareClose => Some("]".to_string()),
any => {
eprintln!("simple word not yet handled: {any:?}");
eprintln!("simple word not handled: {any:?}");
None
}
}
Expand Down
Loading

0 comments on commit c0864b5

Please sign in to comment.