Skip to content

Commit b9c7637

Browse files
author
Stephan Dilly
committed
fix scroll being off in branchlist after tab
refactored scroll stuff into new abstraction for reuse
1 parent 5b07c48 commit b9c7637

File tree

5 files changed

+118
-26
lines changed

5 files changed

+118
-26
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ which = "4.1"
5555
[target.'cfg(not(windows))'.dependencies]
5656
pprof = { version = "0.4", features = ["flamegraph"], optional = true }
5757

58+
[dev-dependencies]
59+
pretty_assertions = "0.7"
60+
5861
[badges]
5962
maintenance = { status = "actively-developed" }
6063

@@ -81,4 +84,4 @@ codegen-units = 1
8184
opt-level = 3
8285

8386
[profile.dev]
84-
split-debuginfo = "unpacked"
87+
split-debuginfo = "unpacked"

src/components/branchlist.rs

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use super::{
2-
visibility_blocking, CommandBlocking, CommandInfo, Component,
3-
DrawableComponent, EventState,
2+
utils::scroll_vertical::VerticalScroll, visibility_blocking,
3+
CommandBlocking, CommandInfo, Component, DrawableComponent,
4+
EventState,
45
};
56
use crate::{
67
components::ScrollType,
78
keys::SharedKeyConfig,
89
queue::{Action, InternalEvent, NeedsUpdate, Queue},
910
strings, try_or_popup,
10-
ui::{self, calc_scroll_top, Size},
11+
ui::{self, Size},
1112
};
1213
use anyhow::Result;
1314
use asyncgit::{
@@ -37,7 +38,7 @@ pub struct BranchListComponent {
3738
local: bool,
3839
visible: bool,
3940
selection: u16,
40-
scroll_top: Cell<usize>,
41+
scroll: VerticalScroll,
4142
current_height: Cell<u16>,
4243
queue: Queue,
4344
theme: SharedTheme,
@@ -87,7 +88,6 @@ impl DrawableComponent for BranchListComponent {
8788
.split(area);
8889

8990
self.draw_tabs(f, chunks[0]);
90-
9191
self.draw_list(f, chunks[1])?;
9292
}
9393

@@ -280,7 +280,7 @@ impl BranchListComponent {
280280
local: true,
281281
visible: false,
282282
selection: 0,
283-
scroll_top: Cell::new(0),
283+
scroll: VerticalScroll::new(),
284284
queue,
285285
theme,
286286
key_config,
@@ -381,9 +381,13 @@ impl BranchListComponent {
381381
width_available: u16,
382382
height: usize,
383383
) -> Text {
384+
const UPSTREAM_SYMBOL: char = '\u{2191}';
385+
const HEAD_SYMBOL: char = '*';
386+
const EMPTY_SYMBOL: char = ' ';
387+
const THREE_DOTS: &str = "...";
384388
const COMMIT_HASH_LENGTH: usize = 8;
385389
const IS_HEAD_STAR_LENGTH: usize = 3; // "* "
386-
const THREE_DOTS_LENGTH: usize = 3; // "..."
390+
const THREE_DOTS_LENGTH: usize = THREE_DOTS.len(); // "..."
387391

388392
let branch_name_length: usize =
389393
width_available as usize * 40 / 100;
@@ -398,7 +402,7 @@ impl BranchListComponent {
398402
for (i, displaybranch) in self
399403
.branches
400404
.iter()
401-
.skip(self.scroll_top.get())
405+
.skip(self.scroll.get())
402406
.take(height)
403407
.enumerate()
404408
{
@@ -409,7 +413,7 @@ impl BranchListComponent {
409413
commit_message_length
410414
.saturating_sub(THREE_DOTS_LENGTH),
411415
);
412-
commit_message += "...";
416+
commit_message += THREE_DOTS;
413417
}
414418

415419
let mut branch_name = displaybranch.name.clone();
@@ -423,25 +427,26 @@ impl BranchListComponent {
423427
)
424428
.0
425429
.to_string();
426-
branch_name += "...";
430+
branch_name += THREE_DOTS;
427431
}
428432

429433
let selected =
430-
self.selection as usize - self.scroll_top.get() == i;
434+
(self.selection as usize - self.scroll.get()) == i;
431435

432436
let is_head = displaybranch
433437
.local_details()
434438
.map(|details| details.is_head)
435439
.unwrap_or_default();
436-
let is_head_str = if is_head { "*" } else { " " };
440+
let is_head_str =
441+
if is_head { HEAD_SYMBOL } else { EMPTY_SYMBOL };
437442
let has_upstream_str = if displaybranch
438443
.local_details()
439444
.map(|details| details.has_upstream)
440445
.unwrap_or_default()
441446
{
442-
"\u{2191}"
447+
UPSTREAM_SYMBOL
443448
} else {
444-
" "
449+
EMPTY_SYMBOL
445450
};
446451

447452
let span_prefix = Span::styled(
@@ -537,11 +542,11 @@ impl BranchListComponent {
537542
let height_in_lines = r.height as usize;
538543
self.current_height.set(height_in_lines.try_into()?);
539544

540-
self.scroll_top.set(calc_scroll_top(
541-
self.scroll_top.get(),
542-
height_in_lines,
545+
self.scroll.update(
543546
self.selection as usize,
544-
));
547+
self.branches.len(),
548+
height_in_lines,
549+
);
545550

546551
f.render_widget(
547552
Paragraph::new(self.get_text(
@@ -558,13 +563,7 @@ impl BranchListComponent {
558563
r.height += 2;
559564
r.y = r.y.saturating_sub(1);
560565

561-
ui::draw_scrollbar(
562-
f,
563-
r,
564-
&self.theme,
565-
self.branches.len().saturating_sub(height_in_lines),
566-
self.scroll_top.get(),
567-
);
566+
self.scroll.draw(f, r, &self.theme);
568567

569568
Ok(())
570569
}

src/components/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use unicode_width::UnicodeWidthStr;
33

44
pub mod filetree;
55
pub mod logitems;
6+
pub mod scroll_vertical;
67
pub mod statustree;
78

89
/// macro to simplify running code that might return Err.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use std::cell::Cell;
2+
3+
use tui::{backend::Backend, layout::Rect, Frame};
4+
5+
use crate::ui::{draw_scrollbar, style::SharedTheme};
6+
7+
pub struct VerticalScroll {
8+
top: Cell<usize>,
9+
max_top: Cell<usize>,
10+
}
11+
12+
impl VerticalScroll {
13+
pub const fn new() -> Self {
14+
Self {
15+
top: Cell::new(0),
16+
max_top: Cell::new(0),
17+
}
18+
}
19+
20+
pub fn get(&self) -> usize {
21+
self.top.get()
22+
}
23+
24+
pub fn update(
25+
&self,
26+
selection: usize,
27+
selection_max: usize,
28+
visual_height: usize,
29+
) -> usize {
30+
let new_top = calc_scroll_top(
31+
self.get(),
32+
visual_height,
33+
selection,
34+
selection_max,
35+
);
36+
self.top.set(new_top);
37+
38+
let new_max = selection_max.saturating_sub(visual_height);
39+
self.max_top.set(new_max);
40+
41+
new_top
42+
}
43+
44+
pub fn draw<B: Backend>(
45+
&self,
46+
f: &mut Frame<B>,
47+
r: Rect,
48+
theme: &SharedTheme,
49+
) {
50+
draw_scrollbar(
51+
f,
52+
r,
53+
theme,
54+
self.max_top.get(),
55+
self.top.get(),
56+
);
57+
}
58+
}
59+
60+
const fn calc_scroll_top(
61+
current_top: usize,
62+
height_in_lines: usize,
63+
selection: usize,
64+
selection_max: usize,
65+
) -> usize {
66+
if selection_max < height_in_lines {
67+
return 0;
68+
}
69+
70+
if current_top + height_in_lines <= selection {
71+
selection.saturating_sub(height_in_lines) + 1
72+
} else if current_top > selection {
73+
selection
74+
} else {
75+
current_top
76+
}
77+
}
78+
79+
#[cfg(test)]
80+
mod tests {
81+
use super::*;
82+
use pretty_assertions::assert_eq;
83+
84+
#[test]
85+
fn test_scroll_no_scroll_to_top() {
86+
assert_eq!(calc_scroll_top(1, 10, 4, 4), 0);
87+
}
88+
}

0 commit comments

Comments
 (0)