@@ -21,14 +21,14 @@ use std::{
2121};
2222
2323use crate::ui::{Prompt, PromptEvent};
24−use helix_core::Position;
24+use helix_core::{movement::Direction, Position};
2525use helix_view::{
2626 editor::Action,
2727 graphics::{Color, CursorKind, Margin, Rect, Style},
2828 Document, Editor,
2929};
3030
31−pub const MIN_SCREEN_WIDTH_FOR_PREVIEW: u16 = 80;
31+pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72;
3232/// Biggest file size to preview in bytes
3333pub const MAX_FILE_SIZE_FOR_PREVIEW: u64 = 10 * 1024 * 1024;
3434
@@ -90,7 +90,7 @@ impl<T> FilePicker<T> {
9090 preview_fn: impl Fn(&Editor, &T) -> Option<FileLocation> + 'static,
9191 ) -> Self {
9292 Self {
93− picker: Picker::new(false, options, format_fn, callback_fn),
93+ picker: Picker::new(options, format_fn, callback_fn),
9494 truncate_start: true,
9595 preview_cache: HashMap::new(),
9696 read_buffer: Vec::with_capacity(1024),
@@ -160,8 +160,7 @@ impl<T: 'static> Component for FilePicker<T> {
160160 // | | | |
161161 // +---------+ +---------+
162162
163− let render_preview = area.width > MIN_SCREEN_WIDTH_FOR_PREVIEW;
164− let area = inner_rect(area);
163+ let render_preview = area.width > MIN_AREA_WIDTH_FOR_PREVIEW;
165164 // -- Render the frame:
166165 // clear area
167166 let background = cx.editor.theme.get("ui.background");
@@ -260,6 +259,16 @@ impl<T: 'static> Component for FilePicker<T> {
260259 fn cursor(&self, area: Rect, ctx: &Editor) -> (Option<Position>, CursorKind) {
261260 self.picker.cursor(area, ctx)
262261 }
262+
263+ fn required_size(&mut self, (width, height): (u16, u16)) -> Option<(u16, u16)> {
264+ let picker_width = if width > MIN_AREA_WIDTH_FOR_PREVIEW {
265+ width / 2
266+ } else {
267+ width
268+ };
269+ self.picker.required_size((picker_width, height))?;
270+ Some((width, height))
271+ }
263272}
264273
265274pub struct Picker<T> {
@@ -271,11 +280,12 @@ pub struct Picker<T> {
271280 /// Filter over original options.
272281 filters: Vec<usize>, // could be optimized into bit but not worth it now
273282
283+ /// Current height of the completions box
284+ completion_height: u16,
285+
274286 cursor: usize,
275287 // pattern: String,
276288 prompt: Prompt,
277− /// Whether to render in the middle of the area
278− render_centered: bool,
279289 /// Wheather to truncate the start (default true)
280290 pub truncate_start: bool,
281291
@@ -285,7 +295,6 @@ pub struct Picker<T> {
285295
286296impl<T> Picker<T> {
287297 pub fn new(
288− render_centered: bool,
289298 options: Vec<T>,
290299 format_fn: impl Fn(&T) -> Cow<str> + 'static,
291300 callback_fn: impl Fn(&mut Context, &T, Action) + 'static,
@@ -306,10 +315,10 @@ impl<T> Picker<T> {
306315 filters: Vec::new(),
307316 cursor: 0,
308317 prompt,
309− render_centered,
310318 truncate_start: true,
311319 format_fn: Box::new(format_fn),
312320 callback_fn: Box::new(callback_fn),
321+ completion_height: 0,
313322 };
314323
315324 // TODO: scoring on empty input should just use a fastpath
@@ -346,22 +355,38 @@ impl<T> Picker<T> {
346355 self.cursor = 0;
347356 }
348357
349− pub fn move_up(&mut self) {
350− if self.matches.is_empty() {
351− return;
352− }
358+ /// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`)
359+ pub fn move_by(&mut self, amount: usize, direction: Direction) {
353360 let len = self.matches.len();
354− let pos = ((self.cursor + len.saturating_sub(1)) % len) % len;
355− self.cursor = pos;
356− }
357361
358− pub fn move_down(&mut self) {
359− if self.matches.is_empty() {
360− return;
362+ match direction {
363+ Direction::Forward => {
364+ self.cursor = self.cursor.saturating_add(amount) % len;
365+ }
366+ Direction::Backward => {
367+ self.cursor = self.cursor.saturating_add(len).saturating_sub(amount) % len;
368+ }
361369 }
362− let len = self.matches.len();
363− let pos = (self.cursor + 1) % len;
364− self.cursor = pos;
370+ }
371+
372+ /// Move the cursor down by exactly one page. After the last page comes the first page.
373+ pub fn page_up(&mut self) {
374+ self.move_by(self.completion_height as usize, Direction::Backward);
375+ }
376+
377+ /// Move the cursor up by exactly one page. After the first page comes the last page.
378+ pub fn page_down(&mut self) {
379+ self.move_by(self.completion_height as usize, Direction::Forward);
380+ }
381+
382+ /// Move the cursor to the first entry
383+ pub fn to_start(&mut self) {
384+ self.cursor = 0;
385+ }
386+
387+ /// Move the cursor to the last entry
388+ pub fn to_end(&mut self) {
389+ self.cursor = self.matches.len().saturating_sub(1);
365390 }
366391
367392 pub fn selection(&self) -> Option<&T> {
@@ -384,23 +409,10 @@ impl<T> Picker<T> {
384409// - on input change:
385410// - score all the names in relation to input
386411
387−fn inner_rect(area: Rect) -> Rect {
388− let margin = Margin {
389− vertical: area.height * 10 / 100,
390− horizontal: area.width * 10 / 100,
391− };
392− area.inner(&margin)
393−}
394−
395412impl<T: 'static> Component for Picker<T> {
396413 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
397− let max_width = 50.min(viewport.0);
398− let max_height = 10.min(viewport.1.saturating_sub(2)); // add some spacing in the viewport
399−
400− let height = (self.options.len() as u16 + 4) // add some spacing for input + padding
401− .min(max_height);
402− let width = max_width;
403− Some((width, height))
414+ self.completion_height = viewport.1.saturating_sub(4);
415+ Some(viewport)
404416 }
405417
406418 fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
@@ -417,10 +429,22 @@ impl<T: 'static> Component for Picker<T> {
417429
418430 match key_event.into() {
419431 shift!(Tab) | key!(Up) | ctrl!('p') | ctrl!('k') => {
420− self.move_up();
432+ self.move_by(1, Direction::Backward);
421433 }
422434 key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => {
423− self.move_down();
435+ self.move_by(1, Direction::Forward);
436+ }
437+ key!(PageDown) | ctrl!('f') => {
438+ self.page_down();
439+ }
440+ key!(PageUp) | ctrl!('b') => {
441+ self.page_up();
442+ }
443+ key!(Home) => {
444+ self.to_start();
445+ }
446+ key!(End) => {
447+ self.to_end();
424448 }
425449 key!(Esc) | ctrl!('c') => {
426450 return close_fn;
@@ -458,12 +482,6 @@ impl<T: 'static> Component for Picker<T> {
458482 }
459483
460484 fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
461− let area = if self.render_centered {
462− inner_rect(area)
463− } else {
464− area
465− };
466−
467485 let text_style = cx.editor.theme.get("ui.text");
468486
469487 // -- Render the frame:
@@ -538,8 +556,6 @@ impl<T: 'static> Component for Picker<T> {
538556 }
539557
540558 fn cursor(&self, area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
541− // TODO: this is mostly duplicate code
542− let area = inner_rect(area);
543559 let block = Block::default().borders(Borders::ALL);
544560 // calculate the inner area inside the box
545561 let inner = block.inner(area);