1
- use std:: { cell:: RefCell , path :: PathBuf } ;
1
+ use std:: { cell:: RefCell , time :: Instant } ;
2
2
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) ]
5
12
use crossterm:: event:: { KeyCode , KeyModifiers } ;
6
13
7
14
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 ,
10
25
} ;
11
26
12
- struct Gitui {
27
+ pub ( crate ) struct Gitui {
13
28
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 < ( ) > ,
16
34
}
17
35
18
36
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 > {
20
43
let ( tx_git, rx_git) = unbounded ( ) ;
21
44
let ( tx_app, rx_app) = unbounded ( ) ;
22
45
23
46
let input = Input :: new ( ) ;
24
47
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
+ } ;
27
57
28
58
let app = App :: new (
29
59
RefCell :: new ( path) ,
@@ -35,11 +65,83 @@ impl Gitui {
35
65
)
36
66
. unwrap ( ) ;
37
67
38
- Self {
68
+ Ok ( Self {
39
69
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
+ }
42
142
}
143
+
144
+ Ok ( self . app . quit_state ( ) )
43
145
}
44
146
45
147
fn draw < B : ratatui:: backend:: Backend > (
@@ -49,10 +151,12 @@ impl Gitui {
49
151
draw ( terminal, & self . app ) . unwrap ( ) ;
50
152
}
51
153
154
+ #[ cfg( test) ]
52
155
fn update_async ( & mut self , event : crate :: AsyncNotification ) {
53
156
self . app . update_async ( event) . unwrap ( ) ;
54
157
}
55
158
159
+ #[ cfg( test) ]
56
160
fn input_event (
57
161
& mut self ,
58
162
code : KeyCode ,
@@ -66,22 +170,26 @@ impl Gitui {
66
170
. unwrap ( ) ;
67
171
}
68
172
173
+ #[ cfg( test) ]
69
174
fn update ( & mut self ) {
70
175
self . app . update ( ) . unwrap ( ) ;
71
176
}
72
177
}
73
178
74
179
#[ cfg( test) ]
75
180
mod tests {
76
- use std:: { thread:: sleep, time:: Duration } ;
181
+ use std:: { path :: PathBuf , thread:: sleep, time:: Duration } ;
77
182
78
183
use asyncgit:: { sync:: RepoPath , AsyncGitNotification } ;
79
184
use crossterm:: event:: { KeyCode , KeyModifiers } ;
80
185
use git2_testing:: repo_init;
81
186
use insta:: assert_snapshot;
82
187
use ratatui:: { backend:: TestBackend , Terminal } ;
83
188
84
- use crate :: { gitui:: Gitui , AsyncNotification } ;
189
+ use crate :: {
190
+ gitui:: Gitui , keys:: KeyConfig , ui:: style:: Theme ,
191
+ AsyncNotification , Updater ,
192
+ } ;
85
193
86
194
// Macro adapted from: https://insta.rs/docs/cmd/
87
195
macro_rules! apply_common_filters {
@@ -106,11 +214,16 @@ mod tests {
106
214
let ( temp_dir, _repo) = repo_init ( ) ;
107
215
let path: RepoPath = temp_dir. path ( ) . to_str ( ) . unwrap ( ) . into ( ) ;
108
216
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
+
109
224
let mut terminal =
110
225
Terminal :: new ( TestBackend :: new ( 120 , 40 ) ) . unwrap ( ) ;
111
226
112
- let mut gitui = Gitui :: new ( path) ;
113
-
114
227
gitui. draw ( & mut terminal) ;
115
228
116
229
sleep ( Duration :: from_millis ( 500 ) ) ;
@@ -128,6 +241,10 @@ mod tests {
128
241
assert_snapshot ! ( "app_loading_finished" , terminal. backend( ) ) ;
129
242
130
243
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
+ ) ;
131
248
132
249
sleep ( Duration :: from_millis ( 500 ) ) ;
133
250
0 commit comments