From 4c176609567eeaab5c78302370bbd37764cf4507 Mon Sep 17 00:00:00 2001 From: Mehran Kordi Date: Sat, 23 May 2020 17:12:38 +0200 Subject: [PATCH] anyhow integration (closes #77) --- Cargo.lock | 7 ++ Cargo.toml | 1 + asyncgit/src/lib.rs | 1 + asyncgit/src/status.rs | 2 +- scopetime/src/lib.rs | 1 + src/app.rs | 101 +++++++++++++++++------------ src/components/changes.rs | 60 ++++++++++------- src/components/commit.rs | 45 ++++++++----- src/components/diff.rs | 93 +++++++++++++++----------- src/components/filetree.rs | 39 ++++++----- src/components/help.rs | 26 +++++--- src/components/mod.rs | 23 ++++--- src/components/msg.rs | 31 ++++++--- src/components/reset.rs | 32 ++++++--- src/components/stashmsg.rs | 27 +++++--- src/components/textinput.rs | 25 ++++--- src/components/utils/filetree.rs | 95 +++++++++++++++++---------- src/components/utils/statustree.rs | 33 ++++++---- src/main.rs | 57 +++++++++------- src/poll.rs | 13 ++-- src/tabs/revlog/mod.rs | 74 ++++++++++++--------- src/tabs/stashing.rs | 57 +++++++++------- src/tabs/status.rs | 95 +++++++++++++++------------ src/ui/style.rs | 48 +++++++------- 24 files changed, 595 insertions(+), 391 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9609f1bc..fb08bb04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,12 @@ dependencies = [ "gimli", ] +[[package]] +name = "anyhow" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" + [[package]] name = "arc-swap" version = "0.4.6" @@ -295,6 +301,7 @@ dependencies = [ name = "gitui" version = "0.3.0" dependencies = [ + "anyhow", "asyncgit", "backtrace", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index 77e3a3b0..1e7acb15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ chrono = "0.4" backtrace = "0.3" ron = "0.6" serde = "1.0" +anyhow = "1.0.31" [features] default=[] diff --git a/asyncgit/src/lib.rs b/asyncgit/src/lib.rs index 3b03ee42..4251d794 100644 --- a/asyncgit/src/lib.rs +++ b/asyncgit/src/lib.rs @@ -3,6 +3,7 @@ #![forbid(unsafe_code)] #![forbid(missing_docs)] #![deny(clippy::all)] +#![deny(clippy::result_unwrap_used)] mod diff; mod error; diff --git a/asyncgit/src/status.rs b/asyncgit/src/status.rs index 6bbe61f5..1b791855 100644 --- a/asyncgit/src/status.rs +++ b/asyncgit/src/status.rs @@ -16,7 +16,7 @@ use sync::status::StatusType; fn current_tick() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) - .unwrap() + .expect("time before unix epoch!") .as_millis() as u64 } diff --git a/scopetime/src/lib.rs b/scopetime/src/lib.rs index b481e0d8..58478d07 100644 --- a/scopetime/src/lib.rs +++ b/scopetime/src/lib.rs @@ -2,6 +2,7 @@ #![forbid(unsafe_code)] #![forbid(missing_docs)] +#![deny(clippy::result_unwrap_used)] use log::trace; use std::time::Instant; diff --git a/src/app.rs b/src/app.rs index 308ca85d..f5b93b27 100644 --- a/src/app.rs +++ b/src/app.rs @@ -11,6 +11,7 @@ use crate::{ tabs::{Revlog, Stashing, Status}, ui::style::Theme, }; +use anyhow::Result; use asyncgit::{sync, AsyncNotification, CWD}; use crossbeam_channel::Sender; use crossterm::event::Event; @@ -26,7 +27,6 @@ use tui::{ widgets::{Block, Borders, Paragraph, Tabs, Text}, Frame, }; - /// pub struct App { do_quit: bool, @@ -73,7 +73,10 @@ impl App { } /// - pub fn draw(&mut self, f: &mut Frame) { + pub fn draw( + &mut self, + f: &mut Frame, + ) -> Result<()> { let chunks_main = Layout::default() .direction(Direction::Vertical) .constraints( @@ -90,9 +93,9 @@ impl App { //TODO: macro because of generic draw call match self.tab { - 0 => self.status_tab.draw(f, chunks_main[1]), - 1 => self.revlog.draw(f, chunks_main[1]), - 2 => self.stashing_tab.draw(f, chunks_main[1]), + 0 => self.status_tab.draw(f, chunks_main[1])?, + 1 => self.revlog.draw(f, chunks_main[1])?, + 2 => self.stashing_tab.draw(f, chunks_main[1])?, _ => panic!("unknown tab"), }; @@ -103,25 +106,27 @@ impl App { self.theme, ); - self.draw_popups(f); + self.draw_popups(f)?; + + Ok(()) } /// - pub fn event(&mut self, ev: Event) { + pub fn event(&mut self, ev: Event) -> Result<()> { trace!("event: {:?}", ev); if self.check_quit(ev) { - return; + return Ok(()); } let mut flags = NeedsUpdate::empty(); - if event_pump(ev, self.components_mut().as_mut_slice()) { + if event_pump(ev, self.components_mut().as_mut_slice())? { flags.insert(NeedsUpdate::COMMANDS); } else if let Event::Key(k) = ev { let new_flags = match k { keys::TAB_TOGGLE => { - self.toggle_tabs(); + self.toggle_tabs()?; NeedsUpdate::COMMANDS } @@ -131,42 +136,51 @@ impl App { flags.insert(new_flags); } - let new_flags = self.process_queue(); + let new_flags = self.process_queue()?; flags.insert(new_flags); if flags.contains(NeedsUpdate::ALL) { - self.update(); + self.update()?; } if flags.contains(NeedsUpdate::DIFF) { - self.status_tab.update_diff(); + self.status_tab.update_diff()?; } if flags.contains(NeedsUpdate::COMMANDS) { self.update_commands(); } + + Ok(()) } //TODO: do we need this? /// forward ticking to components that require it - pub fn update(&mut self) { + pub fn update(&mut self) -> Result<()> { trace!("update"); - self.status_tab.update(); - self.revlog.update(); - self.stashing_tab.update(); + self.status_tab.update()?; + self.revlog.update()?; + self.stashing_tab.update()?; + + Ok(()) } /// - pub fn update_git(&mut self, ev: AsyncNotification) { + pub fn update_git( + &mut self, + ev: AsyncNotification, + ) -> Result<()> { trace!("update_git: {:?}", ev); - self.status_tab.update_git(ev); - self.stashing_tab.update_git(ev); + self.status_tab.update_git(ev)?; + self.stashing_tab.update_git(ev)?; match ev { AsyncNotification::Diff => (), - AsyncNotification::Log => self.revlog.update(), + AsyncNotification::Log => self.revlog.update()?, //TODO: is that needed? AsyncNotification::Status => self.update_commands(), } + + Ok(()) } /// @@ -216,7 +230,7 @@ impl App { ] } - fn toggle_tabs(&mut self) { + fn toggle_tabs(&mut self) -> Result<()> { let mut new_tab = self.tab + 1; { let tabs = self.get_tabs(); @@ -224,13 +238,15 @@ impl App { for (i, t) in tabs.into_iter().enumerate() { if new_tab == i { - t.show(); + t.show()?; } else { t.hide(); } } } self.tab = new_tab; + + Ok(()) } fn update_commands(&mut self) { @@ -239,25 +255,25 @@ impl App { self.current_commands.sort_by_key(|e| e.order); } - fn process_queue(&mut self) -> NeedsUpdate { + fn process_queue(&mut self) -> Result { let mut flags = NeedsUpdate::empty(); loop { let front = self.queue.borrow_mut().pop_front(); if let Some(e) = front { - flags.insert(self.process_internal_event(e)); + flags.insert(self.process_internal_event(e)?); } else { break; } } self.queue.borrow_mut().clear(); - flags + Ok(flags) } fn process_internal_event( &mut self, ev: InternalEvent, - ) -> NeedsUpdate { + ) -> Result { let mut flags = NeedsUpdate::empty(); match ev { InternalEvent::ResetItem(reset_item) => { @@ -280,7 +296,7 @@ impl App { } } InternalEvent::ConfirmResetItem(reset_item) => { - self.reset.open_for_path(reset_item); + self.reset.open_for_path(reset_item)?; flags.insert(NeedsUpdate::COMMANDS); } InternalEvent::AddHunk(hash) => { @@ -288,9 +304,7 @@ impl App { self.status_tab.selected_path() { if is_stage { - if sync::unstage_hunk(CWD, path, hash) - .unwrap() - { + if sync::unstage_hunk(CWD, path, hash)? { flags.insert(NeedsUpdate::ALL); } } else if sync::stage_hunk(CWD, path, hash) @@ -301,17 +315,17 @@ impl App { } } InternalEvent::ShowErrorMsg(msg) => { - self.msg.show_msg(msg.as_str()); + self.msg.show_msg(msg.as_str())?; flags.insert(NeedsUpdate::ALL); } InternalEvent::Update(u) => flags.insert(u), - InternalEvent::OpenCommit => self.commit.show(), + InternalEvent::OpenCommit => self.commit.show()?, InternalEvent::PopupStashing(_opts) => { - self.stashmsg_popup.show() + self.stashmsg_popup.show()? } }; - flags + Ok(flags) } fn commands(&self, force_all: bool) -> Vec { @@ -354,14 +368,19 @@ impl App { || self.msg.is_visible() } - fn draw_popups(&mut self, f: &mut Frame) { + fn draw_popups( + &mut self, + f: &mut Frame, + ) -> Result<()> { let size = f.size(); - self.commit.draw(f, size); - self.stashmsg_popup.draw(f, size); - self.reset.draw(f, size); - self.help.draw(f, size); - self.msg.draw(f, size); + self.commit.draw(f, size)?; + self.stashmsg_popup.draw(f, size)?; + self.reset.draw(f, size)?; + self.help.draw(f, size)?; + self.msg.draw(f, size)?; + + Ok(()) } fn draw_tabs(&self, f: &mut Frame, r: Rect) { diff --git a/src/components/changes.rs b/src/components/changes.rs index 7a0c6a9a..259ffba7 100644 --- a/src/components/changes.rs +++ b/src/components/changes.rs @@ -10,6 +10,7 @@ use crate::{ strings, ui::style::Theme, }; +use anyhow::Result; use asyncgit::{sync, StatusItem, StatusItemType, CWD}; use crossterm::event::Event; use std::path::Path; @@ -45,8 +46,10 @@ impl ChangesComponent { } /// - pub fn update(&mut self, list: &[StatusItem]) { - self.files.update(list) + pub fn update(&mut self, list: &[StatusItem]) -> Result<()> { + self.files.update(list)?; + + Ok(()) } /// @@ -69,38 +72,39 @@ impl ChangesComponent { self.files.is_file_seleted() } - fn index_add_remove(&mut self) -> bool { + fn index_add_remove(&mut self) -> Result { if let Some(tree_item) = self.selection() { if self.is_working_dir { if let FileTreeItemKind::File(i) = tree_item.kind { if let Some(status) = i.status { let path = Path::new(i.path.as_str()); - return match status { + match status { StatusItemType::Deleted => { - sync::stage_addremoved(CWD, path) - .is_ok() + sync::stage_addremoved(CWD, path)? } - _ => sync::stage_add_file(CWD, path) - .is_ok(), + _ => sync::stage_add_file(CWD, path)?, }; + + return Ok(true); } } else { //TODO: check if we can handle the one file case with it aswell - return sync::stage_add_all( + sync::stage_add_all( CWD, tree_item.info.full_path.as_str(), - ) - .is_ok(); + )?; + + return Ok(true); } } else { let path = Path::new(tree_item.info.full_path.as_str()); - sync::reset_stage(CWD, path).unwrap(); - return true; + sync::reset_stage(CWD, path)?; + return Ok(true); } } - false + Ok(false) } fn dispatch_reset_workdir(&mut self) -> bool { @@ -121,8 +125,14 @@ impl ChangesComponent { } impl DrawableComponent for ChangesComponent { - fn draw(&mut self, f: &mut Frame, r: Rect) { - self.files.draw(f, r) + fn draw( + &mut self, + f: &mut Frame, + r: Rect, + ) -> Result<()> { + self.files.draw(f, r)?; + + Ok(()) } } @@ -166,9 +176,9 @@ impl Component for ChangesComponent { CommandBlocking::PassingOn } - fn event(&mut self, ev: Event) -> bool { - if self.files.event(ev) { - return true; + fn event(&mut self, ev: Event) -> Result { + if self.files.event(ev)? { + return Ok(true); } if self.focused() { @@ -181,29 +191,29 @@ impl Component for ChangesComponent { self.queue .borrow_mut() .push_back(InternalEvent::OpenCommit); - true + Ok(true) } keys::STATUS_STAGE_FILE => { - if self.index_add_remove() { + if self.index_add_remove()? { self.queue.borrow_mut().push_back( InternalEvent::Update( NeedsUpdate::ALL, ), ); } - true + Ok(true) } keys::STATUS_RESET_FILE if self.is_working_dir => { - self.dispatch_reset_workdir() + Ok(self.dispatch_reset_workdir()) } - _ => false, + _ => Ok(false), }; } } - false + Ok(false) } fn focused(&self) -> bool { diff --git a/src/components/commit.rs b/src/components/commit.rs index 8f023a7f..5019dfd9 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -7,6 +7,7 @@ use crate::{ strings, ui::style::Theme, }; +use anyhow::Result; use asyncgit::{sync, CWD}; use crossterm::event::{Event, KeyCode}; use log::error; @@ -20,8 +21,14 @@ pub struct CommitComponent { } impl DrawableComponent for CommitComponent { - fn draw(&mut self, f: &mut Frame, rect: Rect) { - self.input.draw(f, rect) + fn draw( + &mut self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + self.input.draw(f, rect)?; + + Ok(()) } } @@ -41,25 +48,27 @@ impl Component for CommitComponent { visibility_blocking(self) } - fn event(&mut self, ev: Event) -> bool { + fn event(&mut self, ev: Event) -> Result { if self.is_visible() { - if self.input.event(ev) { - return true; + if self.input.event(ev)? { + return Ok(true); } if let Event::Key(e) = ev { match e.code { KeyCode::Enter if self.can_commit() => { - self.commit(); + self.commit()?; } + _ => (), }; // stop key event propagation - return true; + return Ok(true); } } - false + + Ok(false) } fn is_visible(&self) -> bool { @@ -70,8 +79,10 @@ impl Component for CommitComponent { self.input.hide() } - fn show(&mut self) { - self.input.show() + fn show(&mut self) -> Result<()> { + self.input.show()?; + + Ok(()) } } @@ -88,10 +99,10 @@ impl CommitComponent { } } - fn commit(&mut self) { + fn commit(&mut self) -> Result<()> { let mut msg = self.input.get_text().clone(); if let HookResult::NotOk(e) = - sync::hooks_commit_msg(CWD, &mut msg).unwrap() + sync::hooks_commit_msg(CWD, &mut msg)? { error!("commit-msg hook error: {}", e); self.queue.borrow_mut().push_back( @@ -100,7 +111,7 @@ impl CommitComponent { e )), ); - return; + return Ok(()); } if let Err(e) = sync::commit(CWD, &msg) { @@ -111,12 +122,10 @@ impl CommitComponent { &e )), ); - return; + return Ok(()); } - if let HookResult::NotOk(e) = - sync::hooks_post_commit(CWD).unwrap() - { + if let HookResult::NotOk(e) = sync::hooks_post_commit(CWD)? { error!("post-commit hook error: {}", e); self.queue.borrow_mut().push_back( InternalEvent::ShowErrorMsg(format!( @@ -132,6 +141,8 @@ impl CommitComponent { self.queue .borrow_mut() .push_back(InternalEvent::Update(NeedsUpdate::ALL)); + + Ok(()) } fn can_commit(&self) -> bool { diff --git a/src/components/diff.rs b/src/components/diff.rs index 55e72f66..c1190026 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -18,6 +18,8 @@ use tui::{ Frame, }; +use anyhow::Result; + #[derive(Default)] struct Current { path: String, @@ -60,13 +62,15 @@ impl DiffComponent { (self.current.path.clone(), self.current.is_stage) } /// - pub fn clear(&mut self) { + pub fn clear(&mut self) -> Result<()> { self.current = Current::default(); self.diff = FileDiff::default(); self.scroll = 0; self.selected_hunk = - Self::find_selected_hunk(&self.diff, self.scroll); + Self::find_selected_hunk(&self.diff, self.scroll)?; + + Ok(()) } /// pub fn update( @@ -74,7 +78,7 @@ impl DiffComponent { path: String, is_stage: bool, diff: FileDiff, - ) { + ) -> Result<()> { let hash = hash(&diff); if self.current.hash != hash { @@ -87,11 +91,13 @@ impl DiffComponent { self.scroll = 0; self.selected_hunk = - Self::find_selected_hunk(&self.diff, self.scroll); + Self::find_selected_hunk(&self.diff, self.scroll)?; } + + Ok(()) } - fn scroll(&mut self, scroll: ScrollType) { + fn scroll(&mut self, scroll: ScrollType) -> Result<()> { let old = self.scroll; let scroll_max = self.diff.lines.saturating_sub(1); @@ -113,17 +119,19 @@ impl DiffComponent { if old != self.scroll { self.selected_hunk = - Self::find_selected_hunk(&self.diff, self.scroll); + Self::find_selected_hunk(&self.diff, self.scroll)?; } + + Ok(()) } fn find_selected_hunk( diff: &FileDiff, line_selected: u16, - ) -> Option { + ) -> Result> { let mut line_cursor = 0_u16; for (i, hunk) in diff.hunks.iter().enumerate() { - let hunk_len = u16::try_from(hunk.lines.len()).unwrap(); + let hunk_len = u16::try_from(hunk.lines.len())?; let hunk_min = line_cursor; let hunk_max = line_cursor + hunk_len; @@ -131,16 +139,16 @@ impl DiffComponent { hunk_min <= line_selected && hunk_max > line_selected; if hunk_selected { - return Some(u16::try_from(i).unwrap()); + return Ok(Some(u16::try_from(i)?)); } line_cursor += hunk_len; } - None + Ok(None) } - fn get_text(&self, width: u16, height: u16) -> Vec { + fn get_text(&self, width: u16, height: u16) -> Result> { let selection = self.scroll; let height_d2 = height / 2; let min = self.scroll.saturating_sub(height_d2); @@ -151,15 +159,16 @@ impl DiffComponent { let mut lines_added = 0_u16; for (i, hunk) in self.diff.hunks.iter().enumerate() { - let hunk_selected = self - .selected_hunk - .map_or(false, |s| s == u16::try_from(i).unwrap()); + let hunk_selected = + self.selected_hunk.map_or(false, |s| { + s == u16::try_from(i).unwrap_or_default() + }); if lines_added >= height { break; } - let hunk_len = u16::try_from(hunk.lines.len()).unwrap(); + let hunk_len = u16::try_from(hunk.lines.len())?; let hunk_min = line_cursor; let hunk_max = line_cursor + hunk_len; @@ -184,7 +193,7 @@ impl DiffComponent { line_cursor += hunk_len; } } - res + Ok(res) } fn add_line( @@ -258,25 +267,29 @@ impl DiffComponent { false } - fn add_hunk(&self) { + fn add_hunk(&self) -> Result<()> { if let Some(hunk) = self.selected_hunk { - let hash = self.diff.hunks - [usize::try_from(hunk).unwrap()] - .header_hash; + let hash = self.diff.hunks[usize::from(hunk)].header_hash; self.queue .borrow_mut() .push_back(InternalEvent::AddHunk(hash)); } + + Ok(()) } } impl DrawableComponent for DiffComponent { - fn draw(&mut self, f: &mut Frame, r: Rect) { + fn draw( + &mut self, + f: &mut Frame, + r: Rect, + ) -> Result<()> { self.current_height = r.height.saturating_sub(2); let title = format!("{}{}", strings::TITLE_DIFF, self.current.path); f.render_widget( - Paragraph::new(self.get_text(r.width, r.height).iter()) + Paragraph::new(self.get_text(r.width, r.height)?.iter()) .block( Block::default() .title(title.as_str()) @@ -287,6 +300,8 @@ impl DrawableComponent for DiffComponent { .alignment(Alignment::Left), r, ); + + Ok(()) } } @@ -325,44 +340,44 @@ impl Component for DiffComponent { CommandBlocking::PassingOn } - fn event(&mut self, ev: Event) -> bool { + fn event(&mut self, ev: Event) -> Result { if self.focused { if let Event::Key(e) = ev { return match e { keys::MOVE_DOWN => { - self.scroll(ScrollType::Down); - true + self.scroll(ScrollType::Down)?; + Ok(true) } keys::SHIFT_DOWN | keys::END => { - self.scroll(ScrollType::End); - true + self.scroll(ScrollType::End)?; + Ok(true) } keys::HOME | keys::SHIFT_UP => { - self.scroll(ScrollType::Home); - true + self.scroll(ScrollType::Home)?; + Ok(true) } keys::MOVE_UP => { - self.scroll(ScrollType::Up); - true + self.scroll(ScrollType::Up)?; + Ok(true) } keys::PAGE_UP => { - self.scroll(ScrollType::PageUp); - true + self.scroll(ScrollType::PageUp)?; + Ok(true) } keys::PAGE_DOWN => { - self.scroll(ScrollType::PageDown); - true + self.scroll(ScrollType::PageDown)?; + Ok(true) } keys::ENTER => { - self.add_hunk(); - true + self.add_hunk()?; + Ok(true) } - _ => false, + _ => Ok(false), }; } } - false + Ok(false) } fn focused(&self) -> bool { diff --git a/src/components/filetree.rs b/src/components/filetree.rs index d2ffd6c2..aef1bfa5 100644 --- a/src/components/filetree.rs +++ b/src/components/filetree.rs @@ -12,6 +12,8 @@ use crate::{ strings, ui, ui::style::Theme, }; + +use anyhow::Result; use asyncgit::{hash, StatusItem, StatusItemType}; use crossterm::event::Event; use std::{borrow::Cow, convert::From, path::Path}; @@ -49,12 +51,14 @@ impl FileTreeComponent { } /// - pub fn update(&mut self, list: &[StatusItem]) { + pub fn update(&mut self, list: &[StatusItem]) -> Result<()> { let new_hash = hash(list); if self.current_hash != new_hash { - self.tree.update(list); + self.tree.update(list)?; self.current_hash = new_hash; } + + Ok(()) } /// @@ -119,9 +123,8 @@ impl FileTreeComponent { Self::item_status_char(status_item.status); let file = Path::new(&status_item.path) .file_name() - .unwrap() - .to_str() - .unwrap(); + .and_then(std::ffi::OsStr::to_str) + .expect("invalid path."); let txt = if selected { format!( @@ -188,7 +191,11 @@ impl FileTreeComponent { } impl DrawableComponent for FileTreeComponent { - fn draw(&mut self, f: &mut Frame, r: Rect) { + fn draw( + &mut self, + f: &mut Frame, + r: Rect, + ) -> Result<()> { let selection_offset = self.tree.tree.items().iter().enumerate().fold( 0, @@ -230,6 +237,8 @@ impl DrawableComponent for FileTreeComponent { self.focused, self.theme, ); + + Ok(()) } } @@ -248,34 +257,34 @@ impl Component for FileTreeComponent { CommandBlocking::PassingOn } - fn event(&mut self, ev: Event) -> bool { + fn event(&mut self, ev: Event) -> Result { if self.focused { if let Event::Key(e) = ev { return match e { keys::MOVE_DOWN => { - self.move_selection(MoveSelection::Down) + Ok(self.move_selection(MoveSelection::Down)) } keys::MOVE_UP => { - self.move_selection(MoveSelection::Up) + Ok(self.move_selection(MoveSelection::Up)) } keys::HOME | keys::SHIFT_UP => { - self.move_selection(MoveSelection::Home) + Ok(self.move_selection(MoveSelection::Home)) } keys::END | keys::SHIFT_DOWN => { - self.move_selection(MoveSelection::End) + Ok(self.move_selection(MoveSelection::End)) } keys::MOVE_LEFT => { - self.move_selection(MoveSelection::Left) + Ok(self.move_selection(MoveSelection::Left)) } keys::MOVE_RIGHT => { - self.move_selection(MoveSelection::Right) + Ok(self.move_selection(MoveSelection::Right)) } - _ => false, + _ => Ok(false), }; } } - false + Ok(false) } fn focused(&self) -> bool { diff --git a/src/components/help.rs b/src/components/help.rs index 113aea38..20451360 100644 --- a/src/components/help.rs +++ b/src/components/help.rs @@ -16,6 +16,8 @@ use tui::{ Frame, }; +use anyhow::Result; + /// pub struct HelpComponent { cmds: Vec, @@ -25,7 +27,11 @@ pub struct HelpComponent { } impl DrawableComponent for HelpComponent { - fn draw(&mut self, f: &mut Frame, _rect: Rect) { + fn draw( + &mut self, + f: &mut Frame, + _rect: Rect, + ) -> Result<()> { if self.visible { const SIZE: (u16, u16) = (65, 24); let scroll_threshold = SIZE.1 / 3; @@ -75,6 +81,8 @@ impl DrawableComponent for HelpComponent { chunks[1], ); } + + Ok(()) } } @@ -113,7 +121,7 @@ impl Component for HelpComponent { visibility_blocking(self) } - fn event(&mut self, ev: Event) -> bool { + fn event(&mut self, ev: Event) -> Result { if self.visible { if let Event::Key(e) = ev { match e { @@ -124,12 +132,12 @@ impl Component for HelpComponent { } } - true + Ok(true) } else if let Event::Key(keys::OPEN_HELP) = ev { - self.show(); - true + self.show()?; + Ok(true) } else { - false + Ok(false) } } @@ -141,8 +149,10 @@ impl Component for HelpComponent { self.visible = false } - fn show(&mut self) { - self.visible = true + fn show(&mut self) -> Result<()> { + self.visible = true; + + Ok(()) } } diff --git a/src/components/mod.rs b/src/components/mod.rs index 254b21fa..c30072f3 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -9,9 +9,11 @@ mod reset; mod stashmsg; mod textinput; mod utils; +use anyhow::Result; pub use changes::ChangesComponent; pub use command::{CommandInfo, CommandText}; pub use commit::CommitComponent; +use crossterm::event::Event; pub use diff::DiffComponent; pub use filetree::FileTreeComponent; pub use help::HelpComponent; @@ -20,7 +22,6 @@ pub use reset::ResetComponent; pub use stashmsg::StashMsgComponent; pub use utils::filetree::FileTreeItemKind; -use crossterm::event::Event; use tui::{ backend::Backend, layout::Alignment, @@ -54,14 +55,14 @@ macro_rules! accessors { pub fn event_pump( ev: Event, components: &mut [&mut dyn Component], -) -> bool { +) -> Result { for c in components { - if c.event(ev) { - return true; + if c.event(ev)? { + return Ok(true); } } - false + Ok(false) } /// helper fn to simplify delegating command @@ -112,7 +113,11 @@ pub fn visibility_blocking( /// pub trait DrawableComponent { /// - fn draw(&mut self, f: &mut Frame, rect: Rect); + fn draw( + &mut self, + f: &mut Frame, + rect: Rect, + ) -> Result<()>; } /// base component trait @@ -125,7 +130,7 @@ pub trait Component { ) -> CommandBlocking; /// returns true if event propagation needs to end (event was consumed) - fn event(&mut self, ev: Event) -> bool; + fn event(&mut self, ev: Event) -> Result; /// fn focused(&self) -> bool { @@ -140,7 +145,9 @@ pub trait Component { /// fn hide(&mut self) {} /// - fn show(&mut self) {} + fn show(&mut self) -> Result<()> { + Ok(()) + } } fn dialog_paragraph<'a, 't, T>( diff --git a/src/components/msg.rs b/src/components/msg.rs index 459262b6..739fb646 100644 --- a/src/components/msg.rs +++ b/src/components/msg.rs @@ -20,10 +20,16 @@ pub struct MsgComponent { theme: Theme, } +use anyhow::Result; + impl DrawableComponent for MsgComponent { - fn draw(&mut self, f: &mut Frame, _rect: Rect) { + fn draw( + &mut self, + f: &mut Frame, + _rect: Rect, + ) -> Result<()> { if !self.visible { - return; + return Ok(()); } let txt = vec![Text::Raw(Cow::from(self.msg.as_str()))]; @@ -41,6 +47,8 @@ impl DrawableComponent for MsgComponent { .wrap(true), area, ); + + Ok(()) } } @@ -59,17 +67,16 @@ impl Component for MsgComponent { visibility_blocking(self) } - fn event(&mut self, ev: Event) -> bool { + fn event(&mut self, ev: Event) -> Result { if self.visible { if let Event::Key(e) = ev { if let keys::CLOSE_MSG = e { self.hide(); } } - - true + Ok(true) } else { - false + Ok(false) } } @@ -81,8 +88,10 @@ impl Component for MsgComponent { self.visible = false } - fn show(&mut self) { - self.visible = true + fn show(&mut self) -> Result<()> { + self.visible = true; + + Ok(()) } } @@ -95,8 +104,10 @@ impl MsgComponent { } } /// - pub fn show_msg(&mut self, msg: &str) { + pub fn show_msg(&mut self, msg: &str) -> Result<()> { self.msg = msg.to_string(); - self.show(); + self.show()?; + + Ok(()) } } diff --git a/src/components/reset.rs b/src/components/reset.rs index f4a5f6b0..61cbfeeb 100644 --- a/src/components/reset.rs +++ b/src/components/reset.rs @@ -8,6 +8,7 @@ use crate::{ strings, ui, ui::style::Theme, }; +use anyhow::Result; use crossterm::event::{Event, KeyCode}; use std::borrow::Cow; use strings::commands; @@ -27,7 +28,11 @@ pub struct ResetComponent { } impl DrawableComponent for ResetComponent { - fn draw(&mut self, f: &mut Frame, _rect: Rect) { + fn draw( + &mut self, + f: &mut Frame, + _rect: Rect, + ) -> Result<()> { if self.visible { let mut txt = Vec::new(); txt.push(Text::Styled( @@ -42,6 +47,8 @@ impl DrawableComponent for ResetComponent { area, ); } + + Ok(()) } } @@ -65,25 +72,26 @@ impl Component for ResetComponent { visibility_blocking(self) } - fn event(&mut self, ev: Event) -> bool { + fn event(&mut self, ev: Event) -> Result { if self.visible { if let Event::Key(e) = ev { return match e.code { KeyCode::Esc => { self.hide(); - true + Ok(true) } KeyCode::Enter => { self.confirm(); - true + Ok(true) } - _ => true, + _ => Ok(true), }; } } - false + + Ok(false) } fn is_visible(&self) -> bool { @@ -94,8 +102,10 @@ impl Component for ResetComponent { self.visible = false } - fn show(&mut self) { - self.visible = true + fn show(&mut self) -> Result<()> { + self.visible = true; + + Ok(()) } } @@ -110,9 +120,11 @@ impl ResetComponent { } } /// - pub fn open_for_path(&mut self, item: ResetItem) { + pub fn open_for_path(&mut self, item: ResetItem) -> Result<()> { self.target = Some(item); - self.show(); + self.show()?; + + Ok(()) } /// pub fn confirm(&mut self) { diff --git a/src/components/stashmsg.rs b/src/components/stashmsg.rs index eb9be7de..938fe5e8 100644 --- a/src/components/stashmsg.rs +++ b/src/components/stashmsg.rs @@ -8,6 +8,7 @@ use crate::{ tabs::StashingOptions, ui::style::Theme, }; +use anyhow::Result; use asyncgit::{sync, CWD}; use crossterm::event::{Event, KeyCode}; use strings::commands; @@ -20,8 +21,14 @@ pub struct StashMsgComponent { } impl DrawableComponent for StashMsgComponent { - fn draw(&mut self, f: &mut Frame, rect: Rect) { - self.input.draw(f, rect) + fn draw( + &mut self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + self.input.draw(f, rect)?; + + Ok(()) } } @@ -41,10 +48,10 @@ impl Component for StashMsgComponent { visibility_blocking(self) } - fn event(&mut self, ev: Event) -> bool { + fn event(&mut self, ev: Event) -> Result { if self.is_visible() { - if self.input.event(ev) { - return true; + if self.input.event(ev)? { + return Ok(true); } if let Event::Key(e) = ev { @@ -71,10 +78,10 @@ impl Component for StashMsgComponent { } // stop key event propagation - return true; + return Ok(true); } } - false + Ok(false) } fn is_visible(&self) -> bool { @@ -85,8 +92,10 @@ impl Component for StashMsgComponent { self.input.hide() } - fn show(&mut self) { - self.input.show() + fn show(&mut self) -> Result<()> { + self.input.show()?; + + Ok(()) } } diff --git a/src/components/textinput.rs b/src/components/textinput.rs index 1d418aba..b7d25ea2 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -5,6 +5,7 @@ use super::{ use crate::{ components::dialog_paragraph, strings, ui, ui::style::Theme, }; +use anyhow::Result; use crossterm::event::{Event, KeyCode}; use std::borrow::Cow; use strings::commands; @@ -53,7 +54,11 @@ impl TextInputComponent { } impl DrawableComponent for TextInputComponent { - fn draw(&mut self, f: &mut Frame, _rect: Rect) { + fn draw( + &mut self, + f: &mut Frame, + _rect: Rect, + ) -> Result<()> { if self.visible { let txt = if self.msg.is_empty() { [Text::Styled( @@ -74,6 +79,8 @@ impl DrawableComponent for TextInputComponent { area, ); } + + Ok(()) } } @@ -94,27 +101,27 @@ impl Component for TextInputComponent { visibility_blocking(self) } - fn event(&mut self, ev: Event) -> bool { + fn event(&mut self, ev: Event) -> Result { if self.visible { if let Event::Key(e) = ev { match e.code { KeyCode::Esc => { self.hide(); - return true; + return Ok(true); } KeyCode::Char(c) => { self.msg.push(c); - return true; + return Ok(true); } KeyCode::Backspace => { self.msg.pop(); - return true; + return Ok(true); } _ => (), }; } } - false + Ok(false) } fn is_visible(&self) -> bool { @@ -125,7 +132,9 @@ impl Component for TextInputComponent { self.visible = false } - fn show(&mut self) { - self.visible = true + fn show(&mut self) -> Result<()> { + self.visible = true; + + Ok(()) } } diff --git a/src/components/utils/filetree.rs b/src/components/utils/filetree.rs index 46923f2d..2d2c1566 100644 --- a/src/components/utils/filetree.rs +++ b/src/components/utils/filetree.rs @@ -6,6 +6,9 @@ use std::{ path::Path, }; +use anyhow::Result; +use std::ffi::OsStr; + /// holds the information shared among all `FileTreeItem` in a `FileTree` #[derive(Debug, Clone)] pub struct TreeItemInfo { @@ -49,19 +52,29 @@ pub struct FileTreeItem { } impl FileTreeItem { - fn new_file(item: &StatusItem) -> Self { + fn new_file(item: &StatusItem) -> Result { let item_path = Path::new(&item.path); let indent = u8::try_from( item_path.ancestors().count().saturating_sub(2), - ) - .unwrap(); - let path = String::from( - item_path.file_name().unwrap().to_str().unwrap(), - ); + )?; - Self { - info: TreeItemInfo::new(indent, path, item.path.clone()), - kind: FileTreeItemKind::File(item.clone()), + let name = item_path + .file_name() + .map(OsStr::to_string_lossy) + .map(|x| x.to_string()); + + match name { + Some(path) => Ok(Self { + info: TreeItemInfo::new( + indent, + path, + item.path.clone(), + ), + kind: FileTreeItemKind::File(item.clone()), + }), + None => { + Err(anyhow::anyhow!("invalid file name {:?}", item)) + } } } @@ -69,22 +82,27 @@ impl FileTreeItem { path: &Path, path_string: String, collapsed: bool, - ) -> Self { + ) -> Result { let indent = - u8::try_from(path.ancestors().count().saturating_sub(2)) - .unwrap(); - let path = String::from( - path.components() - .last() - .unwrap() - .as_os_str() - .to_str() - .unwrap(), - ); + u8::try_from(path.ancestors().count().saturating_sub(2))?; - Self { - info: TreeItemInfo::new(indent, path, path_string), - kind: FileTreeItemKind::Path(PathCollapsed(collapsed)), + match path + .components() + .last() + .map(std::path::Component::as_os_str) + .map(OsStr::to_string_lossy) + .map(String::from) + { + Some(path) => Ok(Self { + info: TreeItemInfo::new(indent, path, path_string), + kind: FileTreeItemKind::Path(PathCollapsed( + collapsed, + )), + }), + + None => Err(anyhow::anyhow!( + "failed to create item from path" + )), } } } @@ -121,7 +139,7 @@ impl FileTreeItems { pub(crate) fn new( list: &[StatusItem], collapsed: &BTreeSet<&String>, - ) -> Self { + ) -> Result { let mut nodes = Vec::with_capacity(list.len()); let mut paths_added = BTreeSet::new(); @@ -134,13 +152,13 @@ impl FileTreeItems { &mut nodes, &mut paths_added, &collapsed, - ); + )?; } - nodes.push(FileTreeItem::new_file(&e)); + nodes.push(FileTreeItem::new_file(&e)?); } - Self(nodes) + Ok(Self(nodes)) } /// @@ -178,7 +196,7 @@ impl FileTreeItems { nodes: &mut Vec, paths_added: &mut BTreeSet<&'a Path>, collapsed: &BTreeSet<&String>, - ) { + ) -> Result<()> { let mut ancestors = { item_path.ancestors().skip(1).collect::>() }; ancestors.reverse(); @@ -194,10 +212,12 @@ impl FileTreeItems { c, path_string, is_collapsed, - )); + )?); } } } + + Ok(()) } } @@ -235,7 +255,8 @@ mod tests { "file.txt", // ]); - let res = FileTreeItems::new(&items, &BTreeSet::new()); + let res = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); assert_eq!( res.0, @@ -255,7 +276,8 @@ mod tests { "file2.txt", // ]); - let res = FileTreeItems::new(&items, &BTreeSet::new()); + let res = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); assert_eq!(res.0.len(), 2); assert_eq!(res.0[1].info.path, items[1].path); @@ -268,6 +290,7 @@ mod tests { ]); let res = FileTreeItems::new(&items, &BTreeSet::new()) + .unwrap() .0 .iter() .map(|i| i.info.full_path.clone()) @@ -285,7 +308,8 @@ mod tests { "a/b/file.txt", // ]); - let list = FileTreeItems::new(&items, &BTreeSet::new()); + let list = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); let mut res = list .0 .iter() @@ -303,7 +327,8 @@ mod tests { "a.txt", // ]); - let list = FileTreeItems::new(&items, &BTreeSet::new()); + let list = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); let mut res = list .0 .iter() @@ -322,6 +347,7 @@ mod tests { ]); let res = FileTreeItems::new(&items, &BTreeSet::new()) + .unwrap() .0 .iter() .map(|i| i.info.full_path.clone()) @@ -350,7 +376,8 @@ mod tests { "a/b/d", // ]), &BTreeSet::new(), - ); + ) + .unwrap(); assert_eq!( res.find_parent_index(&String::from("a/b/c"), 3), diff --git a/src/components/utils/statustree.rs b/src/components/utils/statustree.rs index 83004646..f13b25fd 100644 --- a/src/components/utils/statustree.rs +++ b/src/components/utils/statustree.rs @@ -1,6 +1,7 @@ use super::filetree::{ FileTreeItem, FileTreeItemKind, FileTreeItems, PathCollapsed, }; +use anyhow::Result; use asyncgit::StatusItem; use std::{cmp, collections::BTreeSet}; @@ -35,14 +36,14 @@ impl SelectionChange { impl StatusTree { /// update tree with a new list, try to retain selection and collapse states - pub fn update(&mut self, list: &[StatusItem]) { + pub fn update(&mut self, list: &[StatusItem]) -> Result<()> { let last_collapsed = self.all_collapsed(); let last_selection = self.selected_item().map(|e| e.info.full_path); let last_selection_index = self.selection.unwrap_or(0); - self.tree = FileTreeItems::new(list, &last_collapsed); + self.tree = FileTreeItems::new(list, &last_collapsed)?; self.selection = if let Some(ref last_selection) = last_selection { self.find_last_selection( @@ -56,6 +57,8 @@ impl StatusTree { }; self.update_visibility(None, 0, true); + + Ok(()) } /// @@ -348,7 +351,7 @@ mod tests { ]); let mut res = StatusTree::default(); - res.update(&items); + res.update(&items).unwrap(); assert!(res.move_selection(MoveSelection::Down)); @@ -362,11 +365,11 @@ mod tests { #[test] fn test_keep_selected_item() { let mut res = StatusTree::default(); - res.update(&string_vec_to_status(&["b"])); + res.update(&string_vec_to_status(&["b"])).unwrap(); assert_eq!(res.selection, Some(0)); - res.update(&string_vec_to_status(&["a", "b"])); + res.update(&string_vec_to_status(&["a", "b"])).unwrap(); assert_eq!(res.selection, Some(1)); } @@ -374,10 +377,10 @@ mod tests { #[test] fn test_keep_selected_index() { let mut res = StatusTree::default(); - res.update(&string_vec_to_status(&["a", "b"])); + res.update(&string_vec_to_status(&["a", "b"])).unwrap(); res.selection = Some(1); - res.update(&string_vec_to_status(&["d", "c", "a"])); + res.update(&string_vec_to_status(&["d", "c", "a"])).unwrap(); assert_eq!(res.selection, Some(1)); } @@ -387,7 +390,8 @@ mod tests { res.update(&string_vec_to_status(&[ "a/b", // "c", - ])); + ])) + .unwrap(); res.collapse("a", 0); @@ -409,7 +413,8 @@ mod tests { "a/b", // "c", // "d", - ])); + ])) + .unwrap(); assert_eq!( res.all_collapsed().iter().collect::>(), @@ -440,7 +445,7 @@ mod tests { //3 d let mut res = StatusTree::default(); - res.update(&items); + res.update(&items).unwrap(); res.collapse(&String::from("a/b"), 1); @@ -485,7 +490,7 @@ mod tests { //4 d let mut res = StatusTree::default(); - res.update(&items); + res.update(&items).unwrap(); res.collapse(&String::from("b"), 1); res.collapse(&String::from("a"), 0); @@ -528,7 +533,7 @@ mod tests { //3 c let mut res = StatusTree::default(); - res.update(&items); + res.update(&items).unwrap(); res.collapse(&String::from("a"), 0); @@ -558,7 +563,7 @@ mod tests { //3 d let mut res = StatusTree::default(); - res.update(&items); + res.update(&items).unwrap(); res.collapse(&String::from("a/b"), 1); @@ -616,7 +621,7 @@ mod tests { //3 d let mut res = StatusTree::default(); - res.update(&items); + res.update(&items).unwrap(); res.collapse(&String::from("a/b"), 1); res.selection = Some(1); diff --git a/src/main.rs b/src/main.rs index fa38433c..348c207a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,9 @@ //https://github.com/crossterm-rs/crossterm/issues/432 #![allow(clippy::cargo::multiple_crate_versions)] #![deny(clippy::pedantic)] +#![deny(clippy::result_unwrap_used)] #![allow(clippy::module_name_repetitions)] +use anyhow::{anyhow, Result}; mod app; mod components; @@ -26,7 +28,7 @@ use crossterm::{ disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, }, - ExecutableCommand, Result, + ExecutableCommand, }; use log::error; use scopeguard::defer; @@ -50,7 +52,7 @@ static TICK_INTERVAL: Duration = Duration::from_secs(5); static SPINNER_INTERVAL: Duration = Duration::from_millis(50); fn main() -> Result<()> { - setup_logging(); + setup_logging()?; if invalid_path() { eprintln!("invalid git path\nplease run gitui inside of a git repository"); @@ -62,7 +64,7 @@ fn main() -> Result<()> { shutdown_terminal().expect("shutdown failed"); } - set_panic_handlers(); + set_panic_handlers()?; let mut terminal = start_terminal(io::stdout())?; @@ -74,7 +76,7 @@ fn main() -> Result<()> { let ticker = tick(TICK_INTERVAL); let spinner_ticker = tick(SPINNER_INTERVAL); - app.update(); + app.update()?; draw(&mut terminal, &mut app)?; let mut spinner = Spinner::default(); @@ -85,7 +87,7 @@ fn main() -> Result<()> { &rx_git, &ticker, &spinner_ticker, - ); + )?; { scope_time!("loop"); @@ -94,9 +96,9 @@ fn main() -> Result<()> { for e in events { match e { - QueueEvent::InputEvent(ev) => app.event(ev), - QueueEvent::Tick => app.update(), - QueueEvent::GitEvent(ev) => app.update_git(ev), + QueueEvent::InputEvent(ev) => app.event(ev)?, + QueueEvent::Tick => app.update()?, + QueueEvent::GitEvent(ev) => app.update_git(ev)?, QueueEvent::SpinnerUpdate => { needs_draw = false; spinner.update() @@ -135,7 +137,11 @@ fn draw( terminal: &mut Terminal, app: &mut App, ) -> io::Result<()> { - terminal.draw(|mut f| app.draw(&mut f)) + terminal.draw(|mut f| { + if let Err(e) = app.draw(&mut f) { + log::error!("failed to draw: {:?}", e) + } + }) } fn invalid_path() -> bool { @@ -147,7 +153,7 @@ fn select_event( rx_git: &Receiver, rx_ticker: &Receiver, rx_spinner: &Receiver, -) -> Vec { +) -> Result> { let mut events: Vec = Vec::new(); let mut sel = Select::new(); @@ -172,10 +178,9 @@ fn select_event( .recv(rx_spinner) .map(|_| events.push(QueueEvent::SpinnerUpdate)), _ => panic!("unknown select source"), - } - .unwrap(); + }?; - events + Ok(events) } fn start_terminal( @@ -189,28 +194,31 @@ fn start_terminal( Ok(terminal) } -#[must_use] -pub fn get_app_config_path() -> PathBuf { - let mut path = dirs::cache_dir().unwrap(); +fn get_app_config_path() -> Result { + let mut path = dirs::cache_dir() + .ok_or_else(|| anyhow!("failed to find os cache dir."))?; + path.push("gitui"); - fs::create_dir_all(&path).unwrap(); - path + fs::create_dir_all(&path)?; + Ok(path) } -fn setup_logging() { +fn setup_logging() -> Result<()> { if env::var("GITUI_LOGGING").is_ok() { - let mut path = get_app_config_path(); + let mut path = get_app_config_path()?; path.push("gitui.log"); let _ = WriteLogger::init( LevelFilter::Trace, Config::default(), - File::create(path).unwrap(), + File::create(path)?, ); } + + Ok(()) } -fn set_panic_handlers() { +fn set_panic_handlers() -> Result<()> { // regular panic handler panic::set_hook(Box::new(|e| { let backtrace = Backtrace::new(); @@ -220,12 +228,11 @@ fn set_panic_handlers() { })); // global threadpool - rayon_core::ThreadPoolBuilder::new() + Ok(rayon_core::ThreadPoolBuilder::new() .panic_handler(|e| { error!("thread panic: {:?}", e); panic!(e) }) .num_threads(4) - .build_global() - .unwrap(); + .build_global()?) } diff --git a/src/poll.rs b/src/poll.rs index dc13d0f3..9956c0ae 100644 --- a/src/poll.rs +++ b/src/poll.rs @@ -30,7 +30,9 @@ pub fn start_polling_thread() -> Receiver> { } else { MIN_POLL_DURATION }; - if let Some(e) = poll(timeout) { + if let Some(e) = + poll(timeout).expect("failed to pull events.") + { batch.push(QueueEvent::InputEvent(e)); } @@ -48,11 +50,10 @@ pub fn start_polling_thread() -> Receiver> { } /// -fn poll(dur: Duration) -> Option { - if event::poll(dur).unwrap() { - let event = event::read().unwrap(); - Some(event) +fn poll(dur: Duration) -> anyhow::Result> { + if event::poll(dur)? { + Ok(Some(event::read()?)) } else { - None + Ok(None) } } diff --git a/src/tabs/revlog/mod.rs b/src/tabs/revlog/mod.rs index 9272b99a..6aae3963 100644 --- a/src/tabs/revlog/mod.rs +++ b/src/tabs/revlog/mod.rs @@ -10,6 +10,7 @@ use crate::{ ui::calc_scroll_top, ui::style::Theme, }; +use anyhow::Result; use asyncgit::{sync, AsyncLog, AsyncNotification, FetchStatus, CWD}; use crossbeam_channel::Sender; use crossterm::event::Event; @@ -68,48 +69,51 @@ impl Revlog { } /// - pub fn update(&mut self) { + pub fn update(&mut self) -> Result<()> { if self.visible { let log_changed = - self.git_log.fetch().unwrap() == FetchStatus::Started; + self.git_log.fetch()? == FetchStatus::Started; - self.count_total = self.git_log.count().unwrap(); + self.count_total = self.git_log.count()?; if self .items .needs_data(self.selection, self.selection_max()) || log_changed { - self.fetch_commits(); + self.fetch_commits()?; } if self.tags.is_empty() { - self.tags = sync::get_tags(CWD).unwrap(); + self.tags = sync::get_tags(CWD)?; } } + + Ok(()) } - fn fetch_commits(&mut self) { + fn fetch_commits(&mut self) -> Result<()> { let want_min = self.selection.saturating_sub(SLICE_SIZE / 2); let commits = sync::get_commits_info( CWD, - &self.git_log.get_slice(want_min, SLICE_SIZE).unwrap(), + &self.git_log.get_slice(want_min, SLICE_SIZE)?, self.current_size.0.into(), ); if let Ok(commits) = commits { self.items.set_items(want_min, commits); } + + Ok(()) } - fn move_selection(&mut self, scroll: ScrollType) { + fn move_selection(&mut self, scroll: ScrollType) -> Result<()> { self.update_scroll_speed(); #[allow(clippy::cast_possible_truncation)] - let speed_int = usize::try_from(self.scroll_state.1 as i64) - .unwrap() - .max(1); + let speed_int = + usize::try_from(self.scroll_state.1 as i64)?.max(1); let page_offset = usize::from(self.current_size.1).saturating_sub(1); @@ -134,7 +138,9 @@ impl Revlog { self.selection = cmp::min(self.selection, self.selection_max()); - self.update(); + self.update()?; + + Ok(()) } fn update_scroll_speed(&mut self) { @@ -242,7 +248,11 @@ impl Revlog { } impl DrawableComponent for Revlog { - fn draw(&mut self, f: &mut Frame, area: Rect) { + fn draw( + &mut self, + f: &mut Frame, + area: Rect, + ) -> Result<()> { self.current_size = ( area.width.saturating_sub(2), area.height.saturating_sub(2), @@ -276,44 +286,46 @@ impl DrawableComponent for Revlog { .alignment(Alignment::Left), area, ); + + Ok(()) } } impl Component for Revlog { - fn event(&mut self, ev: Event) -> bool { + fn event(&mut self, ev: Event) -> Result { if self.visible { if let Event::Key(k) = ev { return match k { keys::MOVE_UP => { - self.move_selection(ScrollType::Up); - true + self.move_selection(ScrollType::Up)?; + Ok(true) } keys::MOVE_DOWN => { - self.move_selection(ScrollType::Down); - true + self.move_selection(ScrollType::Down)?; + Ok(true) } keys::SHIFT_UP | keys::HOME => { - self.move_selection(ScrollType::Home); - true + self.move_selection(ScrollType::Home)?; + Ok(true) } keys::SHIFT_DOWN | keys::END => { - self.move_selection(ScrollType::End); - true + self.move_selection(ScrollType::End)?; + Ok(true) } keys::PAGE_UP => { - self.move_selection(ScrollType::PageUp); - true + self.move_selection(ScrollType::PageUp)?; + Ok(true) } keys::PAGE_DOWN => { - self.move_selection(ScrollType::PageDown); - true + self.move_selection(ScrollType::PageDown)?; + Ok(true) } - _ => false, + _ => Ok(false), }; } } - false + Ok(false) } fn commands( @@ -343,9 +355,11 @@ impl Component for Revlog { self.git_log.set_background(); } - fn show(&mut self) { + fn show(&mut self) -> Result<()> { self.visible = true; self.items.clear(); - self.update(); + self.update()?; + + Ok(()) } } diff --git a/src/tabs/stashing.rs b/src/tabs/stashing.rs index 5f13fa84..1930194f 100644 --- a/src/tabs/stashing.rs +++ b/src/tabs/stashing.rs @@ -10,6 +10,7 @@ use crate::{ strings, ui::style::Theme, }; +use anyhow::Result; use asyncgit::{ sync::status::StatusType, AsyncNotification, AsyncStatus, StatusParams, @@ -66,15 +67,15 @@ impl Stashing { } /// - pub fn update(&mut self) { + pub fn update(&mut self) -> Result<()> { if self.visible { - self.git_status - .fetch(StatusParams::new( - StatusType::Both, - self.options.stash_untracked, - )) - .unwrap(); + self.git_status.fetch(StatusParams::new( + StatusType::Both, + self.options.stash_untracked, + ))?; } + + Ok(()) } /// @@ -83,13 +84,18 @@ impl Stashing { } /// - pub fn update_git(&mut self, ev: AsyncNotification) { + pub fn update_git( + &mut self, + ev: AsyncNotification, + ) -> Result<()> { if self.visible { if let AsyncNotification::Status = ev { - let status = self.git_status.last().unwrap(); - self.index.update(&status.items); + let status = self.git_status.last()?; + self.index.update(&status.items)?; } } + + Ok(()) } fn get_option_text(&self) -> Vec { @@ -127,7 +133,7 @@ impl DrawableComponent for Stashing { &mut self, f: &mut tui::Frame, rect: tui::layout::Rect, - ) { + ) -> Result<()> { let chunks = Layout::default() .direction(Direction::Horizontal) .constraints( @@ -153,7 +159,9 @@ impl DrawableComponent for Stashing { right_chunks[0], ); - self.index.draw(f, chunks[0]); + self.index.draw(f, chunks[0])?; + + Ok(()) } } @@ -184,10 +192,10 @@ impl Component for Stashing { visibility_blocking(self) } - fn event(&mut self, ev: crossterm::event::Event) -> bool { + fn event(&mut self, ev: crossterm::event::Event) -> Result { if self.visible { - if event_pump(ev, self.components_mut().as_mut_slice()) { - return true; + if event_pump(ev, self.components_mut().as_mut_slice())? { + return Ok(true); } if let Event::Key(k) = ev { @@ -199,26 +207,26 @@ impl Component for Stashing { ), ); - true + Ok(true) } keys::STASHING_TOGGLE_INDEX => { self.options.keep_index = !self.options.keep_index; - self.update(); - true + self.update()?; + Ok(true) } keys::STASHING_TOGGLE_UNTRACKED => { self.options.stash_untracked = !self.options.stash_untracked; - self.update(); - true + self.update()?; + Ok(true) } - _ => false, + _ => Ok(false), }; } } - false + Ok(false) } fn is_visible(&self) -> bool { @@ -229,8 +237,9 @@ impl Component for Stashing { self.visible = false; } - fn show(&mut self) { + fn show(&mut self) -> Result<()> { self.visible = true; - self.update(); + self.update()?; + Ok(()) } } diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 2f53e7cc..92c6c18d 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -10,6 +10,7 @@ use crate::{ strings, ui::style::Theme, }; +use anyhow::Result; use asyncgit::{ sync::status::StatusType, AsyncDiff, AsyncNotification, AsyncStatus, DiffParams, StatusParams, @@ -52,7 +53,7 @@ impl DrawableComponent for Status { &mut self, f: &mut tui::Frame, rect: tui::layout::Rect, - ) { + ) -> Result<()> { let chunks = Layout::default() .direction(Direction::Horizontal) .constraints( @@ -89,9 +90,11 @@ impl DrawableComponent for Status { ) .split(chunks[0]); - self.index_wd.draw(f, left_chunks[0]); - self.index.draw(f, left_chunks[1]); - self.diff.draw(f, chunks[1]); + self.index_wd.draw(f, left_chunks[0])?; + self.index.draw(f, left_chunks[1])?; + self.diff.draw(f, chunks[1])?; + + Ok(()) } } @@ -137,7 +140,7 @@ impl Status { } } - fn switch_focus(&mut self, f: Focus) -> bool { + fn switch_focus(&mut self, f: Focus) -> Result { if self.focus != f { self.focus = f; @@ -158,12 +161,12 @@ impl Status { } }; - self.update_diff(); + self.update_diff()?; - return true; + return Ok(true); } - false + Ok(false) } fn set_diff_target(&mut self, target: DiffTarget) { @@ -189,19 +192,18 @@ impl Status { } /// - pub fn update(&mut self) { + pub fn update(&mut self) -> Result<()> { if self.is_visible() { - self.git_diff.refresh().unwrap(); - self.git_status_workdir - .fetch(StatusParams::new( - StatusType::WorkingDir, - true, - )) - .unwrap(); + self.git_diff.refresh()?; + self.git_status_workdir.fetch(StatusParams::new( + StatusType::WorkingDir, + true, + ))?; self.git_status_stage - .fetch(StatusParams::new(StatusType::Stage, true)) - .unwrap(); + .fetch(StatusParams::new(StatusType::Stage, true))?; } + + Ok(()) } /// @@ -212,52 +214,59 @@ impl Status { } /// - pub fn update_git(&mut self, ev: AsyncNotification) { + pub fn update_git( + &mut self, + ev: AsyncNotification, + ) -> Result<()> { match ev { - AsyncNotification::Diff => self.update_diff(), - AsyncNotification::Status => self.update_status(), + AsyncNotification::Diff => self.update_diff()?, + AsyncNotification::Status => self.update_status()?, _ => (), } + + Ok(()) } - fn update_status(&mut self) { - let status = self.git_status_stage.last().unwrap(); - self.index.update(&status.items); + fn update_status(&mut self) -> Result<()> { + let status = self.git_status_stage.last()?; + self.index.update(&status.items)?; - let status = self.git_status_workdir.last().unwrap(); - self.index_wd.update(&status.items); + let status = self.git_status_workdir.last()?; + self.index_wd.update(&status.items)?; - self.update_diff(); + self.update_diff()?; + + Ok(()) } /// - pub fn update_diff(&mut self) { + pub fn update_diff(&mut self) -> Result<()> { if let Some((path, is_stage)) = self.selected_path() { let diff_params = DiffParams(path.clone(), is_stage); if self.diff.current() == (path.clone(), is_stage) { // we are already showing a diff of the right file // maybe the diff changed (outside file change) - if let Some((params, last)) = - self.git_diff.last().unwrap() - { + if let Some((params, last)) = self.git_diff.last()? { if params == diff_params { - self.diff.update(path, is_stage, last); + self.diff.update(path, is_stage, last)?; } } } else { // we dont show the right diff right now, so we need to request if let Some(diff) = - self.git_diff.request(diff_params).unwrap() + self.git_diff.request(diff_params)? { - self.diff.update(path, is_stage, diff); + self.diff.update(path, is_stage, diff)?; } else { - self.diff.clear(); + self.diff.clear()?; } } } else { - self.diff.clear(); + self.diff.clear()?; } + + Ok(()) } } @@ -322,10 +331,10 @@ impl Component for Status { visibility_blocking(self) } - fn event(&mut self, ev: crossterm::event::Event) -> bool { + fn event(&mut self, ev: crossterm::event::Event) -> Result { if self.visible { - if event_pump(ev, self.components_mut().as_mut_slice()) { - return true; + if event_pump(ev, self.components_mut().as_mut_slice())? { + return Ok(true); } if let Event::Key(k) = ev { @@ -345,12 +354,12 @@ impl Component for Status { DiffTarget::WorkingDir => Focus::WorkDir, }) } - _ => false, + _ => Ok(false), }; } } - false + Ok(false) } fn is_visible(&self) -> bool { @@ -361,7 +370,9 @@ impl Component for Status { self.visible = false; } - fn show(&mut self) { + fn show(&mut self) -> Result<()> { self.visible = true; + + Ok(()) } } diff --git a/src/ui/style.rs b/src/ui/style.rs index 9a2bbedb..c52c9744 100644 --- a/src/ui/style.rs +++ b/src/ui/style.rs @@ -1,4 +1,5 @@ use crate::get_app_config_path; +use anyhow::Result; use asyncgit::{DiffLineType, StatusItemType}; use ron::{ de::from_bytes, @@ -180,44 +181,41 @@ impl Theme { ) } - fn save(&self) -> Result<(), std::io::Error> { - let theme_file = Self::get_theme_file(); + fn save(&self) -> Result<()> { + let theme_file = Self::get_theme_file()?; let mut file = File::create(theme_file)?; - let data = to_string_pretty(self, PrettyConfig::default()) - .map_err(|_| std::io::Error::from_raw_os_error(100))?; + let data = to_string_pretty(self, PrettyConfig::default())?; file.write_all(data.as_bytes())?; Ok(()) } - fn get_theme_file() -> PathBuf { - let app_home = get_app_config_path(); - app_home.join("theme.ron") + fn get_theme_file() -> Result { + let app_home = get_app_config_path()?; + Ok(app_home.join("theme.ron")) } - fn read_file( - theme_file: PathBuf, - ) -> Result { - if theme_file.exists() { - let mut f = File::open(theme_file)?; - let mut buffer = Vec::new(); - f.read_to_end(&mut buffer)?; + fn read_file(theme_file: PathBuf) -> Result { + let mut f = File::open(theme_file)?; + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer)?; + Ok(from_bytes(&buffer)?) + } - Ok(from_bytes(&buffer).map_err(|_| { - std::io::Error::from_raw_os_error(100) - })?) + fn init_internal() -> Result { + let file = Theme::get_theme_file()?; + if file.exists() { + Ok(Theme::read_file(file)?) } else { - Err(std::io::Error::from_raw_os_error(100)) + let def = Theme::default(); + if def.save().is_err() { + log::warn!("failed to store default theme to disk.") + } + Ok(def) } } pub fn init() -> Theme { - if let Ok(x) = Theme::read_file(Theme::get_theme_file()) { - x - } else { - let res = Self::default(); - res.save().unwrap_or_default(); - res - } + Theme::init_internal().unwrap_or_default() } }