mirror of
https://github.com/extrawurst/gitui.git
synced 2024-11-25 18:44:53 +03:00
anyhow integration (closes #77)
This commit is contained in:
parent
fc884525be
commit
4c17660956
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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",
|
||||
|
@ -35,6 +35,7 @@ chrono = "0.4"
|
||||
backtrace = "0.3"
|
||||
ron = "0.6"
|
||||
serde = "1.0"
|
||||
anyhow = "1.0.31"
|
||||
|
||||
[features]
|
||||
default=[]
|
||||
|
@ -3,6 +3,7 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![forbid(missing_docs)]
|
||||
#![deny(clippy::all)]
|
||||
#![deny(clippy::result_unwrap_used)]
|
||||
|
||||
mod diff;
|
||||
mod error;
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![forbid(missing_docs)]
|
||||
#![deny(clippy::result_unwrap_used)]
|
||||
|
||||
use log::trace;
|
||||
use std::time::Instant;
|
||||
|
101
src/app.rs
101
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<B: Backend>(&mut self, f: &mut Frame<B>) {
|
||||
pub fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
) -> 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<NeedsUpdate> {
|
||||
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<NeedsUpdate> {
|
||||
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<CommandInfo> {
|
||||
@ -354,14 +368,19 @@ impl App {
|
||||
|| self.msg.is_visible()
|
||||
}
|
||||
|
||||
fn draw_popups<B: Backend>(&mut self, f: &mut Frame<B>) {
|
||||
fn draw_popups<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
) -> 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<B: Backend>(&self, f: &mut Frame<B>, r: Rect) {
|
||||
|
@ -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<bool> {
|
||||
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<B: Backend>(&mut self, f: &mut Frame<B>, r: Rect) {
|
||||
self.files.draw(f, r)
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
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<bool> {
|
||||
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 {
|
||||
|
@ -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<B: Backend>(&mut self, f: &mut Frame<B>, rect: Rect) {
|
||||
self.input.draw(f, rect)
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
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<bool> {
|
||||
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 {
|
||||
|
@ -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<u16> {
|
||||
) -> Result<Option<u16>> {
|
||||
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<Text> {
|
||||
fn get_text(&self, width: u16, height: u16) -> Result<Vec<Text>> {
|
||||
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<B: Backend>(&mut self, f: &mut Frame<B>, r: Rect) {
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
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<bool> {
|
||||
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 {
|
||||
|
@ -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<B: Backend>(&mut self, f: &mut Frame<B>, r: Rect) {
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
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<bool> {
|
||||
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 {
|
||||
|
@ -16,6 +16,8 @@ use tui::{
|
||||
Frame,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
///
|
||||
pub struct HelpComponent {
|
||||
cmds: Vec<CommandInfo>,
|
||||
@ -25,7 +27,11 @@ pub struct HelpComponent {
|
||||
}
|
||||
|
||||
impl DrawableComponent for HelpComponent {
|
||||
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, _rect: Rect) {
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
_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<bool> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<bool> {
|
||||
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<T: Component>(
|
||||
///
|
||||
pub trait DrawableComponent {
|
||||
///
|
||||
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, rect: Rect);
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
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<bool>;
|
||||
|
||||
///
|
||||
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>(
|
||||
|
@ -20,10 +20,16 @@ pub struct MsgComponent {
|
||||
theme: Theme,
|
||||
}
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
impl DrawableComponent for MsgComponent {
|
||||
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, _rect: Rect) {
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
_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<bool> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@ -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<B: Backend>(&mut self, f: &mut Frame<B>, _rect: Rect) {
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
_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<bool> {
|
||||
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) {
|
||||
|
@ -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<B: Backend>(&mut self, f: &mut Frame<B>, rect: Rect) {
|
||||
self.input.draw(f, rect)
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
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<bool> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<B: Backend>(&mut self, f: &mut Frame<B>, _rect: Rect) {
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
_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<bool> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@ -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<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<FileTreeItem>,
|
||||
paths_added: &mut BTreeSet<&'a Path>,
|
||||
collapsed: &BTreeSet<&String>,
|
||||
) {
|
||||
) -> Result<()> {
|
||||
let mut ancestors =
|
||||
{ item_path.ancestors().skip(1).collect::<Vec<_>>() };
|
||||
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),
|
||||
|
@ -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::<Vec<_>>(),
|
||||
@ -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);
|
||||
|
||||
|
57
src/main.rs
57
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<B: Backend>(
|
||||
terminal: &mut Terminal<B>,
|
||||
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<AsyncNotification>,
|
||||
rx_ticker: &Receiver<Instant>,
|
||||
rx_spinner: &Receiver<Instant>,
|
||||
) -> Vec<QueueEvent> {
|
||||
) -> Result<Vec<QueueEvent>> {
|
||||
let mut events: Vec<QueueEvent> = 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<W: Write>(
|
||||
@ -189,28 +194,31 @@ fn start_terminal<W: Write>(
|
||||
Ok(terminal)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_app_config_path() -> PathBuf {
|
||||
let mut path = dirs::cache_dir().unwrap();
|
||||
fn get_app_config_path() -> Result<PathBuf> {
|
||||
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()?)
|
||||
}
|
||||
|
13
src/poll.rs
13
src/poll.rs
@ -30,7 +30,9 @@ pub fn start_polling_thread() -> Receiver<Vec<QueueEvent>> {
|
||||
} 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<Vec<QueueEvent>> {
|
||||
}
|
||||
|
||||
///
|
||||
fn poll(dur: Duration) -> Option<Event> {
|
||||
if event::poll(dur).unwrap() {
|
||||
let event = event::read().unwrap();
|
||||
Some(event)
|
||||
fn poll(dur: Duration) -> anyhow::Result<Option<Event>> {
|
||||
if event::poll(dur)? {
|
||||
Ok(Some(event::read()?))
|
||||
} else {
|
||||
None
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
@ -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<B: Backend>(&mut self, f: &mut Frame<B>, area: Rect) {
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
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<bool> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@ -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<Text> {
|
||||
@ -127,7 +133,7 @@ impl DrawableComponent for Stashing {
|
||||
&mut self,
|
||||
f: &mut tui::Frame<B>,
|
||||
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<bool> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@ -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<B>,
|
||||
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<bool> {
|
||||
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<bool> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@ -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<PathBuf> {
|
||||
let app_home = get_app_config_path()?;
|
||||
Ok(app_home.join("theme.ron"))
|
||||
}
|
||||
|
||||
fn read_file(
|
||||
theme_file: PathBuf,
|
||||
) -> Result<Theme, std::io::Error> {
|
||||
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<Theme> {
|
||||
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<Theme> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user