Skip to content

Commit e5db7a9

Browse files
author
Rock Boynton
committed
patchy: auto-merge pull request helix-editor#11441
`patchy` is a tool which makes it easy to declaratively manage personal forks by automatically merging pull requests. Check it out here: https://github.com/NikitaRevenco/patchy
2 parents 91a32f9 + eda5aa1 commit e5db7a9

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
@@ -268,6 +268,10 @@ pub struct Picker<T: 'static + Send + Sync, D: 'static> {
268268
/// An event handler for syntax highlighting the currently previewed file.
269269
preview_highlight_handler: Sender<Arc<Path>>,
270270
dynamic_query_handler: Option<Sender<DynamicQueryChange>>,
271+
272+
preview_scroll_offset: (Direction, usize),
273+
preview_height: u16,
274+
cursor_picker: u32,
271275
}
272276

273277
impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
@@ -389,6 +393,9 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
389393
file_fn: None,
390394
preview_highlight_handler: PreviewHighlightHandler::<T, D>::default().spawn(),
391395
dynamic_query_handler: None,
396+
preview_scroll_offset: (Direction::Forward, 0),
397+
preview_height: 0,
398+
cursor_picker: 0,
392399
}
393400
}
394401

@@ -440,6 +447,44 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
440447
self
441448
}
442449

450+
/// Moves the picker file preview by a number of lines, either down (`Forward`) or up (`Backward`)
451+
fn move_preview_by(&mut self, amount: usize, move_direction: Direction) {
452+
let (current_scroll_direction, current_scroll_offset) = self.preview_scroll_offset;
453+
454+
match move_direction {
455+
Direction::Backward => match current_scroll_direction {
456+
Direction::Backward => {
457+
self.preview_scroll_offset.1 = current_scroll_offset.saturating_add(amount);
458+
}
459+
Direction::Forward => {
460+
if let Some(change) = current_scroll_offset.checked_sub(amount) {
461+
self.preview_scroll_offset.1 = change;
462+
} else {
463+
self.preview_scroll_offset = (
464+
Direction::Backward,
465+
amount.saturating_sub(current_scroll_offset),
466+
);
467+
}
468+
}
469+
},
470+
Direction::Forward => match current_scroll_direction {
471+
Direction::Backward => {
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::Forward,
477+
amount.saturating_sub(current_scroll_offset),
478+
);
479+
}
480+
}
481+
Direction::Forward => {
482+
self.preview_scroll_offset.1 = current_scroll_offset.saturating_add(amount);
483+
}
484+
},
485+
};
486+
}
487+
443488
/// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`)
444489
pub fn move_by(&mut self, amount: u32, direction: Direction) {
445490
let len = self.matcher.snapshot().matched_item_count();
@@ -872,6 +917,15 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
872917
let inner = inner.inner(margin);
873918
BLOCK.render(area, surface);
874919

920+
let mut preview_scroll_offset = self.preview_scroll_offset;
921+
922+
// Reset preview scroll if cursor moved
923+
let cursor_position = self.cursor_picker;
924+
if self.cursor != cursor_position {
925+
preview_scroll_offset = (Direction::Forward, 0);
926+
self.cursor_picker = self.cursor;
927+
}
928+
875929
if let Some((preview, range)) = self.get_preview(cx.editor) {
876930
let doc = match preview.document() {
877931
Some(doc)
@@ -905,6 +959,7 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
905959
return;
906960
}
907961
};
962+
let doc_height = doc.text().len_lines();
908963

909964
let mut offset = ViewPosition::default();
910965
if let Some((start_line, end_line)) = range {
@@ -933,6 +988,28 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
933988
}
934989
}
935990

991+
let mut current_line = doc.text().slice(..).char_to_line(offset.anchor);
992+
993+
preview_scroll_offset.1 = match preview_scroll_offset.0 {
994+
Direction::Backward => preview_scroll_offset.1.min(current_line),
995+
Direction::Forward => preview_scroll_offset.1.min(
996+
doc_height
997+
.saturating_sub(current_line)
998+
.saturating_sub(inner.height as usize),
999+
),
1000+
};
1001+
1002+
offset.anchor = match preview_scroll_offset.0 {
1003+
Direction::Backward => doc
1004+
.text()
1005+
.slice(..)
1006+
.line_to_char(current_line.saturating_sub(preview_scroll_offset.1)),
1007+
Direction::Forward => doc
1008+
.text()
1009+
.slice(..)
1010+
.line_to_char(current_line.saturating_add(preview_scroll_offset.1)),
1011+
};
1012+
9361013
let syntax_highlights = EditorView::doc_syntax_highlights(
9371014
doc,
9381015
offset.anchor,
@@ -970,6 +1047,8 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
9701047
decorations.add_decoration(draw_highlight);
9711048
}
9721049

1050+
current_line = doc.text().slice(..).char_to_line(offset.anchor);
1051+
9731052
render_document(
9741053
surface,
9751054
inner,
@@ -982,6 +1061,39 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
9821061
&cx.editor.theme,
9831062
decorations,
9841063
);
1064+
1065+
self.preview_scroll_offset = preview_scroll_offset;
1066+
1067+
let win_height = inner.height as usize;
1068+
let len = doc_height;
1069+
let fits = len <= win_height;
1070+
let scroll = current_line;
1071+
let scroll_style = cx.editor.theme.get("ui.menu.scroll");
1072+
1073+
const fn div_ceil(a: usize, b: usize) -> usize {
1074+
(a + b - 1) / b
1075+
}
1076+
1077+
if !fits {
1078+
let scroll_height = div_ceil(win_height.pow(2), len).min(win_height);
1079+
let scroll_line = (win_height - scroll_height) * scroll
1080+
/ std::cmp::max(1, len.saturating_sub(win_height));
1081+
1082+
let mut cell;
1083+
for i in 0..win_height {
1084+
cell = &mut surface[(inner.right() - 1, inner.top() + i as u16)];
1085+
1086+
cell.set_symbol("▐"); // right half block
1087+
1088+
if scroll_line <= i && i < scroll_line + scroll_height {
1089+
// Draw scroll thumb
1090+
cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
1091+
} else {
1092+
// Draw scroll track
1093+
cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
1094+
}
1095+
}
1096+
}
9851097
}
9861098
}
9871099
}
@@ -1053,10 +1165,10 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
10531165
key!(Tab) | key!(Down) | ctrl!('n') => {
10541166
self.move_by(1, Direction::Forward);
10551167
}
1056-
key!(PageDown) | ctrl!('d') => {
1168+
key!(PageDown) | ctrl!('d') if !self.show_preview => {
10571169
self.page_down();
10581170
}
1059-
key!(PageUp) | ctrl!('u') => {
1171+
key!(PageUp) | ctrl!('u') if !self.show_preview => {
10601172
self.page_up();
10611173
}
10621174
key!(Home) => {
@@ -1121,6 +1233,36 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
11211233
ctrl!('t') => {
11221234
self.toggle_preview();
11231235
}
1236+
alt!('k') | shift!(Up) if self.show_preview => {
1237+
self.move_preview_by(
1238+
ctx.editor.config().scroll_lines.unsigned_abs(),
1239+
Direction::Backward,
1240+
);
1241+
}
1242+
alt!('j') | shift!(Down) if self.show_preview => {
1243+
self.move_preview_by(
1244+
ctx.editor.config().scroll_lines.unsigned_abs(),
1245+
Direction::Forward,
1246+
);
1247+
}
1248+
alt!('u') if self.show_preview => {
1249+
self.move_preview_by(
1250+
self.preview_height.saturating_div(2) as usize,
1251+
Direction::Backward,
1252+
);
1253+
}
1254+
alt!('d') if self.show_preview => {
1255+
self.move_preview_by(
1256+
self.preview_height.saturating_div(2) as usize,
1257+
Direction::Forward,
1258+
);
1259+
}
1260+
key!(PageUp) | alt!('b') if self.show_preview => {
1261+
self.move_preview_by(self.preview_height as usize, Direction::Backward);
1262+
}
1263+
key!(PageDown) | alt!('f') if self.show_preview => {
1264+
self.move_preview_by(self.preview_height as usize, Direction::Forward);
1265+
}
11241266
_ => {
11251267
self.prompt_handle_event(event, ctx);
11261268
}
@@ -1150,6 +1292,8 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
11501292

11511293
fn required_size(&mut self, (width, height): (u16, u16)) -> Option<(u16, u16)> {
11521294
self.completion_height = height.saturating_sub(4 + self.header_height());
1295+
self.preview_height = height.saturating_sub(2);
1296+
11531297
Some((width, height))
11541298
}
11551299

0 commit comments

Comments
 (0)