Skip to content

Commit 2a1d44f

Browse files
qiu-xGNUSheepManosmer
committed
feat: Redone preview scrolling from #4189
Co-authored-by: GNUSheep <zydekpiotr26@gmail.com> Co-authored-by: Manos Mertzianis <manosmertzianis@gmail.com>
1 parent 68f11f9 commit 2a1d44f

File tree

1 file changed

+146
-2
lines changed

1 file changed

+146
-2
lines changed

helix-term/src/ui/picker.rs

Lines changed: 146 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,10 @@ pub struct Picker<T: 'static + Send + Sync, D: 'static> {
269269
/// An event handler for syntax highlighting the currently previewed file.
270270
preview_highlight_handler: Sender<Arc<Path>>,
271271
dynamic_query_handler: Option<Sender<DynamicQueryChange>>,
272+
273+
preview_scroll_offset: (Direction, usize),
274+
preview_height: u16,
275+
cursor_picker: u32,
272276
}
273277

274278
impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
@@ -391,6 +395,9 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
391395
file_fn: None,
392396
preview_highlight_handler: PreviewHighlightHandler::<T, D>::default().spawn(),
393397
dynamic_query_handler: None,
398+
preview_scroll_offset: (Direction::Forward, 0),
399+
preview_height: 0,
400+
cursor_picker: 0,
394401
}
395402
}
396403

@@ -452,6 +459,44 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
452459
self
453460
}
454461

462+
/// Moves the picker file preview by a number of lines, either down (`Forward`) or up (`Backward`)
463+
fn move_preview_by(&mut self, amount: usize, move_direction: Direction) {
464+
let (current_scroll_direction, current_scroll_offset) = self.preview_scroll_offset;
465+
466+
match move_direction {
467+
Direction::Backward => match current_scroll_direction {
468+
Direction::Backward => {
469+
self.preview_scroll_offset.1 = current_scroll_offset.saturating_add(amount);
470+
}
471+
Direction::Forward => {
472+
if let Some(change) = current_scroll_offset.checked_sub(amount) {
473+
self.preview_scroll_offset.1 = change;
474+
} else {
475+
self.preview_scroll_offset = (
476+
Direction::Backward,
477+
amount.saturating_sub(current_scroll_offset),
478+
);
479+
}
480+
}
481+
},
482+
Direction::Forward => match current_scroll_direction {
483+
Direction::Backward => {
484+
if let Some(change) = current_scroll_offset.checked_sub(amount) {
485+
self.preview_scroll_offset.1 = change;
486+
} else {
487+
self.preview_scroll_offset = (
488+
Direction::Forward,
489+
amount.saturating_sub(current_scroll_offset),
490+
);
491+
}
492+
}
493+
Direction::Forward => {
494+
self.preview_scroll_offset.1 = current_scroll_offset.saturating_add(amount);
495+
}
496+
},
497+
};
498+
}
499+
455500
/// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`)
456501
pub fn move_by(&mut self, amount: u32, direction: Direction) {
457502
let len = self.matcher.snapshot().matched_item_count();
@@ -890,6 +935,15 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
890935
let inner = inner.inner(margin);
891936
BLOCK.render(area, surface);
892937

938+
let mut preview_scroll_offset = self.preview_scroll_offset;
939+
940+
// Reset preview scroll if cursor moved
941+
let cursor_position = self.cursor_picker;
942+
if self.cursor != cursor_position {
943+
preview_scroll_offset = (Direction::Forward, 0);
944+
self.cursor_picker = self.cursor;
945+
}
946+
893947
if let Some((preview, range)) = self.get_preview(cx.editor) {
894948
let doc = match preview.document() {
895949
Some(doc)
@@ -923,6 +977,7 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
923977
return;
924978
}
925979
};
980+
let doc_height = doc.text().len_lines();
926981

927982
let mut offset = ViewPosition::default();
928983
if let Some((start_line, end_line)) = range {
@@ -951,6 +1006,28 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
9511006
}
9521007
}
9531008

1009+
let mut current_line = doc.text().slice(..).char_to_line(offset.anchor);
1010+
1011+
preview_scroll_offset.1 = match preview_scroll_offset.0 {
1012+
Direction::Backward => preview_scroll_offset.1.min(current_line),
1013+
Direction::Forward => preview_scroll_offset.1.min(
1014+
doc_height
1015+
.saturating_sub(current_line)
1016+
.saturating_sub(inner.height as usize),
1017+
),
1018+
};
1019+
1020+
offset.anchor = match preview_scroll_offset.0 {
1021+
Direction::Backward => doc
1022+
.text()
1023+
.slice(..)
1024+
.line_to_char(current_line.saturating_sub(preview_scroll_offset.1)),
1025+
Direction::Forward => doc
1026+
.text()
1027+
.slice(..)
1028+
.line_to_char(current_line.saturating_add(preview_scroll_offset.1)),
1029+
};
1030+
9541031
let loader = cx.editor.syn_loader.load();
9551032

9561033
let syntax_highlighter =
@@ -985,6 +1062,8 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
9851062
decorations.add_decoration(draw_highlight);
9861063
}
9871064

1065+
current_line = doc.text().slice(..).char_to_line(offset.anchor);
1066+
9881067
render_document(
9891068
surface,
9901069
inner,
@@ -997,6 +1076,39 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
9971076
&cx.editor.theme,
9981077
decorations,
9991078
);
1079+
1080+
self.preview_scroll_offset = preview_scroll_offset;
1081+
1082+
let win_height = inner.height as usize;
1083+
let len = doc_height;
1084+
let fits = len <= win_height;
1085+
let scroll = current_line;
1086+
let scroll_style = cx.editor.theme.get("ui.menu.scroll");
1087+
1088+
const fn div_ceil(a: usize, b: usize) -> usize {
1089+
(a + b - 1) / b
1090+
}
1091+
1092+
if !fits {
1093+
let scroll_height = div_ceil(win_height.pow(2), len).min(win_height);
1094+
let scroll_line = (win_height - scroll_height) * scroll
1095+
/ std::cmp::max(1, len.saturating_sub(win_height));
1096+
1097+
let mut cell;
1098+
for i in 0..win_height {
1099+
cell = &mut surface[(inner.right() - 1, inner.top() + i as u16)];
1100+
1101+
cell.set_symbol("▐"); // right half block
1102+
1103+
if scroll_line <= i && i < scroll_line + scroll_height {
1104+
// Draw scroll thumb
1105+
cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
1106+
} else {
1107+
// Draw scroll track
1108+
cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
1109+
}
1110+
}
1111+
}
10001112
}
10011113
}
10021114
}
@@ -1068,10 +1180,10 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
10681180
key!(Tab) | key!(Down) | ctrl!('n') => {
10691181
self.move_by(1, Direction::Forward);
10701182
}
1071-
key!(PageDown) | ctrl!('d') => {
1183+
key!(PageDown) | ctrl!('d') if !self.show_preview => {
10721184
self.page_down();
10731185
}
1074-
key!(PageUp) | ctrl!('u') => {
1186+
key!(PageUp) | ctrl!('u') if !self.show_preview => {
10751187
self.page_up();
10761188
}
10771189
key!(Home) => {
@@ -1136,6 +1248,36 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
11361248
ctrl!('t') => {
11371249
self.toggle_preview();
11381250
}
1251+
alt!('k') | shift!(Up) if self.show_preview => {
1252+
self.move_preview_by(
1253+
ctx.editor.config().scroll_lines.unsigned_abs(),
1254+
Direction::Backward,
1255+
);
1256+
}
1257+
alt!('j') | shift!(Down) if self.show_preview => {
1258+
self.move_preview_by(
1259+
ctx.editor.config().scroll_lines.unsigned_abs(),
1260+
Direction::Forward,
1261+
);
1262+
}
1263+
alt!('u') if self.show_preview => {
1264+
self.move_preview_by(
1265+
self.preview_height.saturating_div(2) as usize,
1266+
Direction::Backward,
1267+
);
1268+
}
1269+
alt!('d') if self.show_preview => {
1270+
self.move_preview_by(
1271+
self.preview_height.saturating_div(2) as usize,
1272+
Direction::Forward,
1273+
);
1274+
}
1275+
key!(PageUp) | alt!('b') if self.show_preview => {
1276+
self.move_preview_by(self.preview_height as usize, Direction::Backward);
1277+
}
1278+
key!(PageDown) | alt!('f') if self.show_preview => {
1279+
self.move_preview_by(self.preview_height as usize, Direction::Forward);
1280+
}
11391281
_ => {
11401282
self.prompt_handle_event(event, ctx);
11411283
}
@@ -1165,6 +1307,8 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
11651307

11661308
fn required_size(&mut self, (width, height): (u16, u16)) -> Option<(u16, u16)> {
11671309
self.completion_height = height.saturating_sub(4 + self.header_height());
1310+
self.preview_height = height.saturating_sub(2);
1311+
11681312
Some((width, height))
11691313
}
11701314

0 commit comments

Comments
 (0)