Skip to content

Commit 0e3bf0c

Browse files
committed
Extract main loop into Gitui::run_main_loop
1 parent 45fe62c commit 0e3bf0c

File tree

3 files changed

+145
-124
lines changed

3 files changed

+145
-124
lines changed

src/gitui.rs

Lines changed: 135 additions & 18 deletions
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

src/main.rs

Lines changed: 7 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -50,35 +50,29 @@ mod watcher;
5050
use crate::{app::App, args::process_cmdline};
5151
use anyhow::{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,
7671
path::Path,
7772
process,
7873
time::{Duration, Instant},
7974
};
8075
use ui::style::Theme;
81-
use watcher::RepoWatcher;
8276

8377
type Terminal = ratatui::Terminal<CrosstermBackend<io::Stdout>>;
8478

@@ -147,7 +141,6 @@ fn main() -> Result<()> {
147141

148142
let mut repo_path = cliargs.repo_path;
149143
let mut terminal = start_terminal(io::stdout(), &repo_path)?;
150-
let input = Input::new();
151144

152145
let updater = if cliargs.notify_watcher {
153146
Updater::NotifyWatcher
@@ -161,7 +154,6 @@ fn main() -> Result<()> {
161154
repo_path.clone(),
162155
theme.clone(),
163156
key_config.clone(),
164-
&input,
165157
updater,
166158
&mut terminal,
167159
)?;
@@ -182,100 +174,15 @@ fn run_app(
182174
repo: RepoPath,
183175
theme: Theme,
184176
key_config: KeyConfig,
185-
input: &Input,
186177
updater: Updater,
187178
terminal: &mut Terminal,
188179
) -> Result<QuitState, anyhow::Error> {
189-
let (tx_git, rx_git) = unbounded();
190-
let (tx_app, rx_app) = unbounded();
191-
192-
let rx_input = input.receiver();
193-
194-
let (rx_ticker, rx_watcher) = match updater {
195-
Updater::NotifyWatcher => {
196-
let repo_watcher =
197-
RepoWatcher::new(repo_work_dir(&repo)?.as_str());
198-
199-
(never(), repo_watcher.receiver())
200-
}
201-
Updater::Ticker => (tick(TICK_INTERVAL), never()),
202-
};
203-
204-
let spinner_ticker = tick(SPINNER_INTERVAL);
205-
206-
let mut app = App::new(
207-
RefCell::new(repo),
208-
tx_git,
209-
tx_app,
210-
input.clone(),
211-
theme,
212-
key_config,
213-
)?;
214-
215-
let mut spinner = Spinner::default();
180+
let mut gitui =
181+
Gitui::new(repo.clone(), theme, &key_config, updater)?;
216182

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

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

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

0 commit comments

Comments
 (0)