gitu/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
mod bindings;
pub mod cli;
mod cmd_log;
pub mod config;
mod git;
mod git2_opts;
mod items;
mod key_parser;
mod menu;
mod ops;
mod prompt;
mod screen;
pub mod state;
mod syntax_highlight;
pub mod term;
#[cfg(test)]
mod tests;
mod ui;
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventState, KeyModifiers};
use git2::Repository;
use items::Item;
use ops::Action;
use std::{error::Error, path::PathBuf, process::Command, rc::Rc, time::Duration};
use term::Term;
// An overview of Gitu's ui and terminology:
//
// Screen (src/screen/*)
// │
// ▼
// ┌──────────────────────────────────────────────────────────────────┐
// Item───┬─► On branch master │
// Item └─► Your branch is up to date with 'origin/master'. │
// ... │ │
// │ Untracked files │
// │ src/tests/rebase.rs │
// │ │
// │ Unstaged changes (4) │
// │▌modified src/keybinds.rs… │
// │ modified src/ops/mod.rs… │
// │ modified src/ops/rebase.rs… │
// │ modified src/tests/mod.rs… │
// │ │
// │ Stashes │
// │ stash@0 On master: scroll │
// │ stash@1 WIP on fix/run-cmd-error-on-bad-exit: 098d14a feat: prom…│
// │ │
// │ Recent commits │
// Ops (src/ops/*) ├──────────────────────────────────────────────────────────────────┤
// │ │Help Submenu modified src/keybinds.r│
// └─────┬───►g Refresh h Help ret Show │
// └───►tab Toggle section b Branch K Discard │
// │k p ↑ Move up c Commit s Stage │
// │j n ↓ Move down f Fetch u Unstage │
// Submenu ───────►│C-k C-p C-↑ Move up line l Log │
// │C-j C-n C-↓ Move down line F Pull │
// │C-u Half page up P Push │
// │C-d Half page down r Rebase │
// │y Show refs X Reset │
// │ z Stash │
// └──────────────────────────────────────────────────────────────────┘
pub type Res<T> = Result<T, Box<dyn Error>>;
pub fn run(args: &cli::Args, term: &mut Term) -> Res<()> {
log::debug!("Finding git dir");
let dir = PathBuf::from(
String::from_utf8(
Command::new("git")
.args(["rev-parse", "--show-toplevel"])
.output()?
.stdout,
)?
.trim_end(),
);
log::debug!("Opening repo");
let repo = open_repo_from_env()?;
repo.set_workdir(&dir, false)?;
log::debug!("Initializing config");
let config = config::init_config()?;
log::debug!("Creating initial state");
let mut state = state::State::create(Rc::new(repo), term.size()?, args, Rc::new(config), true)?;
log::debug!("Initial update");
state.update(term, &[Event::FocusGained])?;
if args.print {
return Ok(());
}
if let Some(keys_string) = &args.keys {
let ("", keys) = key_parser::parse_keys(keys_string).expect("Couldn't parse keys") else {
panic!("Couldn't parse keys");
};
handle_initial_send_keys(&keys, &mut state, term)?;
}
while !state.quit {
let events = if event::poll(Duration::from_millis(100))? {
vec![event::read()?]
} else {
vec![]
};
state.update(term, &events)?;
}
Ok(())
}
fn open_repo_from_env() -> Res<Repository> {
match Repository::open_from_env() {
Ok(repo) => Ok(repo),
Err(err) if err.code() == git2::ErrorCode::NotFound => {
Err("No .git found in the current directory".into())
}
Err(err) => Err(Box::new(err)),
}
}
fn handle_initial_send_keys(
keys: &[(KeyModifiers, KeyCode)],
state: &mut state::State,
term: &mut ratatui::prelude::Terminal<term::TermBackend>,
) -> Result<(), Box<dyn Error>> {
let initial_events = keys
.iter()
.map(|(mods, key)| {
Event::Key(KeyEvent {
code: *key,
modifiers: *mods,
kind: event::KeyEventKind::Press,
state: KeyEventState::NONE,
})
})
.collect::<Vec<_>>();
if !initial_events.is_empty() {
state.update(term, &initial_events)?;
}
Ok(())
}