Better handling of panics
parent
04289a4724
commit
b56ee8ffa5
|
@ -51,6 +51,12 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"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]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
@ -172,8 +178,10 @@ dependencies = [
|
||||||
name = "dirbuilder"
|
name = "dirbuilder"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm 0.28.1",
|
"crossterm 0.28.1",
|
||||||
|
"gag",
|
||||||
"tui",
|
"tui",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -187,6 +195,33 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"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]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -258,6 +293,15 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"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]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
|
@ -281,6 +325,12 @@ dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.86"
|
version = "1.0.86"
|
||||||
|
@ -381,6 +431,39 @@ dependencies = [
|
||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "tui"
|
name = "tui"
|
||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
|
@ -464,6 +547,15 @@ dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
|
|
|
@ -4,8 +4,10 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.89"
|
||||||
clap = { version = "4.5.19", features = ["derive"] }
|
clap = { version = "4.5.19", features = ["derive"] }
|
||||||
crossterm = "0.28.1"
|
crossterm = "0.28.1"
|
||||||
|
gag = "1.0.0"
|
||||||
tui = "0.19.0"
|
tui = "0.19.0"
|
||||||
|
|
||||||
[lints.clippy]
|
[lints.clippy]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::io::Error;
|
||||||
|
|
||||||
use tui::widgets::{Block, Borders, Paragraph};
|
use tui::widgets::{Block, Borders, Paragraph};
|
||||||
|
|
||||||
use crate::pane::prelude::*;
|
use crate::pane::prelude::*;
|
||||||
|
@ -16,12 +18,12 @@ impl Command {
|
||||||
impl Pane for Command {
|
impl Pane for Command {
|
||||||
fn cursor_position(&self) -> Option<(u16, u16)> {
|
fn cursor_position(&self) -> Option<(u16, u16)> {
|
||||||
let x = u16::try_from(self.buf.len())
|
let x = u16::try_from(self.buf.len())
|
||||||
.expect("too big to display");
|
.unwrap_or(u16::MAX);
|
||||||
let y = 0;
|
let y = 0;
|
||||||
Some((x, y))
|
Some((x, y))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lines_hint(&self) -> usize {
|
fn lines_hint(&self) -> u16 {
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,13 +34,15 @@ impl Pane for Command {
|
||||||
output.render_widget(widget, area);
|
output.render_widget(widget, area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, key: KeyCode) -> Message {
|
fn update(&mut self, key: KeyCode) -> Result<Message, Error> {
|
||||||
match key {
|
let result = match key {
|
||||||
KeyCode::Char(c) => { self.buf.push(c); Message::Nothing },
|
KeyCode::Char(c) => { self.buf.push(c); Message::Nothing },
|
||||||
KeyCode::Backspace => { self.buf.pop(); Message::Nothing },
|
KeyCode::Backspace => { self.buf.pop(); Message::Nothing },
|
||||||
KeyCode::Esc => Message::CancelCommand,
|
KeyCode::Esc => Message::CancelCommand,
|
||||||
KeyCode::Enter => Message::ExecuteCommand,
|
KeyCode::Enter if !self.buf.is_empty() => Message::ExecuteCommand,
|
||||||
_ => Message::Nothing,
|
_ => Message::Nothing,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -26,9 +26,9 @@ impl Pane for CommandOutput {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lines_hint(&self) -> usize {
|
fn lines_hint(&self) -> u16 {
|
||||||
match self.contents.as_ref() {
|
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,
|
Ok(None) => 0,
|
||||||
Err(_) => 1,
|
Err(_) => 1,
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ impl Pane for CommandOutput {
|
||||||
output.render_widget(widget, area);
|
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")
|
unimplemented!("Output does not receive input")
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -114,7 +114,7 @@ impl Directory {
|
||||||
self.selection as usize
|
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 {
|
match key {
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Char(c) => {
|
||||||
match c {
|
match c {
|
||||||
|
@ -127,7 +127,7 @@ impl Directory {
|
||||||
self.editing = true;
|
self.editing = true;
|
||||||
},
|
},
|
||||||
'x' | 'X' => {
|
'x' | 'X' => {
|
||||||
return Message::BeginCommand;
|
return Ok(Message::BeginCommand);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
@ -138,9 +138,9 @@ impl Directory {
|
||||||
self.src_name = Some(self.path());
|
self.src_name = Some(self.path());
|
||||||
self.editing = true;
|
self.editing = true;
|
||||||
},
|
},
|
||||||
KeyCode::Esc => return Message::Exit,
|
KeyCode::Esc => return Ok(Message::Exit),
|
||||||
KeyCode::Backspace if self.selected().depth > 0 => {
|
KeyCode::Backspace if self.selected().depth > 0 => {
|
||||||
self.delete().unwrap();
|
self.delete()?;
|
||||||
if self.selected().is_file {
|
if self.selected().is_file {
|
||||||
self.dirs.remove(self.selection());
|
self.dirs.remove(self.selection());
|
||||||
} else {
|
} 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 {
|
match key {
|
||||||
KeyCode::Char(c) => self.selected_mut().name.push(c),
|
KeyCode::Char(c) => self.selected_mut().name.push(c),
|
||||||
KeyCode::Enter if !self.selected().name.is_empty() => {
|
KeyCode::Enter if !self.selected().name.is_empty() => {
|
||||||
self.create().unwrap();
|
self.create()?;
|
||||||
if let Some(src) = self.src_name.take() {
|
if let Some(src) = self.src_name.take() {
|
||||||
rename(src, self.path()).unwrap();
|
rename(src, self.path())?;
|
||||||
}
|
}
|
||||||
self.editing = false;
|
self.editing = false;
|
||||||
},
|
},
|
||||||
|
@ -178,7 +178,7 @@ impl Directory {
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
.map_or(String::new(), ToOwned::to_owned);
|
.map_or(String::new(), ToOwned::to_owned);
|
||||||
self.editing = false;
|
self.editing = false;
|
||||||
} else if self.selected().name.len() > 0 {
|
} else if ! self.selected().name.is_empty() {
|
||||||
self.editing = false;
|
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
|
let x = u16::from(self.selected().depth) * 4
|
||||||
+ u16::try_from(self.selected().name.len())
|
+ u16::try_from(self.selected().name.len())
|
||||||
.expect("name too long to display");
|
.unwrap_or(u16::MAX);
|
||||||
let y = self.selection;
|
let y = self.selection;
|
||||||
Some((x, y))
|
Some((x, y))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lines_hint(&self) -> usize {
|
fn lines_hint(&self) -> u16 {
|
||||||
self.dirs.len()
|
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 {
|
if self.editing {
|
||||||
self.update_edit(key)
|
self.update_edit(key)
|
||||||
} else {
|
} else {
|
||||||
|
|
28
src/main.rs
28
src/main.rs
|
@ -13,12 +13,36 @@ use state::State;
|
||||||
use terminal::Terminal;
|
use terminal::Terminal;
|
||||||
use pane::prelude::*;
|
use pane::prelude::*;
|
||||||
|
|
||||||
|
use std::fs::{remove_file, OpenOptions};
|
||||||
use std::io::Error;
|
use std::io::Error;
|
||||||
|
|
||||||
|
use anyhow::{Result as AnyResult, Error as AnyError};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use crossterm::event::{read, Event, KeyEvent};
|
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 args = Args::parse();
|
||||||
|
|
||||||
let mut terminal = Terminal::new()?;
|
let mut terminal = Terminal::new()?;
|
||||||
|
@ -39,7 +63,7 @@ fn main() -> Result<(), Error> {
|
||||||
let Event::Key(KeyEvent { code: key, .. }) = read()?
|
let Event::Key(KeyEvent { code: key, .. }) = read()?
|
||||||
else { continue };
|
else { continue };
|
||||||
|
|
||||||
match state.update(key) {
|
match state.update(key)? {
|
||||||
Message::Nothing => (),
|
Message::Nothing => (),
|
||||||
Message::Exit => break,
|
Message::Exit => break,
|
||||||
Message::BeginCommand => state.begin_command(),
|
Message::BeginCommand => state.begin_command(),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::io::Stdout;
|
use std::io::{Error, Stdout};
|
||||||
|
|
||||||
use crossterm::event::KeyCode;
|
use crossterm::event::KeyCode;
|
||||||
use tui::{layout::Rect, Frame};
|
use tui::{layout::Rect, Frame};
|
||||||
|
@ -7,11 +7,11 @@ use tui::backend::CrosstermBackend;
|
||||||
pub trait Pane {
|
pub trait Pane {
|
||||||
fn cursor_position(&self) -> Option<(u16, u16)>;
|
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 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>>;
|
pub type OutputSink<'a, 'b> = &'a mut Frame<'b, CrosstermBackend<Stdout>>;
|
||||||
|
|
23
src/state.rs
23
src/state.rs
|
@ -32,21 +32,21 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&self, terminal: &mut Terminal) -> Result<(), Error> {
|
pub fn render(&self, terminal: &mut Terminal) -> Result<(), Error> {
|
||||||
let command_size = match &self.command {
|
let command_size = self.command.as_ref().map_or(
|
||||||
None => 0,
|
0,
|
||||||
Some(e) => match e {
|
|e| match e {
|
||||||
Either::Cmd(c) => c.lines_hint(),
|
Either::Cmd(c) => c.lines_hint(),
|
||||||
Either::CmdOut(o) => o.lines_hint(),
|
Either::CmdOut(o) => o.lines_hint(),
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
|
|
||||||
let (dir_area, bottom_area) = if command_size > 0 {
|
let (dir_area, bottom_area) = if command_size > 0 {
|
||||||
(Rect {
|
(Rect {
|
||||||
height: self.size.height - command_size as u16 - 2,
|
height: self.size.height - command_size - 2,
|
||||||
..self.size
|
..self.size
|
||||||
}, Rect {
|
}, Rect {
|
||||||
y: self.size.height - command_size as u16 - 2,
|
y: self.size.height - command_size - 2,
|
||||||
height: command_size as u16 + 2,
|
height: command_size + 2,
|
||||||
..self.size
|
..self.size
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -84,7 +84,7 @@ impl State {
|
||||||
let (x, y) = pane.cursor_position()?;
|
let (x, y) = pane.cursor_position()?;
|
||||||
let new_x = x + 1;
|
let new_x = x + 1;
|
||||||
let dy = if let Some(Either::Cmd(c)) = &self.command {
|
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 {
|
} else {
|
||||||
self.size.y
|
self.size.y
|
||||||
};
|
};
|
||||||
|
@ -93,12 +93,12 @@ impl State {
|
||||||
Some((new_x, new_y))
|
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() {
|
match self.command.as_mut() {
|
||||||
None => self.dir.update(key),
|
None => self.dir.update(key),
|
||||||
Some(either) => match either {
|
Some(either) => match either {
|
||||||
Either::Cmd(c) => c.update(key),
|
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(' ')
|
.split(' ')
|
||||||
.filter(|s| !s.is_empty());
|
.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)
|
let output = Executable::new(prog_name)
|
||||||
.args(iter)
|
.args(iter)
|
||||||
.arg(self.dir.path())
|
.arg(self.dir.path())
|
||||||
|
|
Loading…
Reference in New Issue