pub mod app;
mod bindings;
mod calling_process;
mod capped_input;
pub mod cli;
mod cmd_log;
pub mod config;
mod diff_renderer;
pub mod error;
mod file_watcher;
mod git;
pub mod gitu_diff;
mod highlight;
mod item_data;
mod items;
mod key_parser;
mod menu;
mod ops;
pub mod picker;
mod prompt;
mod rebase_todo;
mod screen;
mod search;
pub mod style;
mod syntax_parser;
pub mod term;
#[cfg(test)]
mod tests;
mod text_input;
mod ui;
use bindings::Bindings;
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventState, KeyModifiers};
use error::Error;
use git2::Repository;
use items::Item;
use ops::Action;
use std::{
io::{self, IsTerminal},
path::{Path, PathBuf},
process::Command,
rc::Rc,
sync::Arc,
time::Duration,
};
use term::Term;
use crate::capped_input::Capped;
use crate::config::Config;
use crate::screen::pager::Paged;
pub const LOG_FILE_NAME: &str = "gitu.log";
pub type Res<T> = Result<T, Error>;
pub fn run(config: Arc<Config>, args: &cli::Args, term: &mut Term) -> Res<i32> {
let git_argv = args.pager.then(calling_process::paging_for).flatten();
log::debug!("Paging the output of {git_argv:?}");
let dir = find_git_dir()?;
let repo = open_repo(&dir)?;
let piped = args
.pager
.then(|| read_piped_input(git_argv, config.general.max_input_bytes))
.transpose()?;
let mut app = app::App::create(
Rc::new(repo),
term.size().map_err(Error::Term)?,
args,
Arc::clone(&config),
true,
piped,
)?;
if args.quit_if_one_screen && app.print_if_one_screen(term)? {
return Ok(app.state.exit_code);
}
if args.print {
return run_app(&mut app, args, term);
}
term.setup_term(&config).map_err(Error::Term)?;
let result = run_app(&mut app, args, term);
term.reset_term(&config).map_err(Error::Term)?;
result
}
fn run_app(app: &mut app::App, args: &cli::Args, term: &mut Term) -> Res<i32> {
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");
};
for event in keys_to_events(&keys) {
app.handle_event(term, event)?;
}
}
app.redraw_now(term)?;
if args.print {
return Ok(app.state.exit_code);
}
app.run(term, Duration::from_millis(100))?;
Ok(app.state.exit_code)
}
fn read_piped_input(git_argv: Option<Vec<String>>, limit: u64) -> Res<Paged> {
if io::stdin().is_terminal() {
return Err(Error::PagerWithoutInput);
}
let input = Capped::read(io::stdin(), limit).map_err(Error::ReadPipedInput)?;
Ok(Paged {
text: input.text,
truncated: input.truncated,
git_argv,
})
}
fn open_repo(dir: &Path) -> Res<Repository> {
log::debug!("Opening repo");
let repo = open_repo_from_env()?;
repo.set_workdir(dir, false).map_err(Error::OpenRepo)?;
Ok(repo)
}
fn find_git_dir() -> Res<PathBuf> {
log::debug!("Finding git dir");
let dir = PathBuf::from(
String::from_utf8(
Command::new("git")
.args(["rev-parse", "--show-toplevel"])
.output()
.map_err(Error::FindGitDir)?
.stdout,
)
.map_err(Error::GitDirUtf8)?
.trim_end(),
);
Ok(dir)
}
fn open_repo_from_env() -> Res<Repository> {
match Repository::open_from_env() {
Ok(repo) => Ok(repo),
Err(err) => Err(Error::OpenRepo(err)),
}
}
fn keys_to_events(keys: &[(KeyModifiers, KeyCode)]) -> Vec<Event> {
keys.iter()
.map(|(mods, key)| {
Event::Key(KeyEvent {
code: *key,
modifiers: *mods,
kind: event::KeyEventKind::Press,
state: KeyEventState::NONE,
})
})
.collect::<Vec<_>>()
}