diff --git a/src/main.rs b/src/main.rs index 6906741..e00528d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,10 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use clap::Parser; +use ratatui::Terminal; +use ratatui::backend::CrosstermBackend; +use std::io::stdout; use std::process::{Command, Stdio}; +use crate::tui::Tui; pub mod gui; pub mod tui; @@ -30,16 +34,17 @@ fn main() -> Result<()> { // If the CLI was provided a directory, convert it to absolute. Some(path) => std::path::absolute(path)?, // If no path was provided, use the current directory. - None => std::env::current_dir().unwrap_or_else(|_| - // If we can't find the CWD, attempt to open the home directory. - dirs::home_dir().expect("Failed to access filesystem.")), + None => std::env::current_dir().unwrap_or( + // If we can't find the CWD, attempt to open the home directory. + dirs::home_dir().context("Failed to obtain home directory")?, + ), }; match args.gui { true => gui::run(root_path), false => match args.tui { // Open the TUI editor if requested, otherwise use the QML GUI by default. - true => Ok(tui::Tui::new(root_path).start()?), + true => Ok(Tui::new(root_path)?.start()?), false => { // Relaunch the CLIDE GUI in a separate process. Command::new(std::env::current_exe()?) diff --git a/src/tui.rs b/src/tui.rs index d10ff05..97721b7 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -20,12 +20,11 @@ pub struct Tui { } impl Tui { - pub fn new(root_path: std::path::PathBuf) -> Self { - Self { - terminal: Terminal::new(CrosstermBackend::new(stdout())) - .expect("Failed to initialize terminal"), + pub fn new(root_path: std::path::PathBuf) -> Result { + Ok(Self { + terminal: Terminal::new(CrosstermBackend::new(stdout()))?, root_path, - } + }) } pub fn start(self) -> Result<()> { @@ -38,7 +37,7 @@ impl Tui { )?; enable_raw_mode()?; - let app_result = app::App::new(self.root_path) + let app_result = app::App::new(self.root_path)? .run(self.terminal) .context("Failed to start the TUI editor."); Self::stop()?; diff --git a/src/tui/app.rs b/src/tui/app.rs index 7fa5d26..1ea198e 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,7 +1,7 @@ use crate::tui::component::{Action, Component}; use crate::tui::editor::Editor; use crate::tui::explorer::Explorer; -use anyhow::{Result, anyhow}; +use anyhow::{Context, Result, anyhow}; use ratatui::buffer::Buffer; use ratatui::crossterm::event; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; @@ -23,18 +23,21 @@ pub struct App<'a> { } impl<'a> App<'a> { - pub(crate) fn new(root_path: PathBuf) -> Self { + pub fn new(root_path: PathBuf) -> Result { let mut app = Self { components: vec![ - AppComponents::AppExplorer(Explorer::new(&root_path)), + AppComponents::AppExplorer(Explorer::new(&root_path)?), AppComponents::AppEditor(Editor::new()), ], }; app.get_editor_mut() .unwrap() .set_contents(&root_path.join("src/tui/app.rs")) - .expect("Failed to set editor contents."); - app + .context(format!( + "Failed to initialize editor contents to path: {}", + root_path.to_string_lossy() + ))?; + Ok(app) } fn get_explorer(&self) -> Result<&Explorer<'a>> { @@ -77,27 +80,22 @@ impl<'a> App<'a> { None } - fn get_event(&mut self) -> Option { - if !event::poll(Duration::from_millis(250)).expect("event poll failed") { - return None; - } - - event::read().ok() - } - pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { loop { - // TODO: Handle events based on which component is active. + self.refresh_editor_contents() + .context("Failed to refresh editor contents.")?; + terminal.draw(|f| { f.render_widget(&mut self, f.area()); })?; - if let Some(event) = self.get_event() { - match self.handle_event(event) { + // TODO: Handle events based on which component is active. + if event::poll(Duration::from_millis(250)).context("event poll failed")? { + match self.handle_event(event::read()?)? { Action::Quit => break, Action::Handled => {} _ => { - // panic!("Unhandled event: {:?}", event); + // anyhow::anyhow!("Unhandled event: {:?}", event); } } } @@ -160,7 +158,7 @@ impl<'a> App<'a> { }; let editor = self .get_editor_mut() - .expect("Failed to get active editor while refreshing contents."); + .context("Failed to get active editor while refreshing contents.")?; if let Some(current_file_path) = editor.file_path.clone() { if selected_pathbuf == current_file_path || !selected_pathbuf.is_file() { return Ok(()); @@ -208,8 +206,6 @@ impl<'a> Widget for &mut App<'a> { explorer.render(horizontal[0], buf); } self.draw_tabs(editor_layout[0], buf); - self.refresh_editor_contents() - .expect("Failed to refresh editor contents."); self.get_editor_mut().unwrap().render(editor_layout[1], buf); } } @@ -227,12 +223,14 @@ impl<'a> Component for App<'a> { /// (such as editor tabs, file explorer, status bars, etc..) /// /// Handles events for the App and delegates to attached Components. - fn handle_event(&mut self, event: Event) -> Action { + fn handle_event(&mut self, event: Event) -> Result { // Handle events in the primary application. if let Some(key_event) = event.as_key_event() { - match self.handle_key_events(key_event) { - Action::Quit => return Action::Quit, - Action::Handled => return Action::Handled, + let res = self + .handle_key_events(key_event) + .context("Failed to handle key events for primary App Component."); + match res { + Ok(Action::Quit) | Ok(Action::Handled) => return res, _ => {} } } @@ -240,29 +238,29 @@ impl<'a> Component for App<'a> { // Handle events for all components. for component in &mut self.components { let action = match component { - AppComponents::AppEditor(editor) => editor.handle_event(event.clone()), - AppComponents::AppExplorer(explorer) => explorer.handle_event(event.clone()), - AppComponents::AppComponent(comp) => comp.handle_event(event.clone()), + AppComponents::AppEditor(editor) => editor.handle_event(event.clone())?, + AppComponents::AppExplorer(explorer) => explorer.handle_event(event.clone())?, + AppComponents::AppComponent(comp) => comp.handle_event(event.clone())?, }; // Actions returned here abort the input handling iteration. match action { - Action::Quit | Action::Handled => return action, + Action::Quit | Action::Handled => return Ok(action), _ => {} } } - Action::Noop + Ok(Action::Noop) } /// Handles key events for the App Component only. - fn handle_key_events(&mut self, key: KeyEvent) -> Action { + fn handle_key_events(&mut self, key: KeyEvent) -> Result { match key { KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, kind: KeyEventKind::Press, state: _state, - } => Action::Quit, - _ => Action::Noop, + } => Ok(Action::Quit), + _ => Ok(Action::Noop), } } } diff --git a/src/tui/component.rs b/src/tui/component.rs index 4af5b70..99dd7c9 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -1,5 +1,6 @@ #![allow(dead_code, unused_variables)] +use anyhow::Result; use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent}; pub enum Action { @@ -25,22 +26,22 @@ pub trait Component { /// This is used for lookup in a container of Components. fn id(&self) -> &str; - fn handle_event(&mut self, event: Event) -> Action { + fn handle_event(&mut self, event: Event) -> Result { match event { Event::Key(key_event) => self.handle_key_events(key_event), - _ => Action::Noop, + _ => Ok(Action::Noop), } } - fn handle_key_events(&mut self, key: KeyEvent) -> Action { - Action::Noop + fn handle_key_events(&mut self, key: KeyEvent) -> Result { + Ok(Action::Noop) } - fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Action { - Action::Noop + fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Result { + Ok(Action::Noop) } - fn update(&mut self, action: Action) -> Action { - Action::Noop + fn update(&mut self, action: Action) -> Result { + Ok(Action::Noop) } } diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 71eae11..411edfd 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -90,32 +90,32 @@ impl Component for Editor { "editor" } - fn handle_event(&mut self, event: Event) -> Action { + fn handle_event(&mut self, event: Event) -> Result { if let Some(key_event) = event.as_key_event() { // Handle events here that should not be passed on to the vim emulation handler. - match self.handle_key_events(key_event) { - Action::Handled => return Action::Handled, + match self.handle_key_events(key_event)? { + Action::Handled => return Ok(Action::Handled), _ => {} } } self.event_handler.on_event(event, &mut self.state); - Action::Pass + Ok(Action::Pass) } /// The events for the vim emulation should be handled by EditorEventHandler::on_event. /// These events are custom to the clide application. - fn handle_key_events(&mut self, key: KeyEvent) -> Action { + fn handle_key_events(&mut self, key: KeyEvent) -> Result { match key { KeyEvent { code: KeyCode::Char('s'), modifiers: KeyModifiers::CONTROL, .. } => { - self.save().expect("Failed to save file."); - Action::Handled + self.save().context("Failed to save file.")?; + Ok(Action::Handled) } // For other events not handled here, pass to the vim emulation handler. - _ => Action::Noop, + _ => Ok(Action::Noop), } } } diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 4b72648..aa39ae5 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,5 +1,5 @@ use crate::tui::component::{Action, Component}; -use anyhow::Result; +use anyhow::{Context, Result}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::layout::{Alignment, Position, Rect}; @@ -17,33 +17,36 @@ pub struct Explorer<'a> { } impl<'a> Explorer<'a> { - pub fn new(path: &std::path::PathBuf) -> Self { + pub fn new(path: &std::path::PathBuf) -> Result { let explorer = Explorer { root_path: path.to_owned(), - tree_items: Self::build_tree_from_path(path.to_owned()), + tree_items: Self::build_tree_from_path(path.to_owned())?, tree_state: TreeState::default(), }; - explorer + Ok(explorer) } - fn build_tree_from_path(path: std::path::PathBuf) -> TreeItem<'static, String> { + fn build_tree_from_path(path: std::path::PathBuf) -> Result> { let mut children = vec![]; if let Ok(entries) = fs::read_dir(&path) { let mut paths = entries .map(|res| res.map(|e| e.path())) .collect::, std::io::Error>>() - .expect(""); + .context(format!( + "Failed to build vector of paths under directory: {:?}", + path + ))?; paths.sort(); for path in paths { if path.is_dir() { - children.push(Self::build_tree_from_path(path)); + children.push(Self::build_tree_from_path(path)?); } else { if let Ok(path) = std::path::absolute(&path) { let path_str = path.to_string_lossy().to_string(); children.push(TreeItem::new_leaf( path_str, path.file_name() - .expect("Failed to get file name from path.") + .context("Failed to get file name from path.")? .to_string_lossy() .to_string(), )); @@ -53,13 +56,10 @@ impl<'a> Explorer<'a> { } let abs = std::path::absolute(&path) - .expect( - format!( - "Failed to find absolute path for TreeItem: {}", - path.to_string_lossy().to_string() - ) - .as_str(), - ) + .context(format!( + "Failed to find absolute path for TreeItem: {:?}", + path + ))? .to_string_lossy() .to_string(); TreeItem::new( @@ -70,13 +70,13 @@ impl<'a> Explorer<'a> { .to_string(), children, ) - .expect("Failed to build tree from path.") + .context("Failed to build tree from path.") } - pub fn render(&mut self, area: Rect, buf: &mut Buffer) { + pub fn render(&mut self, area: Rect, buf: &mut Buffer) -> Result<()> { StatefulWidget::render( Tree::new(&self.tree_items.children()) - .expect("Failed to build tree.") + .context("Failed to build file Explorer Tree.")? .style(Style::default()) .block( Block::default() @@ -84,7 +84,7 @@ impl<'a> Explorer<'a> { .title( self.root_path .file_name() - .expect("Failed to get file name from path.") + .context("Failed to get file name from path.")? .to_string_lossy(), ) .title_style(Style::default().fg(Color::Green)) @@ -99,7 +99,8 @@ impl<'a> Explorer<'a> { area, buf, &mut self.tree_state, - ) + ); + Ok(()) } pub fn selected(&self) -> Result { @@ -114,24 +115,24 @@ impl<'a> Component for Explorer<'a> { fn id(&self) -> &str { "explorer" } - fn handle_event(&mut self, event: Event) -> Action { + fn handle_event(&mut self, event: Event) -> Result { if let Some(key_event) = event.as_key_event() { // Handle events here that should not be passed on to the vim emulation handler. - match self.handle_key_events(key_event) { - Action::Handled => return Action::Handled, + match self.handle_key_events(key_event)? { + Action::Handled => return Ok(Action::Handled), _ => {} } } if let Some(mouse_event) = event.as_mouse_event() { - match self.handle_mouse_events(mouse_event) { - Action::Handled => return Action::Handled, + match self.handle_mouse_events(mouse_event)? { + Action::Handled => return Ok(Action::Handled), _ => {} } } - Action::Pass + Ok(Action::Pass) } - fn handle_key_events(&mut self, key: KeyEvent) -> Action { + fn handle_key_events(&mut self, key: KeyEvent) -> Result { let changed = match key.code { KeyCode::Up => self.tree_state.key_up(), KeyCode::Char('k') => self.tree_state.key_up(), @@ -145,12 +146,12 @@ impl<'a> Component for Explorer<'a> { _ => false, }; if changed { - return Action::Handled; + return Ok(Action::Handled); } - Action::Noop + Ok(Action::Noop) } - fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Action { + fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Result { let changed = match mouse.kind { MouseEventKind::ScrollDown => self.tree_state.scroll_down(1), MouseEventKind::ScrollUp => self.tree_state.scroll_up(1), @@ -160,8 +161,8 @@ impl<'a> Component for Explorer<'a> { _ => false, }; if changed { - return Action::Handled; + return Ok(Action::Handled); } - Action::Noop + Ok(Action::Noop) } }