mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28:21 +00:00
Add snapshot test using insta (#2411)
This commit is contained in:
parent
b6ce67dad3
commit
e53692e781
9 changed files with 400 additions and 116 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
|
@ -465,6 +465,18 @@ dependencies = [
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.15.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
|
|
@ -802,6 +814,12 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.35"
|
version = "0.8.35"
|
||||||
|
|
@ -1192,7 +1210,9 @@ dependencies = [
|
||||||
"filetreelist",
|
"filetreelist",
|
||||||
"fuzzy-matcher",
|
"fuzzy-matcher",
|
||||||
"gh-emoji",
|
"gh-emoji",
|
||||||
|
"git2-testing",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
"insta",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"log",
|
"log",
|
||||||
"notify",
|
"notify",
|
||||||
|
|
@ -2266,6 +2286,18 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "insta"
|
||||||
|
version = "1.44.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5c943d4415edd8153251b6f197de5eb1640e56d84e8d9159bea190421c73698"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"once_cell",
|
||||||
|
"regex",
|
||||||
|
"similar",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instability"
|
name = "instability"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
|
@ -3463,6 +3495,12 @@ dependencies = [
|
||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "similar"
|
||||||
|
version = "2.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simplelog"
|
name = "simplelog"
|
||||||
version = "0.12.2"
|
version = "0.12.2"
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,8 @@ chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
git2-testing = { path = "./git2-testing" }
|
||||||
|
insta = { version = "1.41.0", features = ["filters"] }
|
||||||
pretty_assertions = "1.4"
|
pretty_assertions = "1.4"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,18 @@ pub fn repo_init_empty() -> (TempDir, Repository) {
|
||||||
(td, repo)
|
(td, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// initialize test repo in temp path with an empty first commit
|
/// initialize test repo in temp path with given suffix and an empty first commit
|
||||||
pub fn repo_init() -> (TempDir, Repository) {
|
pub fn repo_init_suffix<T: AsRef<std::ffi::OsStr>>(
|
||||||
|
suffix: Option<T>,
|
||||||
|
) -> (TempDir, Repository) {
|
||||||
init_log();
|
init_log();
|
||||||
|
|
||||||
sandbox_config_files();
|
sandbox_config_files();
|
||||||
|
|
||||||
let td = TempDir::new().unwrap();
|
let td = match suffix {
|
||||||
|
Some(suffix) => TempDir::with_suffix(suffix).unwrap(),
|
||||||
|
None => TempDir::new().unwrap(),
|
||||||
|
};
|
||||||
let repo = Repository::init(td.path()).unwrap();
|
let repo = Repository::init(td.path()).unwrap();
|
||||||
{
|
{
|
||||||
let mut config = repo.config().unwrap();
|
let mut config = repo.config().unwrap();
|
||||||
|
|
@ -45,6 +50,11 @@ pub fn repo_init() -> (TempDir, Repository) {
|
||||||
(td, repo)
|
(td, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// initialize test repo in temp path with an empty first commit
|
||||||
|
pub fn repo_init() -> (TempDir, Repository) {
|
||||||
|
repo_init_suffix::<&std::ffi::OsStr>(None)
|
||||||
|
}
|
||||||
|
|
||||||
// init log
|
// init log
|
||||||
fn init_log() {
|
fn init_log() {
|
||||||
let _ = env_logger::builder()
|
let _ = env_logger::builder()
|
||||||
|
|
|
||||||
280
src/gitui.rs
Normal file
280
src/gitui.rs
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use asyncgit::{sync::utils::repo_work_dir, AsyncGitNotification};
|
||||||
|
use crossbeam_channel::{never, tick, unbounded, Receiver};
|
||||||
|
use scopetime::scope_time;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::{App, QuitState},
|
||||||
|
args::CliArgs,
|
||||||
|
draw,
|
||||||
|
input::{Input, InputEvent, InputState},
|
||||||
|
keys::KeyConfig,
|
||||||
|
select_event,
|
||||||
|
spinner::Spinner,
|
||||||
|
ui::style::Theme,
|
||||||
|
watcher::RepoWatcher,
|
||||||
|
AsyncAppNotification, AsyncNotification, QueueEvent, Updater,
|
||||||
|
SPINNER_INTERVAL, TICK_INTERVAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Gitui {
|
||||||
|
app: crate::app::App,
|
||||||
|
rx_input: Receiver<InputEvent>,
|
||||||
|
rx_git: Receiver<AsyncGitNotification>,
|
||||||
|
rx_app: Receiver<AsyncAppNotification>,
|
||||||
|
rx_ticker: Receiver<Instant>,
|
||||||
|
rx_watcher: Receiver<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gitui {
|
||||||
|
pub(crate) fn new(
|
||||||
|
cliargs: CliArgs,
|
||||||
|
theme: Theme,
|
||||||
|
key_config: &KeyConfig,
|
||||||
|
updater: Updater,
|
||||||
|
) -> Result<Self, anyhow::Error> {
|
||||||
|
let (tx_git, rx_git) = unbounded();
|
||||||
|
let (tx_app, rx_app) = unbounded();
|
||||||
|
|
||||||
|
let input = Input::new();
|
||||||
|
|
||||||
|
let (rx_ticker, rx_watcher) = match updater {
|
||||||
|
Updater::NotifyWatcher => {
|
||||||
|
let repo_watcher = RepoWatcher::new(
|
||||||
|
repo_work_dir(&cliargs.repo_path)?.as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
|
(never(), repo_watcher.receiver())
|
||||||
|
}
|
||||||
|
Updater::Ticker => (tick(TICK_INTERVAL), never()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let app = App::new(
|
||||||
|
cliargs,
|
||||||
|
tx_git,
|
||||||
|
tx_app,
|
||||||
|
input.clone(),
|
||||||
|
theme,
|
||||||
|
key_config.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
app,
|
||||||
|
rx_input: input.receiver(),
|
||||||
|
rx_git,
|
||||||
|
rx_app,
|
||||||
|
rx_ticker,
|
||||||
|
rx_watcher,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run_main_loop<B: ratatui::backend::Backend>(
|
||||||
|
&mut self,
|
||||||
|
terminal: &mut ratatui::Terminal<B>,
|
||||||
|
) -> Result<QuitState, anyhow::Error> {
|
||||||
|
let spinner_ticker = tick(SPINNER_INTERVAL);
|
||||||
|
let mut spinner = Spinner::default();
|
||||||
|
|
||||||
|
self.app.update()?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let event = select_event(
|
||||||
|
&self.rx_input,
|
||||||
|
&self.rx_git,
|
||||||
|
&self.rx_app,
|
||||||
|
&self.rx_ticker,
|
||||||
|
&self.rx_watcher,
|
||||||
|
&spinner_ticker,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
{
|
||||||
|
if matches!(event, QueueEvent::SpinnerUpdate) {
|
||||||
|
spinner.update();
|
||||||
|
spinner.draw(terminal)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope_time!("loop");
|
||||||
|
|
||||||
|
match event {
|
||||||
|
QueueEvent::InputEvent(ev) => {
|
||||||
|
if matches!(
|
||||||
|
ev,
|
||||||
|
InputEvent::State(InputState::Polling)
|
||||||
|
) {
|
||||||
|
//Note: external ed closed, we need to re-hide cursor
|
||||||
|
terminal.hide_cursor()?;
|
||||||
|
}
|
||||||
|
self.app.event(ev)?;
|
||||||
|
}
|
||||||
|
QueueEvent::Tick | QueueEvent::Notify => {
|
||||||
|
self.app.update()?;
|
||||||
|
}
|
||||||
|
QueueEvent::AsyncEvent(ev) => {
|
||||||
|
if !matches!(
|
||||||
|
ev,
|
||||||
|
AsyncNotification::Git(
|
||||||
|
AsyncGitNotification::FinishUnchanged
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
self.app.update_async(ev)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QueueEvent::SpinnerUpdate => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.draw(terminal)?;
|
||||||
|
|
||||||
|
spinner.set_state(self.app.any_work_pending());
|
||||||
|
spinner.draw(terminal)?;
|
||||||
|
|
||||||
|
if self.app.is_quit() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.app.quit_state())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw<B: ratatui::backend::Backend>(
|
||||||
|
&self,
|
||||||
|
terminal: &mut ratatui::Terminal<B>,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
draw(terminal, &self.app)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn update_async(&mut self, event: crate::AsyncNotification) {
|
||||||
|
self.app.update_async(event).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn input_event(
|
||||||
|
&mut self,
|
||||||
|
code: KeyCode,
|
||||||
|
modifiers: KeyModifiers,
|
||||||
|
) {
|
||||||
|
let event = crossterm::event::KeyEvent::new(code, modifiers);
|
||||||
|
self.app
|
||||||
|
.event(crate::input::InputEvent::Input(
|
||||||
|
crossterm::event::Event::Key(event),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn wait_for_async_git_notification(
|
||||||
|
&self,
|
||||||
|
expected: AsyncGitNotification,
|
||||||
|
) {
|
||||||
|
loop {
|
||||||
|
let actual = self
|
||||||
|
.rx_git
|
||||||
|
.recv_timeout(std::time::Duration::from_millis(100))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if actual == expected {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn update(&mut self) {
|
||||||
|
self.app.update().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use asyncgit::{sync::RepoPath, AsyncGitNotification};
|
||||||
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
|
use git2_testing::repo_init_suffix;
|
||||||
|
use insta::assert_snapshot;
|
||||||
|
use ratatui::{backend::TestBackend, Terminal};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
args::CliArgs, gitui::Gitui, keys::KeyConfig,
|
||||||
|
ui::style::Theme, AsyncNotification, Updater,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Macro adapted from: https://insta.rs/docs/cmd/
|
||||||
|
macro_rules! apply_common_filters {
|
||||||
|
{} => {
|
||||||
|
let mut settings = insta::Settings::clone_current();
|
||||||
|
// Windows and MacOS
|
||||||
|
// We don't match on the full path, but on the suffix we pass to `repo_init_suffix` below.
|
||||||
|
settings.add_filter(r" *\[…\]\S+-insta/?", "[TEMP_FILE]");
|
||||||
|
// Linux Temp Folder
|
||||||
|
settings.add_filter(r" */tmp/\.tmp\S+-insta/", "[TEMP_FILE]");
|
||||||
|
// Commit ids that follow a vertical bar
|
||||||
|
settings.add_filter(r"│[a-z0-9]{7} ", "│[AAAAA] ");
|
||||||
|
let _bound = settings.bind_to_scope();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gitui_starts() {
|
||||||
|
apply_common_filters!();
|
||||||
|
|
||||||
|
let (temp_dir, _repo) = repo_init_suffix(Some("-insta"));
|
||||||
|
let path: RepoPath = temp_dir.path().to_str().unwrap().into();
|
||||||
|
let cliargs = CliArgs {
|
||||||
|
theme: PathBuf::from("theme.ron"),
|
||||||
|
select_file: None,
|
||||||
|
repo_path: path,
|
||||||
|
notify_watcher: false,
|
||||||
|
key_bindings_path: None,
|
||||||
|
key_symbols_path: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let theme = Theme::init(&PathBuf::new());
|
||||||
|
let key_config = KeyConfig::default();
|
||||||
|
|
||||||
|
let mut gitui =
|
||||||
|
Gitui::new(cliargs, theme, &key_config, Updater::Ticker)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut terminal =
|
||||||
|
Terminal::new(TestBackend::new(90, 12)).unwrap();
|
||||||
|
|
||||||
|
gitui.draw(&mut terminal).unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!("app_loading", terminal.backend());
|
||||||
|
|
||||||
|
let event =
|
||||||
|
AsyncNotification::Git(AsyncGitNotification::Status);
|
||||||
|
gitui.update_async(event);
|
||||||
|
|
||||||
|
gitui.draw(&mut terminal).unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!("app_loading_finished", terminal.backend());
|
||||||
|
|
||||||
|
gitui.input_event(KeyCode::Char('2'), KeyModifiers::empty());
|
||||||
|
gitui.input_event(
|
||||||
|
key_config.keys.tab_log.code,
|
||||||
|
key_config.keys.tab_log.modifiers,
|
||||||
|
);
|
||||||
|
|
||||||
|
gitui.wait_for_async_git_notification(
|
||||||
|
AsyncGitNotification::Log,
|
||||||
|
);
|
||||||
|
|
||||||
|
gitui.update();
|
||||||
|
|
||||||
|
gitui.draw(&mut terminal).unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(
|
||||||
|
"app_log_tab_showing_one_commit",
|
||||||
|
terminal.backend()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/main.rs
120
src/main.rs
|
|
@ -65,6 +65,7 @@ mod bug_report;
|
||||||
mod clipboard;
|
mod clipboard;
|
||||||
mod cmdbar;
|
mod cmdbar;
|
||||||
mod components;
|
mod components;
|
||||||
|
mod gitui;
|
||||||
mod input;
|
mod input;
|
||||||
mod keys;
|
mod keys;
|
||||||
mod notify_mutex;
|
mod notify_mutex;
|
||||||
|
|
@ -85,12 +86,9 @@ use crate::{
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use app::QuitState;
|
use app::QuitState;
|
||||||
use asyncgit::{
|
use asyncgit::{sync::RepoPath, AsyncGitNotification};
|
||||||
sync::{utils::repo_work_dir, RepoPath},
|
|
||||||
AsyncGitNotification,
|
|
||||||
};
|
|
||||||
use backtrace::Backtrace;
|
use backtrace::Backtrace;
|
||||||
use crossbeam_channel::{never, tick, unbounded, Receiver, Select};
|
use crossbeam_channel::{Receiver, Select};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
terminal::{
|
terminal::{
|
||||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen,
|
disable_raw_mode, enable_raw_mode, EnterAlternateScreen,
|
||||||
|
|
@ -98,12 +96,11 @@ use crossterm::{
|
||||||
},
|
},
|
||||||
ExecutableCommand,
|
ExecutableCommand,
|
||||||
};
|
};
|
||||||
use input::{Input, InputEvent, InputState};
|
use gitui::Gitui;
|
||||||
|
use input::InputEvent;
|
||||||
use keys::KeyConfig;
|
use keys::KeyConfig;
|
||||||
use ratatui::backend::CrosstermBackend;
|
use ratatui::backend::CrosstermBackend;
|
||||||
use scopeguard::defer;
|
use scopeguard::defer;
|
||||||
use scopetime::scope_time;
|
|
||||||
use spinner::Spinner;
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, Stdout},
|
io::{self, Stdout},
|
||||||
panic,
|
panic,
|
||||||
|
|
@ -111,7 +108,6 @@ use std::{
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use ui::style::Theme;
|
use ui::style::Theme;
|
||||||
use watcher::RepoWatcher;
|
|
||||||
|
|
||||||
type Terminal = ratatui::Terminal<CrosstermBackend<io::Stdout>>;
|
type Terminal = ratatui::Terminal<CrosstermBackend<io::Stdout>>;
|
||||||
|
|
||||||
|
|
@ -187,7 +183,6 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
let mut terminal =
|
let mut terminal =
|
||||||
start_terminal(io::stdout(), &cliargs.repo_path)?;
|
start_terminal(io::stdout(), &cliargs.repo_path)?;
|
||||||
let input = Input::new();
|
|
||||||
|
|
||||||
let updater = if cliargs.notify_watcher {
|
let updater = if cliargs.notify_watcher {
|
||||||
Updater::NotifyWatcher
|
Updater::NotifyWatcher
|
||||||
|
|
@ -202,8 +197,7 @@ fn main() -> Result<()> {
|
||||||
app_start,
|
app_start,
|
||||||
args.clone(),
|
args.clone(),
|
||||||
theme.clone(),
|
theme.clone(),
|
||||||
key_config.clone(),
|
&key_config,
|
||||||
&input,
|
|
||||||
updater,
|
updater,
|
||||||
&mut terminal,
|
&mut terminal,
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -230,106 +224,15 @@ fn run_app(
|
||||||
app_start: Instant,
|
app_start: Instant,
|
||||||
cliargs: CliArgs,
|
cliargs: CliArgs,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
key_config: KeyConfig,
|
key_config: &KeyConfig,
|
||||||
input: &Input,
|
|
||||||
updater: Updater,
|
updater: Updater,
|
||||||
terminal: &mut Terminal,
|
terminal: &mut Terminal,
|
||||||
) -> Result<QuitState, anyhow::Error> {
|
) -> Result<QuitState, anyhow::Error> {
|
||||||
let (tx_git, rx_git) = unbounded();
|
let mut gitui = Gitui::new(cliargs, theme, key_config, updater)?;
|
||||||
let (tx_app, rx_app) = unbounded();
|
|
||||||
|
|
||||||
let rx_input = input.receiver();
|
|
||||||
|
|
||||||
let (rx_ticker, rx_watcher) = match updater {
|
|
||||||
Updater::NotifyWatcher => {
|
|
||||||
let repo_watcher = RepoWatcher::new(
|
|
||||||
repo_work_dir(&cliargs.repo_path)?.as_str(),
|
|
||||||
);
|
|
||||||
|
|
||||||
(never(), repo_watcher.receiver())
|
|
||||||
}
|
|
||||||
Updater::Ticker => (tick(TICK_INTERVAL), never()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let spinner_ticker = tick(SPINNER_INTERVAL);
|
|
||||||
|
|
||||||
let mut app = App::new(
|
|
||||||
cliargs,
|
|
||||||
tx_git,
|
|
||||||
tx_app,
|
|
||||||
input.clone(),
|
|
||||||
theme,
|
|
||||||
key_config,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut spinner = Spinner::default();
|
|
||||||
let mut first_update = true;
|
|
||||||
|
|
||||||
log::trace!("app start: {} ms", app_start.elapsed().as_millis());
|
log::trace!("app start: {} ms", app_start.elapsed().as_millis());
|
||||||
|
|
||||||
loop {
|
gitui.run_main_loop(terminal)
|
||||||
let event = if first_update {
|
|
||||||
first_update = false;
|
|
||||||
QueueEvent::Notify
|
|
||||||
} else {
|
|
||||||
select_event(
|
|
||||||
&rx_input,
|
|
||||||
&rx_git,
|
|
||||||
&rx_app,
|
|
||||||
&rx_ticker,
|
|
||||||
&rx_watcher,
|
|
||||||
&spinner_ticker,
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
if matches!(event, QueueEvent::SpinnerUpdate) {
|
|
||||||
spinner.update();
|
|
||||||
spinner.draw(terminal)?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
scope_time!("loop");
|
|
||||||
|
|
||||||
match event {
|
|
||||||
QueueEvent::InputEvent(ev) => {
|
|
||||||
if matches!(
|
|
||||||
ev,
|
|
||||||
InputEvent::State(InputState::Polling)
|
|
||||||
) {
|
|
||||||
//Note: external ed closed, we need to re-hide cursor
|
|
||||||
terminal.hide_cursor()?;
|
|
||||||
}
|
|
||||||
app.event(ev)?;
|
|
||||||
}
|
|
||||||
QueueEvent::Tick | QueueEvent::Notify => {
|
|
||||||
app.update()?;
|
|
||||||
}
|
|
||||||
QueueEvent::AsyncEvent(ev) => {
|
|
||||||
if !matches!(
|
|
||||||
ev,
|
|
||||||
AsyncNotification::Git(
|
|
||||||
AsyncGitNotification::FinishUnchanged
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
app.update_async(ev)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QueueEvent::SpinnerUpdate => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
draw(terminal, &app)?;
|
|
||||||
|
|
||||||
spinner.set_state(app.any_work_pending());
|
|
||||||
spinner.draw(terminal)?;
|
|
||||||
|
|
||||||
if app.is_quit() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(app.quit_state())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_terminal() -> Result<()> {
|
fn setup_terminal() -> Result<()> {
|
||||||
|
|
@ -353,7 +256,10 @@ fn shutdown_terminal() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(terminal: &mut Terminal, app: &App) -> io::Result<()> {
|
fn draw<B: ratatui::backend::Backend>(
|
||||||
|
terminal: &mut ratatui::Terminal<B>,
|
||||||
|
app: &App,
|
||||||
|
) -> io::Result<()> {
|
||||||
if app.requires_redraw() {
|
if app.requires_redraw() {
|
||||||
terminal.clear()?;
|
terminal.clear()?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
src/snapshots/gitui__gitui__tests__app_loading.snap
Normal file
17
src/snapshots/gitui__gitui__tests__app_loading.snap
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
source: src/gitui.rs
|
||||||
|
expression: terminal.backend()
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
" Status [1] | Log [2] | Files [3] | Stashing [4] | Stashes [5][TEMP_FILE] "
|
||||||
|
" ──────────────────────────────────────────────────────────────────────────────────────── "
|
||||||
|
"┌Unstaged Changes───────────────────────────┐┌Diff: ─────────────────────────────────────┐"
|
||||||
|
"│Loading ... ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"└───────────────────────────────────{master}┘│ │"
|
||||||
|
"┌Staged Changes─────────────────────────────┐│ │"
|
||||||
|
"│Loading ... ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"└───────────────────────────────────────────┘└───────────────────────────────────────────┘"
|
||||||
|
" "
|
||||||
17
src/snapshots/gitui__gitui__tests__app_loading_finished.snap
Normal file
17
src/snapshots/gitui__gitui__tests__app_loading_finished.snap
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
source: src/gitui.rs
|
||||||
|
expression: terminal.backend()
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
" Status [1] | Log [2] | Files [3] | Stashing [4] | Stashes [5][TEMP_FILE] "
|
||||||
|
" ──────────────────────────────────────────────────────────────────────────────────────── "
|
||||||
|
"┌Unstaged Changes───────────────────────────┐┌Diff: ─────────────────────────────────────┐"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"└───────────────────────────────────{master}┘│ │"
|
||||||
|
"┌Staged Changes─────────────────────────────┐│ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"└───────────────────────────────────────────┘└───────────────────────────────────────────┘"
|
||||||
|
"Branches [b] Push [p] Fetch [⇧F] Pull [f] Undo Commit [⇧U] Submodules [⇧S] more [.]"
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
source: src/gitui.rs
|
||||||
|
expression: terminal.backend()
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
" Status [1] | Log [2] | Files [3] | Stashing [4] | Stashes [5][TEMP_FILE] "
|
||||||
|
" ──────────────────────────────────────────────────────────────────────────────────────── "
|
||||||
|
"┌Commit 1/1──────────────────────────────────────────────────────────────────────────────┐"
|
||||||
|
"│[AAAAA] <1m ago name initial █"
|
||||||
|
"│ ║"
|
||||||
|
"│ ║"
|
||||||
|
"│ ║"
|
||||||
|
"│ ║"
|
||||||
|
"│ ║"
|
||||||
|
"│ ║"
|
||||||
|
"└────────────────────────────────────────────────────────────────────────────────────────┘"
|
||||||
|
"Scroll [↑↓] Mark [˽] Details [⏎] Branches [b] Compare [⇧C] Copy Hash [y] Tag [t] more [.]"
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
use ratatui::{
|
use ratatui::{backend::Backend, Terminal};
|
||||||
backend::{Backend, CrosstermBackend},
|
|
||||||
Terminal,
|
|
||||||
};
|
|
||||||
use std::{cell::Cell, char, io};
|
use std::{cell::Cell, char, io};
|
||||||
|
|
||||||
// static SPINNER_CHARS: &[char] = &['◢', '◣', '◤', '◥'];
|
// static SPINNER_CHARS: &[char] = &['◢', '◣', '◤', '◥'];
|
||||||
|
|
@ -39,9 +36,9 @@ impl Spinner {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// draws or removes spinner char depending on `pending` state
|
/// draws or removes spinner char depending on `pending` state
|
||||||
pub fn draw(
|
pub fn draw<B: ratatui::backend::Backend>(
|
||||||
&self,
|
&self,
|
||||||
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
terminal: &mut Terminal<B>,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
let idx = self.idx;
|
let idx = self.idx;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue