Skip to content

Commit 2c71c67

Browse files
committed
Extract main loop into Gitui::run_main_loop
1 parent f65cd45 commit 2c71c67

File tree

3 files changed

+145
-124
lines changed

3 files changed

+145
-124
lines changed

Diff for: src/gitui.rs

+135-18
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,59 @@
1-
use std::{cell::RefCell, path::PathBuf};
1+
use std::{cell::RefCell, time::Instant};
22

3-
use asyncgit::{sync::RepoPath, AsyncGitNotification};
4-
use crossbeam_channel::{unbounded, Receiver};
3+
use anyhow::Result;
4+
use asyncgit::{
5+
sync::{utils::repo_work_dir, RepoPath},
6+
AsyncGitNotification,
7+
};
8+
use crossbeam_channel::{never, tick, unbounded, Receiver};
9+
use scopetime::scope_time;
10+
11+
#[cfg(test)]
512
use crossterm::event::{KeyCode, KeyModifiers};
613

714
use crate::{
8-
app::App, draw, input::Input, keys::KeyConfig, ui::style::Theme,
9-
AsyncAppNotification,
15+
app::{App, QuitState},
16+
draw,
17+
input::{Input, InputEvent, InputState},
18+
keys::KeyConfig,
19+
select_event,
20+
spinner::Spinner,
21+
ui::style::Theme,
22+
watcher::RepoWatcher,
23+
AsyncAppNotification, AsyncNotification, QueueEvent, Updater,
24+
SPINNER_INTERVAL, TICK_INTERVAL,
1025
};
1126

12-
struct Gitui {
27+
pub(crate) struct Gitui {
1328
app: crate::app::App,
14-
_rx_git: Receiver<AsyncGitNotification>,
15-
_rx_app: Receiver<AsyncAppNotification>,
29+
rx_input: Receiver<InputEvent>,
30+
rx_git: Receiver<AsyncGitNotification>,
31+
rx_app: Receiver<AsyncAppNotification>,
32+
rx_ticker: Receiver<Instant>,
33+
rx_watcher: Receiver<()>,
1634
}
1735

1836
impl Gitui {
19-
fn new(path: RepoPath) -> Self {
37+
pub(crate) fn new(
38+
path: RepoPath,
39+
theme: Theme,
40+
key_config: &KeyConfig,
41+
updater: Updater,
42+
) -> Result<Self, anyhow::Error> {
2043
let (tx_git, rx_git) = unbounded();
2144
let (tx_app, rx_app) = unbounded();
2245

2346
let input = Input::new();
2447

25-
let theme = Theme::init(&PathBuf::new());
26-
let key_config = KeyConfig::default();
48+
let (rx_ticker, rx_watcher) = match updater {
49+
Updater::NotifyWatcher => {
50+
let repo_watcher =
51+
RepoWatcher::new(repo_work_dir(&path)?.as_str());
52+
53+
(never(), repo_watcher.receiver())
54+
}
55+
Updater::Ticker => (tick(TICK_INTERVAL), never()),
56+
};
2757

2858
let app = App::new(
2959
RefCell::new(path),
@@ -35,11 +65,83 @@ impl Gitui {
3565
)
3666
.unwrap();
3767

38-
Self {
68+
Ok(Self {
3969
app,
40-
_rx_git: rx_git,
41-
_rx_app: rx_app,
70+
rx_input: input.receiver(),
71+
rx_git,
72+
rx_app,
73+
rx_ticker,
74+
rx_watcher,
75+
})
76+
}
77+
78+
pub(crate) fn run_main_loop<B: ratatui::backend::Backend>(
79+
&mut self,
80+
terminal: &mut ratatui::Terminal<B>,
81+
) -> Result<QuitState, anyhow::Error> {
82+
let spinner_ticker = tick(SPINNER_INTERVAL);
83+
let mut spinner = Spinner::default();
84+
85+
self.app.update()?;
86+
87+
loop {
88+
let event = select_event(
89+
&self.rx_input,
90+
&self.rx_git,
91+
&self.rx_app,
92+
&self.rx_ticker,
93+
&self.rx_watcher,
94+
&spinner_ticker,
95+
)?;
96+
97+
{
98+
if matches!(event, QueueEvent::SpinnerUpdate) {
99+
spinner.update();
100+
spinner.draw(terminal)?;
101+
continue;
102+
}
103+
104+
scope_time!("loop");
105+
106+
match event {
107+
QueueEvent::InputEvent(ev) => {
108+
if matches!(
109+
ev,
110+
InputEvent::State(InputState::Polling)
111+
) {
112+
//Note: external ed closed, we need to re-hide cursor
113+
terminal.hide_cursor()?;
114+
}
115+
self.app.event(ev)?;
116+
}
117+
QueueEvent::Tick | QueueEvent::Notify => {
118+
self.app.update()?;
119+
}
120+
QueueEvent::AsyncEvent(ev) => {
121+
if !matches!(
122+
ev,
123+
AsyncNotification::Git(
124+
AsyncGitNotification::FinishUnchanged
125+
)
126+
) {
127+
self.app.update_async(ev)?;
128+
}
129+
}
130+
QueueEvent::SpinnerUpdate => unreachable!(),
131+
}
132+
133+
self.draw(terminal);
134+
135+
spinner.set_state(self.app.any_work_pending());
136+
spinner.draw(terminal)?;
137+
138+
if self.app.is_quit() {
139+
break;
140+
}
141+
}
42142
}
143+
144+
Ok(self.app.quit_state())
43145
}
44146

45147
fn draw<B: ratatui::backend::Backend>(
@@ -49,10 +151,12 @@ impl Gitui {
49151
draw(terminal, &self.app).unwrap();
50152
}
51153

154+
#[cfg(test)]
52155
fn update_async(&mut self, event: crate::AsyncNotification) {
53156
self.app.update_async(event).unwrap();
54157
}
55158

159+
#[cfg(test)]
56160
fn input_event(
57161
&mut self,
58162
code: KeyCode,
@@ -66,22 +170,26 @@ impl Gitui {
66170
.unwrap();
67171
}
68172

173+
#[cfg(test)]
69174
fn update(&mut self) {
70175
self.app.update().unwrap();
71176
}
72177
}
73178

74179
#[cfg(test)]
75180
mod tests {
76-
use std::{thread::sleep, time::Duration};
181+
use std::{path::PathBuf, thread::sleep, time::Duration};
77182

78183
use asyncgit::{sync::RepoPath, AsyncGitNotification};
79184
use crossterm::event::{KeyCode, KeyModifiers};
80185
use git2_testing::repo_init;
81186
use insta::assert_snapshot;
82187
use ratatui::{backend::TestBackend, Terminal};
83188

84-
use crate::{gitui::Gitui, AsyncNotification};
189+
use crate::{
190+
gitui::Gitui, keys::KeyConfig, ui::style::Theme,
191+
AsyncNotification, Updater,
192+
};
85193

86194
// Macro adapted from: https://insta.rs/docs/cmd/
87195
macro_rules! apply_common_filters {
@@ -106,11 +214,16 @@ mod tests {
106214
let (temp_dir, _repo) = repo_init();
107215
let path: RepoPath = temp_dir.path().to_str().unwrap().into();
108216

217+
let theme = Theme::init(&PathBuf::new());
218+
let key_config = KeyConfig::default();
219+
220+
let mut gitui =
221+
Gitui::new(path, theme, &key_config, Updater::Ticker)
222+
.unwrap();
223+
109224
let mut terminal =
110225
Terminal::new(TestBackend::new(120, 40)).unwrap();
111226

112-
let mut gitui = Gitui::new(path);
113-
114227
gitui.draw(&mut terminal);
115228

116229
sleep(Duration::from_millis(500));
@@ -128,6 +241,10 @@ mod tests {
128241
assert_snapshot!("app_loading_finished", terminal.backend());
129242

130243
gitui.input_event(KeyCode::Char('2'), KeyModifiers::empty());
244+
gitui.input_event(
245+
key_config.keys.tab_log.code,
246+
key_config.keys.tab_log.modifiers,
247+
);
131248

132249
sleep(Duration::from_millis(500));
133250

Diff for: src/main.rs

+7-100
Original file line numberDiff line numberDiff line change
@@ -50,33 +50,27 @@ mod watcher;
5050
use crate::{app::App, args::process_cmdline};
5151
use anyhow::{bail, Result};
5252
use app::QuitState;
53-
use asyncgit::{
54-
sync::{utils::repo_work_dir, RepoPath},
55-
AsyncGitNotification,
56-
};
53+
use asyncgit::{sync::RepoPath, AsyncGitNotification};
5754
use backtrace::Backtrace;
58-
use crossbeam_channel::{never, tick, unbounded, Receiver, Select};
55+
use crossbeam_channel::{Receiver, Select};
5956
use crossterm::{
6057
terminal::{
6158
disable_raw_mode, enable_raw_mode, EnterAlternateScreen,
6259
LeaveAlternateScreen,
6360
},
6461
ExecutableCommand,
6562
};
66-
use input::{Input, InputEvent, InputState};
63+
use gitui::Gitui;
64+
use input::InputEvent;
6765
use keys::KeyConfig;
6866
use ratatui::backend::CrosstermBackend;
6967
use scopeguard::defer;
70-
use scopetime::scope_time;
71-
use spinner::Spinner;
7268
use std::{
73-
cell::RefCell,
7469
io::{self, Stdout},
7570
panic, process,
7671
time::{Duration, Instant},
7772
};
7873
use ui::style::Theme;
79-
use watcher::RepoWatcher;
8074

8175
type Terminal = ratatui::Terminal<CrosstermBackend<io::Stdout>>;
8276

@@ -145,7 +139,6 @@ fn main() -> Result<()> {
145139

146140
let mut terminal = start_terminal(io::stdout())?;
147141
let mut repo_path = cliargs.repo_path;
148-
let input = Input::new();
149142

150143
let updater = if cliargs.notify_watcher {
151144
Updater::NotifyWatcher
@@ -159,7 +152,6 @@ fn main() -> Result<()> {
159152
repo_path.clone(),
160153
theme.clone(),
161154
key_config.clone(),
162-
&input,
163155
updater,
164156
&mut terminal,
165157
)?;
@@ -180,100 +172,15 @@ fn run_app(
180172
repo: RepoPath,
181173
theme: Theme,
182174
key_config: KeyConfig,
183-
input: &Input,
184175
updater: Updater,
185176
terminal: &mut Terminal,
186177
) -> Result<QuitState, anyhow::Error> {
187-
let (tx_git, rx_git) = unbounded();
188-
let (tx_app, rx_app) = unbounded();
189-
190-
let rx_input = input.receiver();
191-
192-
let (rx_ticker, rx_watcher) = match updater {
193-
Updater::NotifyWatcher => {
194-
let repo_watcher =
195-
RepoWatcher::new(repo_work_dir(&repo)?.as_str());
196-
197-
(never(), repo_watcher.receiver())
198-
}
199-
Updater::Ticker => (tick(TICK_INTERVAL), never()),
200-
};
201-
202-
let spinner_ticker = tick(SPINNER_INTERVAL);
203-
204-
let mut app = App::new(
205-
RefCell::new(repo),
206-
tx_git,
207-
tx_app,
208-
input.clone(),
209-
theme,
210-
key_config,
211-
)?;
212-
213-
let mut spinner = Spinner::default();
178+
let mut gitui =
179+
Gitui::new(repo.clone(), theme, &key_config, updater)?;
214180

215181
log::trace!("app start: {} ms", app_start.elapsed().as_millis());
216182

217-
app.update()?;
218-
219-
loop {
220-
let event = select_event(
221-
&rx_input,
222-
&rx_git,
223-
&rx_app,
224-
&rx_ticker,
225-
&rx_watcher,
226-
&spinner_ticker,
227-
)?;
228-
229-
{
230-
if matches!(event, QueueEvent::SpinnerUpdate) {
231-
spinner.update();
232-
spinner.draw(terminal)?;
233-
continue;
234-
}
235-
236-
scope_time!("loop");
237-
238-
match event {
239-
QueueEvent::InputEvent(ev) => {
240-
if matches!(
241-
ev,
242-
InputEvent::State(InputState::Polling)
243-
) {
244-
//Note: external ed closed, we need to re-hide cursor
245-
terminal.hide_cursor()?;
246-
}
247-
app.event(ev)?;
248-
}
249-
QueueEvent::Tick | QueueEvent::Notify => {
250-
app.update()?;
251-
}
252-
QueueEvent::AsyncEvent(ev) => {
253-
if !matches!(
254-
ev,
255-
AsyncNotification::Git(
256-
AsyncGitNotification::FinishUnchanged
257-
)
258-
) {
259-
app.update_async(ev)?;
260-
}
261-
}
262-
QueueEvent::SpinnerUpdate => unreachable!(),
263-
}
264-
265-
draw(terminal, &app)?;
266-
267-
spinner.set_state(app.any_work_pending());
268-
spinner.draw(terminal)?;
269-
270-
if app.is_quit() {
271-
break;
272-
}
273-
}
274-
}
275-
276-
Ok(app.quit_state())
183+
gitui.run_main_loop(terminal)
277184
}
278185

279186
fn setup_terminal() -> Result<()> {

0 commit comments

Comments
 (0)