Better handling of panics

main
nick 2024-10-03 16:11:40 -04:00
parent 04289a4724
commit b56ee8ffa5
8 changed files with 163 additions and 38 deletions

92
Cargo.lock generated
View File

@ -51,6 +51,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anyhow"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
[[package]]
name = "autocfg"
version = "1.3.0"
@ -172,8 +178,10 @@ dependencies = [
name = "dirbuilder"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"crossterm 0.28.1",
"gag",
"tui",
]
@ -187,6 +195,33 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "fastrand"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]]
name = "filedescriptor"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
dependencies = [
"libc",
"thiserror",
"winapi",
]
[[package]]
name = "gag"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a713bee13966e9fbffdf7193af71d54a6b35a0bb34997cd6c9519ebeb5005972"
dependencies = [
"filedescriptor",
"tempfile",
]
[[package]]
name = "heck"
version = "0.5.0"
@ -258,6 +293,15 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "once_cell"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1"
dependencies = [
"portable-atomic",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
@ -281,6 +325,12 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "portable-atomic"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
[[package]]
name = "proc-macro2"
version = "1.0.86"
@ -381,6 +431,39 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
dependencies = [
"cfg-if",
"fastrand",
"once_cell",
"rustix",
"windows-sys 0.59.0",
]
[[package]]
name = "thiserror"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tui"
version = "0.19.0"
@ -464,6 +547,15 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.48.5"

View File

@ -4,8 +4,10 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.89"
clap = { version = "4.5.19", features = ["derive"] }
crossterm = "0.28.1"
gag = "1.0.0"
tui = "0.19.0"
[lints.clippy]

View File

@ -1,3 +1,5 @@
use std::io::Error;
use tui::widgets::{Block, Borders, Paragraph};
use crate::pane::prelude::*;
@ -16,12 +18,12 @@ impl Command {
impl Pane for Command {
fn cursor_position(&self) -> Option<(u16, u16)> {
let x = u16::try_from(self.buf.len())
.expect("too big to display");
.unwrap_or(u16::MAX);
let y = 0;
Some((x, y))
}
fn lines_hint(&self) -> usize {
fn lines_hint(&self) -> u16 {
1
}
@ -32,13 +34,15 @@ impl Pane for Command {
output.render_widget(widget, area);
}
fn update(&mut self, key: KeyCode) -> Message {
match key {
fn update(&mut self, key: KeyCode) -> Result<Message, Error> {
let result = match key {
KeyCode::Char(c) => { self.buf.push(c); Message::Nothing },
KeyCode::Backspace => { self.buf.pop(); Message::Nothing },
KeyCode::Esc => Message::CancelCommand,
KeyCode::Enter => Message::ExecuteCommand,
KeyCode::Enter if !self.buf.is_empty() => Message::ExecuteCommand,
_ => Message::Nothing,
}
};
Ok(result)
}
}

View File

@ -26,9 +26,9 @@ impl Pane for CommandOutput {
None
}
fn lines_hint(&self) -> usize {
fn lines_hint(&self) -> u16 {
match self.contents.as_ref() {
Ok(Some(s)) => s.lines().count(),
Ok(Some(s)) => s.lines().count().try_into().unwrap_or(u16::MAX),
Ok(None) => 0,
Err(_) => 1,
}
@ -44,7 +44,7 @@ impl Pane for CommandOutput {
output.render_widget(widget, area);
}
fn update(&mut self, _: KeyCode) -> Message {
fn update(&mut self, _: KeyCode) -> Result<Message, Error> {
unimplemented!("Output does not receive input")
}
}

View File

@ -114,7 +114,7 @@ impl Directory {
self.selection as usize
}
pub fn update_navigation(&mut self, key: KeyCode) -> Message {
pub fn update_navigation(&mut self, key: KeyCode) -> Result<Message, Error> {
match key {
KeyCode::Char(c) => {
match c {
@ -127,7 +127,7 @@ impl Directory {
self.editing = true;
},
'x' | 'X' => {
return Message::BeginCommand;
return Ok(Message::BeginCommand);
}
_ => (),
};
@ -138,9 +138,9 @@ impl Directory {
self.src_name = Some(self.path());
self.editing = true;
},
KeyCode::Esc => return Message::Exit,
KeyCode::Esc => return Ok(Message::Exit),
KeyCode::Backspace if self.selected().depth > 0 => {
self.delete().unwrap();
self.delete()?;
if self.selected().is_file {
self.dirs.remove(self.selection());
} else {
@ -159,16 +159,16 @@ impl Directory {
_ => (),
}
Message::Nothing
Ok(Message::Nothing)
}
pub fn update_edit(&mut self, key: KeyCode) -> Message {
pub fn update_edit(&mut self, key: KeyCode) -> Result<Message, Error> {
match key {
KeyCode::Char(c) => self.selected_mut().name.push(c),
KeyCode::Enter if !self.selected().name.is_empty() => {
self.create().unwrap();
self.create()?;
if let Some(src) = self.src_name.take() {
rename(src, self.path()).unwrap();
rename(src, self.path())?;
}
self.editing = false;
},
@ -178,7 +178,7 @@ impl Directory {
.and_then(|s| s.to_str())
.map_or(String::new(), ToOwned::to_owned);
self.editing = false;
} else if self.selected().name.len() > 0 {
} else if ! self.selected().name.is_empty() {
self.editing = false;
}
}
@ -188,7 +188,7 @@ impl Directory {
_ => (),
}
Message::Nothing
Ok(Message::Nothing)
}
}
@ -201,16 +201,18 @@ impl Pane for Directory {
let x = u16::from(self.selected().depth) * 4
+ u16::try_from(self.selected().name.len())
.expect("name too long to display");
.unwrap_or(u16::MAX);
let y = self.selection;
Some((x, y))
}
fn lines_hint(&self) -> usize {
fn lines_hint(&self) -> u16 {
self.dirs.len()
.try_into()
.unwrap_or(u16::MAX)
}
fn update(&mut self, key: KeyCode) -> Message {
fn update(&mut self, key: KeyCode) -> Result<Message, Error> {
if self.editing {
self.update_edit(key)
} else {

View File

@ -13,12 +13,36 @@ use state::State;
use terminal::Terminal;
use pane::prelude::*;
use std::fs::{remove_file, OpenOptions};
use std::io::Error;
use anyhow::{Result as AnyResult, Error as AnyError};
use clap::Parser;
use crossterm::event::{read, Event, KeyEvent};
use gag::Redirect;
fn main() -> Result<(), Error> {
const LOG_FILE: &str = "dirbuilder.log";
const LOG_EXISTS_MSG: &str = "the log file exists, implying dirbuider didn't exit cleanly last time. consult the log and delete it before rerunning.";
fn main() -> AnyResult<()> {
let target = OpenOptions::new()
.write(true)
.create_new(true)
.open(LOG_FILE)
.map_err(|e| {
<Error as Into<AnyError>>::into(e).context(LOG_FILE).context(LOG_EXISTS_MSG)
})?;
let _redirect = Redirect::stderr(target)?;
do_tui()?;
remove_file(LOG_FILE)?;
Ok(())
}
fn do_tui() -> Result<(), Error> {
let args = Args::parse();
let mut terminal = Terminal::new()?;
@ -39,7 +63,7 @@ fn main() -> Result<(), Error> {
let Event::Key(KeyEvent { code: key, .. }) = read()?
else { continue };
match state.update(key) {
match state.update(key)? {
Message::Nothing => (),
Message::Exit => break,
Message::BeginCommand => state.begin_command(),

View File

@ -1,4 +1,4 @@
use std::io::Stdout;
use std::io::{Error, Stdout};
use crossterm::event::KeyCode;
use tui::{layout::Rect, Frame};
@ -7,11 +7,11 @@ use tui::backend::CrosstermBackend;
pub trait Pane {
fn cursor_position(&self) -> Option<(u16, u16)>;
fn lines_hint(&self) -> usize;
fn lines_hint(&self) -> u16;
fn display(&self, output: OutputSink, area: Rect);
fn update(&mut self, key: KeyCode) -> Message;
fn update(&mut self, key: KeyCode) -> Result<Message, Error>;
}
pub type OutputSink<'a, 'b> = &'a mut Frame<'b, CrosstermBackend<Stdout>>;

View File

@ -32,21 +32,21 @@ impl State {
}
pub fn render(&self, terminal: &mut Terminal) -> Result<(), Error> {
let command_size = match &self.command {
None => 0,
Some(e) => match e {
let command_size = self.command.as_ref().map_or(
0,
|e| match e {
Either::Cmd(c) => c.lines_hint(),
Either::CmdOut(o) => o.lines_hint(),
}
};
);
let (dir_area, bottom_area) = if command_size > 0 {
(Rect {
height: self.size.height - command_size as u16 - 2,
height: self.size.height - command_size - 2,
..self.size
}, Rect {
y: self.size.height - command_size as u16 - 2,
height: command_size as u16 + 2,
y: self.size.height - command_size - 2,
height: command_size + 2,
..self.size
})
} else {
@ -84,7 +84,7 @@ impl State {
let (x, y) = pane.cursor_position()?;
let new_x = x + 1;
let dy = if let Some(Either::Cmd(c)) = &self.command {
self.size.height - c.lines_hint() as u16 - 2
self.size.height - c.lines_hint() - 2
} else {
self.size.y
};
@ -93,12 +93,12 @@ impl State {
Some((new_x, new_y))
}
pub fn update(&mut self, key: KeyCode) -> Message {
pub fn update(&mut self, key: KeyCode) -> Result<Message, Error> {
match self.command.as_mut() {
None => self.dir.update(key),
Some(either) => match either {
Either::Cmd(c) => c.update(key),
Either::CmdOut(_) => { self.close_window(); Message::Nothing },
Either::CmdOut(_) => { self.close_window(); Ok(Message::Nothing) },
}
}
}
@ -115,7 +115,8 @@ impl State {
.split(' ')
.filter(|s| !s.is_empty());
let prog_name = iter.next().unwrap();
let prog_name = iter.next()
.expect("must provide a program name");
let output = Executable::new(prog_name)
.args(iter)
.arg(self.dir.path())