Skip to content

Commit fbb0fe1

Browse files
committed
diff: implement horizontal scrolling
converting tabs to spaces had to be moved from after the formatting to before it because `trim_offset` seems to remove leading tabs from the output string (not the input string) for some reason. calculating the longtest line is cached to prevent a theoretical decrease in performance.
1 parent 4ce1948 commit fbb0fe1

File tree

2 files changed

+62
-4
lines changed

2 files changed

+62
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2828
* switch focus to index after staging last file ([#1169](https://github.com/extrawurst/gitui/pull/1169))
2929
* fix stashlist multi marking not updated after dropping ([#1207](https://github.com/extrawurst/gitui/pull/1207))
3030
* exact matches have a higher priority and are placed to the top of the list when fuzzily finding files ([#1183](https://github.com/extrawurst/gitui/pull/1183))
31+
* support horizontal scrolling in diff view ([#1017](https://github.com/extrawurst/gitui/issues/1017))
3132

3233
## [0.20.1] - 2021-01-26
3334

src/components/diff.rs

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ use crate::{
77
keys::SharedKeyConfig,
88
queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem},
99
string_utils::tabs_to_spaces,
10+
string_utils::trim_offset,
1011
strings, try_or_popup,
11-
ui::style::SharedTheme,
12+
ui::{draw_scrollbar, style::SharedTheme, Orientation},
1213
};
1314
use anyhow::Result;
1415
use asyncgit::{
@@ -102,6 +103,7 @@ impl Selection {
102103
pub struct DiffComponent {
103104
repo: RepoPathRef,
104105
diff: Option<FileDiff>,
106+
longest_line: usize,
105107
pending: bool,
106108
selection: Selection,
107109
selected_hunk: Option<usize>,
@@ -113,6 +115,7 @@ pub struct DiffComponent {
113115
theme: SharedTheme,
114116
key_config: SharedKeyConfig,
115117
is_immutable: bool,
118+
scrolled_right: usize,
116119
}
117120

118121
impl DiffComponent {
@@ -131,13 +134,15 @@ impl DiffComponent {
131134
pending: false,
132135
selected_hunk: None,
133136
diff: None,
137+
longest_line: 0,
134138
current_size: Cell::new((0, 0)),
135139
selection: Selection::Single(0),
136140
scroll: VerticalScroll::new(),
137141
theme,
138142
key_config,
139143
is_immutable,
140144
repo,
145+
scrolled_right: 0,
141146
}
142147
}
143148
///
@@ -155,10 +160,12 @@ impl DiffComponent {
155160
pub fn clear(&mut self, pending: bool) {
156161
self.current = Current::default();
157162
self.diff = None;
163+
self.longest_line = 0;
158164
self.scroll.reset();
159165
self.selection = Selection::Single(0);
160166
self.selected_hunk = None;
161167
self.pending = pending;
168+
self.scrolled_right = 0;
162169
}
163170
///
164171
pub fn update(
@@ -182,6 +189,24 @@ impl DiffComponent {
182189

183190
self.diff = Some(diff);
184191

192+
self.longest_line = self
193+
.diff
194+
.as_ref()
195+
.map(|diff| {
196+
diff.hunks
197+
.iter()
198+
.map(|hunk| {
199+
hunk.lines
200+
.iter()
201+
.map(|line| line.content.len())
202+
.max()
203+
.unwrap_or(0)
204+
})
205+
.max()
206+
.unwrap_or(0)
207+
})
208+
.unwrap_or(0);
209+
185210
if reset_selection {
186211
self.scroll.reset();
187212
self.selection = Selection::Single(0);
@@ -241,6 +266,11 @@ impl DiffComponent {
241266
self.diff.as_ref().map_or(0, |diff| diff.lines)
242267
}
243268

269+
fn max_scroll_right(&self) -> usize {
270+
self.longest_line
271+
.saturating_sub(self.current_size.get().0.into())
272+
}
273+
244274
fn modify_selection(&mut self, direction: Direction) {
245275
if self.diff.is_some() {
246276
self.selection.modify(direction, self.lines_count());
@@ -379,6 +409,7 @@ impl DiffComponent {
379409
hunk_selected,
380410
i == hunk_len as usize - 1,
381411
&self.theme,
412+
self.scrolled_right,
382413
));
383414
lines_added += 1;
384415
}
@@ -401,6 +432,7 @@ impl DiffComponent {
401432
selected_hunk: bool,
402433
end_of_hunk: bool,
403434
theme: &SharedTheme,
435+
scrolled_right: usize,
404436
) -> Spans<'a> {
405437
let style = theme.diff_hunk_marker(selected_hunk);
406438

@@ -419,18 +451,22 @@ impl DiffComponent {
419451
}
420452
};
421453

454+
let content =
455+
tabs_to_spaces(line.content.as_ref().to_string());
456+
let content = trim_offset(&content, scrolled_right);
457+
422458
let filled = if selected {
423459
// selected line
424-
format!("{:w$}\n", line.content, w = width as usize)
460+
format!("{:w$}\n", content, w = width as usize)
425461
} else {
426462
// weird eof missing eol line
427-
format!("{}\n", line.content)
463+
format!("{}\n", content)
428464
};
429465

430466
Spans::from(vec![
431467
left_side_of_line,
432468
Span::styled(
433-
Cow::from(tabs_to_spaces(filled)),
469+
Cow::from(filled),
434470
theme.diff_line(line.line_type, selected),
435471
),
436472
])
@@ -645,6 +681,17 @@ impl DrawableComponent for DiffComponent {
645681

646682
if self.focused() {
647683
self.scroll.draw(f, r, &self.theme);
684+
685+
if self.scrolled_right != 0 {
686+
draw_scrollbar(
687+
f,
688+
r,
689+
&self.theme,
690+
self.max_scroll_right(),
691+
self.scrolled_right,
692+
Orientation::Horizontal,
693+
);
694+
}
648695
}
649696

650697
Ok(())
@@ -749,6 +796,16 @@ impl Component for DiffComponent {
749796
} else if e == self.key_config.keys.page_down {
750797
self.move_selection(ScrollType::PageDown);
751798
Ok(EventState::Consumed)
799+
} else if e == self.key_config.keys.move_right {
800+
if self.scrolled_right < self.max_scroll_right() {
801+
self.scrolled_right += 1;
802+
}
803+
Ok(EventState::Consumed)
804+
} else if e == self.key_config.keys.move_left
805+
&& self.scrolled_right > 0
806+
{
807+
self.scrolled_right -= 1;
808+
Ok(EventState::Consumed)
752809
} else if e == self.key_config.keys.stage_unstage_item
753810
&& !self.is_immutable
754811
{

0 commit comments

Comments
 (0)