@@ -21,12 +21,15 @@ mod wallet_prompt;
21
21
use std:: path:: PathBuf ;
22
22
23
23
use clap:: Command ;
24
+ use itertools:: Itertools ;
24
25
use reedline:: {
25
26
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
26
27
ColumnarMenu , DefaultValidator , EditMode , Emacs , FileBackedHistory , ListMenu , MenuBuilder ,
27
28
Reedline , ReedlineMenu , Signal , Vi ,
28
29
} ;
30
+
29
31
use tokio:: sync:: { mpsc, oneshot} ;
32
+ use utils:: once_destructor:: OnceDestructor ;
30
33
use wallet_cli_commands:: { get_repl_command, parse_input, ConsoleCommand } ;
31
34
use wallet_rpc_lib:: types:: NodeInterface ;
32
35
@@ -197,6 +200,9 @@ fn handle_response<N: NodeInterface>(
197
200
Ok ( Some ( ConsoleCommand :: Print ( text) ) ) => {
198
201
console. print_line ( & text) ;
199
202
}
203
+ Ok ( Some ( ConsoleCommand :: PaginatedPrint { header, body } ) ) => {
204
+ paginate_output ( header, body, line_editor, console) . expect ( "Should not fail normally" ) ;
205
+ }
200
206
Ok ( Some ( ConsoleCommand :: SetStatus {
201
207
status,
202
208
print_message,
@@ -226,3 +232,118 @@ fn handle_response<N: NodeInterface>(
226
232
}
227
233
None
228
234
}
235
+
236
+ fn paginate_output (
237
+ header : String ,
238
+ body : String ,
239
+ line_editor : & mut Reedline ,
240
+ console : & mut impl ConsoleOutput ,
241
+ ) -> std:: io:: Result < ( ) > {
242
+ let mut current_index = 0 ;
243
+
244
+ let ( mut page_rows, mut offsets) = compute_page_line_offsets ( & body) ;
245
+ let mut last_batch = offsets. len ( ) - 1 ;
246
+
247
+ loop {
248
+ line_editor. clear_screen ( ) ?;
249
+
250
+ let end_batch = std:: cmp:: min ( current_index + page_rows, last_batch) ;
251
+ let start = offsets[ current_index] ;
252
+ let end = offsets[ end_batch] ;
253
+ console. print_line ( & header) ;
254
+ console. print_line ( body. get ( start..end) . expect ( "safe point" ) ) ;
255
+
256
+ let commands = match ( current_index, end_batch) {
257
+ ( 0 , end) if end == last_batch => "Press 'q' to quit" ,
258
+ ( 0 , _) => "Press 'j' for next, 'q' to quit" ,
259
+ ( _, end) if end == last_batch => "Press 'k' for previous, 'q' to quit" ,
260
+ _ => "Press 'j' for next, 'k' for previous, 'q' to quit" ,
261
+ } ;
262
+ console. print_line ( commands) ;
263
+
264
+ match read_command ( current_index, last_batch, end_batch) ? {
265
+ PagginationCommand :: Exit => break ,
266
+ PagginationCommand :: Next => {
267
+ current_index += 1 ;
268
+ }
269
+ PagginationCommand :: Previous => {
270
+ current_index -= 1 ;
271
+ }
272
+ PagginationCommand :: TerminalResize => {
273
+ ( page_rows, offsets) = compute_page_line_offsets ( & body) ;
274
+ last_batch = offsets. len ( ) - 1 ;
275
+ current_index = std:: cmp:: min ( current_index, last_batch - page_rows) ;
276
+ }
277
+ }
278
+ }
279
+
280
+ line_editor. clear_screen ( )
281
+ }
282
+
283
+ fn compute_page_line_offsets ( body : & str ) -> ( usize , Vec < usize > ) {
284
+ let ( cols, rows) = crossterm:: terminal:: size ( ) . unwrap_or ( ( 80 , 24 ) ) ;
285
+ let cols = cols as usize ;
286
+
287
+ // make room for the header and prompt
288
+ let page_rows = ( rows - 4 ) as usize ;
289
+
290
+ let position_offsets = ( 0 ..1 ) // prepend 0 as starting position
291
+ . chain ( body. char_indices ( ) . peekable ( ) . batching ( |it| {
292
+ // if no more characters exit
293
+ it. peek ( ) ?;
294
+
295
+ // advance the iterator to the next new line or at least cols characters
296
+ let _skip = it. take ( cols) . find ( |( _, c) | * c == '\n' ) ;
297
+
298
+ match it. peek ( ) {
299
+ Some ( ( idx, _) ) => Some ( * idx) ,
300
+ None => Some ( body. len ( ) ) ,
301
+ }
302
+ } ) )
303
+ . collect_vec ( ) ;
304
+ ( page_rows, position_offsets)
305
+ }
306
+
307
+ enum PagginationCommand {
308
+ Exit ,
309
+ Next ,
310
+ Previous ,
311
+ TerminalResize ,
312
+ }
313
+
314
+ fn read_command (
315
+ current_index : usize ,
316
+ last_batch : usize ,
317
+ end_batch : usize ,
318
+ ) -> Result < PagginationCommand , std:: io:: Error > {
319
+ // TODO: maybe enable raw mode only once per paggination
320
+ crossterm:: terminal:: enable_raw_mode ( ) ?;
321
+ let _cleanup = OnceDestructor :: new ( || {
322
+ crossterm:: terminal:: disable_raw_mode ( ) . expect ( "Should not fail normally" )
323
+ } ) ;
324
+
325
+ loop {
326
+ let event = crossterm:: event:: read ( ) ?;
327
+
328
+ match event {
329
+ crossterm:: event:: Event :: Key ( key_event) => {
330
+ match key_event. code {
331
+ reedline:: KeyCode :: Char ( 'j' ) | reedline:: KeyCode :: Down
332
+ if end_batch < last_batch =>
333
+ {
334
+ return Ok ( PagginationCommand :: Next )
335
+ }
336
+ reedline:: KeyCode :: Char ( 'k' ) | reedline:: KeyCode :: Up if current_index > 0 => {
337
+ return Ok ( PagginationCommand :: Previous )
338
+ }
339
+ reedline:: KeyCode :: Char ( 'q' ) | reedline:: KeyCode :: Esc => {
340
+ return Ok ( PagginationCommand :: Exit ) ;
341
+ }
342
+ _ => { } // Ignore other keys
343
+ }
344
+ }
345
+ crossterm:: event:: Event :: Resize ( _, _) => return Ok ( PagginationCommand :: TerminalResize ) ,
346
+ _ => { }
347
+ }
348
+ }
349
+ }
0 commit comments