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) ]
512use crossterm:: event:: { KeyCode , KeyModifiers } ;
613
714use 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
1836impl 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) ]
75180mod 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
0 commit comments