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}