pub mod app;
mod bindings;
mod calling_process;
mod capped_input;
pub mod cli;
mod cmd_log;
pub mod config;
mod config_edit;
mod diff_renderer;
pub mod error;
mod file_watcher;
mod git;
mod gitu_diff;
mod item_data;
mod items;
mod key_parser;
mod menu;
mod ops;
mod picker;
mod prompt;
mod rebase_todo;
mod screen;
mod search;
mod stream;
mod style;
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::{
env,
io::{self, IsTerminal},
path::Path,
rc::Rc,
sync::Arc,
time::Duration,
};
use term::Term;
use crate::calling_process::GitCommand;
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:?}");
log::debug!("Opening repo");
let repo = open_repo_from_env()?;
let piped = args
.pager
.then(|| read_piped_input(git_argv, repo.workdir(), 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>>,
workdir: Option<&Path>,
limit: u64,
) -> Res<Paged> {
if io::stdin().is_terminal() {
return Err(Error::PagerWithoutInput);
}
paged(io::stdin(), git_argv, workdir, limit)
}
fn paged(
input: impl io::Read,
git_argv: Option<Vec<String>>,
workdir: Option<&Path>,
limit: u64,
) -> Res<Paged> {
if git_argv.clone().and_then(GitCommand::of).is_some() {
return Ok(Paged {
text: String::new(),
truncated: false,
git_argv,
prefix: command_prefix(workdir),
});
}
let input = Capped::read(input, limit).map_err(Error::ReadPipedInput)?;
Ok(Paged {
text: input.text,
truncated: input.truncated,
git_argv,
prefix: String::new(),
})
}
fn command_prefix(workdir: Option<&Path>) -> String {
if let Ok(prefix) = env::var("GIT_PREFIX") {
return prefix;
}
let relative = || {
let dir = env::current_dir().ok()?.canonicalize().ok()?;
let root = workdir?.canonicalize().ok()?;
Some(dir.strip_prefix(root).ok()?.to_str()?.to_owned())
};
match relative() {
Some(dir) if !dir.is_empty() => format!("{dir}/"),
_ => String::new(),
}
}
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 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<_>>()
}