From ec91ed9cbbac192c7a8012b88b5db0c7e500a6f4 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Wed, 22 Dec 2021 22:58:23 -0500 Subject: error handling for parse errors --- src/parse.rs | 30 +++++++-- src/state/history.rs | 182 +++++++++++++++++++++++++++++++++------------------ src/state/mod.rs | 33 ++++++---- 3 files changed, 167 insertions(+), 78 deletions(-) diff --git a/src/parse.rs b/src/parse.rs index 03b865c..69b5135 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -77,16 +77,16 @@ pub struct Commands { } impl Commands { - pub fn parse(full_cmd: &str) -> Self { - Self::build_ast( + pub fn parse(full_cmd: &str) -> Result { + Ok(Self::build_ast( Shell::parse(Rule::line, full_cmd) - .unwrap() + .map_err(|e| Error::new(full_cmd, anyhow::anyhow!(e)))? .next() .unwrap() .into_inner() .next() .unwrap(), - ) + )) } pub fn pipelines(&self) -> &[Pipeline] { @@ -109,3 +109,25 @@ impl Commands { } } } + +pub struct Error { + input: String, + e: anyhow::Error, +} + +impl Error { + fn new(input: &str, e: anyhow::Error) -> Self { + Self { + input: input.to_string(), + e, + } + } + + pub fn input(&self) -> &str { + &self.input + } + + pub fn error(&self) -> &anyhow::Error { + &self.e + } +} diff --git a/src/state/history.rs b/src/state/history.rs index 96f302a..09cd517 100644 --- a/src/state/history.rs +++ b/src/state/history.rs @@ -92,7 +92,7 @@ impl History { let (resize_w, resize_r) = async_std::channel::unbounded(); let entry = async_std::sync::Arc::new(async_std::sync::Mutex::new( - Entry::new(ast.clone(), self.size, input_w, resize_w), + Entry::new(Ok(ast.clone()), self.size, input_w, resize_w), )); run_commands( @@ -107,6 +107,31 @@ impl History { Ok(self.entries.len() - 1) } + pub async fn parse_error( + &mut self, + e: crate::parse::Error, + event_w: async_std::channel::Sender, + ) { + // XXX would be great to not have to do this + let (input_w, input_r) = async_std::channel::unbounded(); + let (resize_w, resize_r) = async_std::channel::unbounded(); + input_w.close(); + input_r.close(); + resize_w.close(); + resize_r.close(); + + let mut entry = Entry::new(Err(e), self.size, input_w, resize_w); + let status = async_std::process::ExitStatus::from_raw(1 << 8); + entry.exit_info = Some(ExitInfo::new(status)); + self.entries.push(async_std::sync::Arc::new( + async_std::sync::Mutex::new(entry), + )); + event_w + .send(crate::event::Event::ProcessExit) + .await + .unwrap(); + } + pub async fn entry( &self, idx: usize, @@ -223,7 +248,7 @@ impl std::iter::DoubleEndedIterator for VisibleEntries { } pub struct Entry { - ast: crate::parse::Commands, + ast: Result, vt: vt100::Parser, audible_bell_state: usize, visual_bell_state: usize, @@ -237,7 +262,7 @@ pub struct Entry { impl Entry { fn new( - ast: crate::parse::Commands, + ast: Result, size: (u16, u16), input: async_std::channel::Sender>, resize: async_std::channel::Sender<(u16, u16)>, @@ -295,7 +320,7 @@ impl Entry { if self.running() { out.set_bgcolor(textmode::Color::Rgb(16, 64, 16)); } - out.write_str(self.ast.input_string()); + out.write_str(self.cmd()); out.reset_attributes(); set_bgcolor(out, focused); @@ -324,75 +349,99 @@ impl Entry { out.write_str(" "); out.reset_attributes(); - if self.binary() { - let msg = "This appears to be binary data. Fullscreen this entry to view anyway."; - let len: u16 = msg.len().try_into().unwrap(); - out.move_to( - out.screen().cursor_position().0 + 1, - (width - len) / 2, - ); - out.set_fgcolor(textmode::color::RED); - out.write_str(msg); - out.hide_cursor(true); - out.reset_attributes(); - } else { - let last_row = self.output_lines(width, focused && !scrolling); - if last_row > 5 { - out.write(b"\r\n"); - out.set_fgcolor(textmode::color::BLUE); - out.write_str("..."); - out.reset_attributes(); - } - let mut out_row = out.screen().cursor_position().0 + 1; - let screen = self.vt.screen(); - let pos = screen.cursor_position(); - let mut wrapped = false; - let mut cursor_found = None; - for (idx, row) in screen - .rows_formatted(0, width) - .enumerate() - .take(last_row) - .skip(last_row.saturating_sub(5)) - { - let idx: u16 = idx.try_into().unwrap(); - out.reset_attributes(); - if !wrapped { - out.move_to(out_row, 0); - } - out.write(&row); - wrapped = screen.row_wrapped(idx); - if pos.0 == idx { - cursor_found = Some(out_row); - } - out_row += 1; - } - if focused && !scrolling { - if let Some(row) = cursor_found { - out.hide_cursor(screen.hide_cursor()); - out.move_to(row, pos.1); - } else { + match &self.ast { + Ok(_) => { + if self.binary() { + let msg = "This appears to be binary data. Fullscreen this entry to view anyway."; + let len: u16 = msg.len().try_into().unwrap(); + out.move_to( + out.screen().cursor_position().0 + 1, + (width - len) / 2, + ); + out.set_fgcolor(textmode::color::RED); + out.write_str(msg); out.hide_cursor(true); + } else { + let last_row = + self.output_lines(width, focused && !scrolling); + if last_row > 5 { + out.write(b"\r\n"); + out.set_fgcolor(textmode::color::BLUE); + out.write_str("..."); + out.reset_attributes(); + } + let mut out_row = out.screen().cursor_position().0 + 1; + let screen = self.vt.screen(); + let pos = screen.cursor_position(); + let mut wrapped = false; + let mut cursor_found = None; + for (idx, row) in screen + .rows_formatted(0, width) + .enumerate() + .take(last_row) + .skip(last_row.saturating_sub(5)) + { + let idx: u16 = idx.try_into().unwrap(); + out.reset_attributes(); + if !wrapped { + out.move_to(out_row, 0); + } + out.write(&row); + wrapped = screen.row_wrapped(idx); + if pos.0 == idx { + cursor_found = Some(out_row); + } + out_row += 1; + } + if focused && !scrolling { + if let Some(row) = cursor_found { + out.hide_cursor(screen.hide_cursor()); + out.move_to(row, pos.1); + } else { + out.hide_cursor(true); + } + } } } + Err(e) => { + out.move_to(out.screen().cursor_position().0 + 1, 0); + out.set_fgcolor(textmode::color::RED); + out.write_str( + &format!("{}", e.error()).replace('\n', "\r\n"), + ); + out.hide_cursor(true); + } } out.reset_attributes(); } fn render_fullscreen(&mut self, out: &mut impl textmode::Textmode) { - let screen = self.vt.screen(); - let new_audible_bell_state = screen.audible_bell_count(); - let new_visual_bell_state = screen.visual_bell_count(); + match &self.ast { + Ok(_) => { + let screen = self.vt.screen(); + let new_audible_bell_state = screen.audible_bell_count(); + let new_visual_bell_state = screen.visual_bell_count(); - out.write(&screen.state_formatted()); + out.write(&screen.state_formatted()); - if self.audible_bell_state != new_audible_bell_state { - out.write(b"\x07"); - self.audible_bell_state = new_audible_bell_state; - } + if self.audible_bell_state != new_audible_bell_state { + out.write(b"\x07"); + self.audible_bell_state = new_audible_bell_state; + } - if self.visual_bell_state != new_visual_bell_state { - out.write(b"\x1bg"); - self.visual_bell_state = new_visual_bell_state; + if self.visual_bell_state != new_visual_bell_state { + out.write(b"\x1bg"); + self.visual_bell_state = new_visual_bell_state; + } + } + Err(e) => { + out.move_to(0, 0); + out.set_fgcolor(textmode::color::RED); + out.write_str( + &format!("{}", e.error()).replace('\n', "\r\n"), + ); + out.hide_cursor(true); + } } out.reset_attributes(); @@ -405,7 +454,10 @@ impl Entry { } pub fn cmd(&self) -> &str { - self.ast.input_string() + match &self.ast { + Ok(ast) => ast.input_string(), + Err(e) => e.input(), + } } pub fn toggle_fullscreen(&mut self) { @@ -434,6 +486,10 @@ impl Entry { } pub fn output_lines(&self, width: u16, focused: bool) -> usize { + if let Err(e) = &self.ast { + return e.error().to_string().lines().count(); + } + if self.binary() { return 1; } diff --git a/src/state/mod.rs b/src/state/mod.rs index 0eb8cfa..0ac9c75 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -311,15 +311,23 @@ impl State { textmode::Key::Ctrl(b'm') => { let input = self.readline.input(); if !input.is_empty() { - let cmd = self.parse(&input); self.readline.clear_input(); - let idx = self - .history - .run(&cmd, event_w.clone()) - .await - .unwrap(); - self.set_focus(Focus::History(idx), None).await; - self.hide_readline = true; + match self.parse(&input) { + Ok(ast) => { + let idx = self + .history + .run(&ast, event_w.clone()) + .await + .unwrap(); + self.set_focus(Focus::History(idx), None).await; + self.hide_readline = true; + } + Err(e) => { + self.history + .parse_error(e, event_w.clone()) + .await; + } + } } } textmode::Key::Ctrl(b'u') => self.readline.clear_backwards(), @@ -446,9 +454,12 @@ impl State { self.focus_idx().map_or(Focus::Readline, Focus::History) } - fn parse(&self, cmd: &str) -> crate::parse::Commands { - let ast = crate::parse::Commands::parse(cmd); + fn parse( + &self, + cmd: &str, + ) -> Result { + let ast = crate::parse::Commands::parse(cmd)?; // todo: interpolate - ast + Ok(ast) } } -- cgit v1.2.3-54-g00ecf