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