@@ -19,7 +19,6 @@ use asyncgit::{
19
19
diff_contains_file, get_commits_info, CommitId , RepoPathRef ,
20
20
} ,
21
21
AsyncDiff , AsyncGitNotification , AsyncLog , DiffParams , DiffType ,
22
- FetchStatus ,
23
22
} ;
24
23
use chrono:: { DateTime , Local } ;
25
24
use crossbeam_channel:: Sender ;
@@ -123,7 +122,10 @@ impl FileRevlogComponent {
123
122
& self . sender ,
124
123
Some ( filter) ,
125
124
) ) ;
126
- self . table_state . get_mut ( ) . select ( Some ( 0 ) ) ;
125
+
126
+ self . items . clear ( ) ;
127
+ self . set_selection ( open_request. selection . unwrap_or ( 0 ) ) ;
128
+
127
129
self . show ( ) ?;
128
130
129
131
self . diff . focus ( false ) ;
@@ -146,20 +148,9 @@ impl FileRevlogComponent {
146
148
///
147
149
pub fn update ( & mut self ) -> Result < ( ) > {
148
150
if let Some ( ref mut git_log) = self . git_log {
149
- let log_changed =
150
- git_log. fetch ( ) ? == FetchStatus :: Started ;
151
-
152
- let table_state = self . table_state . take ( ) ;
153
- let start = table_state. selected ( ) . unwrap_or ( 0 ) ;
154
- self . table_state . set ( table_state) ;
155
-
156
- if self . items . needs_data ( start, git_log. count ( ) ?)
157
- || log_changed
158
- {
159
- self . fetch_commits ( ) ?;
160
- self . set_open_selection ( ) ;
161
- }
151
+ git_log. fetch ( ) ?;
162
152
153
+ self . fetch_commits_if_needed ( ) ?;
163
154
self . update_diff ( ) ?;
164
155
}
165
156
@@ -220,47 +211,26 @@ impl FileRevlogComponent {
220
211
Ok ( ( ) )
221
212
}
222
213
223
- fn fetch_commits ( & mut self ) -> Result < ( ) > {
214
+ fn fetch_commits (
215
+ & mut self ,
216
+ new_offset : usize ,
217
+ new_max_offset : usize ,
218
+ ) -> Result < ( ) > {
224
219
if let Some ( git_log) = & mut self . git_log {
225
- let table_state = self . table_state . take ( ) ;
220
+ let amount = new_max_offset
221
+ . saturating_sub ( new_offset)
222
+ . max ( SLICE_SIZE ) ;
226
223
227
224
let commits = get_commits_info (
228
225
& self . repo_path . borrow ( ) ,
229
- & git_log. get_slice ( 0 , SLICE_SIZE ) ?,
226
+ & git_log. get_slice ( new_offset , amount ) ?,
230
227
self . current_width . get ( ) ,
231
228
) ;
232
229
233
230
if let Ok ( commits) = commits {
234
- // 2023-04-12
235
- //
236
- // There is an issue with how windowing works in `self.items` and
237
- // `self.table_state`. Because of that issue, we currently have to pass
238
- // `0` as the first argument to `set_items`. If we did not do that, the
239
- // offset that is kept separately in `self.items` and `self.table_state`
240
- // would get out of sync, resulting in the table showing the wrong rows.
241
- //
242
- // The offset determines the number of rows `render_stateful_widget`
243
- // skips when rendering a table. When `set_items` is called, it clears
244
- // its internal `Vec` of items and sets `index_offset` based on the
245
- // parameter passed. Unfortunately, there is no way for us to pass this
246
- // information, `index_offset`, to `render_stateful_widget`. Because of
247
- // that, `render_stateful_widget` assumes that the rows provided by
248
- // `Table` are 0-indexed while in reality they are
249
- // `index_offset`-indexed.
250
- //
251
- // This fix makes the `FileRevlog` unable to show histories that have
252
- // more than `SLICE_SIZE` items, but since it is broken for larger
253
- // histories anyway, this seems acceptable for the time being.
254
- //
255
- // This issue can only properly be fixed upstream, in `tui-rs`. See
256
- // [tui-issue].
257
- //
258
- // [gitui-issue]: https://github.com/extrawurst/gitui/issues/1560
259
- // [tui-issue]: https://github.com/fdehau/tui-rs/issues/626
260
- self . items . set_items ( 0 , commits, & None ) ;
231
+ self . items . set_items ( new_offset, commits, & None ) ;
261
232
}
262
233
263
- self . table_state . set ( table_state) ;
264
234
self . count_total = git_log. count ( ) ?;
265
235
}
266
236
@@ -273,7 +243,10 @@ impl FileRevlogComponent {
273
243
let commit_id = table_state. selected ( ) . and_then ( |selected| {
274
244
self . items
275
245
. iter ( )
276
- . nth ( selected)
246
+ . nth (
247
+ selected
248
+ . saturating_sub ( self . items . index_offset ( ) ) ,
249
+ )
277
250
. as_ref ( )
278
251
. map ( |entry| entry. id )
279
252
} ) ;
@@ -345,10 +318,12 @@ impl FileRevlogComponent {
345
318
} )
346
319
}
347
320
348
- fn move_selection ( & mut self , scroll_type : ScrollType ) -> bool {
349
- let mut table_state = self . table_state . take ( ) ;
350
-
351
- let old_selection = table_state. selected ( ) . unwrap_or ( 0 ) ;
321
+ fn move_selection (
322
+ & mut self ,
323
+ scroll_type : ScrollType ,
324
+ ) -> Result < ( ) > {
325
+ let old_selection =
326
+ self . table_state . get_mut ( ) . selected ( ) . unwrap_or ( 0 ) ;
352
327
let max_selection = self . get_max_selection ( ) ;
353
328
let height_in_items = self . current_height . get ( ) / 2 ;
354
329
@@ -372,20 +347,40 @@ impl FileRevlogComponent {
372
347
self . queue . push ( InternalEvent :: Update ( NeedsUpdate :: DIFF ) ) ;
373
348
}
374
349
375
- table_state . select ( Some ( new_selection) ) ;
376
- self . table_state . set ( table_state ) ;
350
+ self . set_selection ( new_selection) ;
351
+ self . fetch_commits_if_needed ( ) ? ;
377
352
378
- needs_update
353
+ Ok ( ( ) )
354
+ }
355
+
356
+ fn set_selection ( & mut self , selection : usize ) {
357
+ let height_in_items =
358
+ ( self . current_height . get ( ) . saturating_sub ( 2 ) ) / 2 ;
359
+
360
+ let offset = * self . table_state . get_mut ( ) . offset_mut ( ) ;
361
+ let min_offset = selection
362
+ . saturating_sub ( height_in_items. saturating_sub ( 1 ) ) ;
363
+
364
+ let new_offset = offset. clamp ( min_offset, selection) ;
365
+
366
+ * self . table_state . get_mut ( ) . offset_mut ( ) = new_offset;
367
+ self . table_state . get_mut ( ) . select ( Some ( selection) ) ;
379
368
}
380
369
381
- fn set_open_selection ( & mut self ) {
382
- if let Some ( selection) =
383
- self . open_request . as_ref ( ) . and_then ( |req| req. selection )
384
- {
385
- let mut table_state = self . table_state . take ( ) ;
386
- table_state. select ( Some ( selection) ) ;
387
- self . table_state . set ( table_state) ;
370
+ fn fetch_commits_if_needed ( & mut self ) -> Result < ( ) > {
371
+ let selection =
372
+ self . table_state . get_mut ( ) . selected ( ) . unwrap_or ( 0 ) ;
373
+ let offset = * self . table_state . get_mut ( ) . offset_mut ( ) ;
374
+ let height_in_items =
375
+ ( self . current_height . get ( ) . saturating_sub ( 2 ) ) / 2 ;
376
+ let new_max_offset =
377
+ selection. saturating_add ( height_in_items) ;
378
+
379
+ if self . items . needs_data ( offset, new_max_offset) {
380
+ self . fetch_commits ( offset, new_max_offset) ?;
388
381
}
382
+
383
+ Ok ( ( ) )
389
384
}
390
385
391
386
fn get_selection ( & self ) -> Option < usize > {
@@ -423,10 +418,32 @@ impl FileRevlogComponent {
423
418
. border_style ( self . theme . block ( true ) ) ,
424
419
) ;
425
420
426
- let mut table_state = self . table_state . take ( ) ;
421
+ let table_state = self . table_state . take ( ) ;
422
+ // We have to adjust the table state for drawing to account for the fact
423
+ // that `self.items` not necessarily starts at index 0.
424
+ //
425
+ // When a user scrolls down, items outside of the current view are removed
426
+ // when new data is fetched. Let’s have a look at an example: if the item at
427
+ // index 50 is the first item in the current view and `self.items` has been
428
+ // freshly fetched, the current offset is 50 and `self.items[0]` is the item
429
+ // at index 50. Subtracting the current offset from the selected index
430
+ // yields the correct index in `self.items`, in this case 0.
431
+ let mut adjusted_table_state = TableState :: default ( )
432
+ . with_selected ( table_state. selected ( ) . map ( |selected| {
433
+ selected. saturating_sub ( self . items . index_offset ( ) )
434
+ } ) )
435
+ . with_offset (
436
+ table_state
437
+ . offset ( )
438
+ . saturating_sub ( self . items . index_offset ( ) ) ,
439
+ ) ;
427
440
428
441
f. render_widget ( Clear , area) ;
429
- f. render_stateful_widget ( table, area, & mut table_state) ;
442
+ f. render_stateful_widget (
443
+ table,
444
+ area,
445
+ & mut adjusted_table_state,
446
+ ) ;
430
447
431
448
draw_scrollbar (
432
449
f,
@@ -545,36 +562,36 @@ impl Component for FileRevlogComponent {
545
562
}
546
563
} else if key_match ( key, self . key_config . keys . move_up )
547
564
{
548
- self . move_selection ( ScrollType :: Up ) ;
565
+ self . move_selection ( ScrollType :: Up ) ? ;
549
566
} else if key_match (
550
567
key,
551
568
self . key_config . keys . move_down ,
552
569
) {
553
- self . move_selection ( ScrollType :: Down ) ;
570
+ self . move_selection ( ScrollType :: Down ) ? ;
554
571
} else if key_match (
555
572
key,
556
573
self . key_config . keys . shift_up ,
557
574
) || key_match (
558
575
key,
559
576
self . key_config . keys . home ,
560
577
) {
561
- self . move_selection ( ScrollType :: Home ) ;
578
+ self . move_selection ( ScrollType :: Home ) ? ;
562
579
} else if key_match (
563
580
key,
564
581
self . key_config . keys . shift_down ,
565
582
) || key_match (
566
583
key,
567
584
self . key_config . keys . end ,
568
585
) {
569
- self . move_selection ( ScrollType :: End ) ;
586
+ self . move_selection ( ScrollType :: End ) ? ;
570
587
} else if key_match ( key, self . key_config . keys . page_up )
571
588
{
572
- self . move_selection ( ScrollType :: PageUp ) ;
589
+ self . move_selection ( ScrollType :: PageUp ) ? ;
573
590
} else if key_match (
574
591
key,
575
592
self . key_config . keys . page_down ,
576
593
) {
577
- self . move_selection ( ScrollType :: PageDown ) ;
594
+ self . move_selection ( ScrollType :: PageDown ) ? ;
578
595
}
579
596
}
580
597
0 commit comments