@@ -21,14 +21,14 @@ use std::{
23use crate::ui::{Prompt, PromptEvent};
23use crate::ui::{Prompt, PromptEvent};
24−use helix_core::Position;
24+use helix_core::{movement::Direction, Position};
25use helix_view::{
25use helix_view::{
26 editor::Action,
26 editor::Action,
27 graphics::{Color, CursorKind, Margin, Rect, Style},
27 graphics::{Color, CursorKind, Margin, Rect, Style},
28 Document, Editor,
28 Document, Editor,
31−pub const MIN_SCREEN_WIDTH_FOR_PREVIEW: u16 = 80;
31+pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72;
32/// Biggest file size to preview in bytes
32/// Biggest file size to preview in bytes
33pub const MAX_FILE_SIZE_FOR_PREVIEW: u64 = 10 * 1024 * 1024;
33pub const MAX_FILE_SIZE_FOR_PREVIEW: u64 = 10 * 1024 * 1024;
@@ -90,7 +90,7 @@ impl<T> FilePicker<T> {
90 preview_fn: impl Fn(&Editor, &T) -> Option<FileLocation> + 'static,
90 preview_fn: impl Fn(&Editor, &T) -> Option<FileLocation> + 'static,
91 ) -> Self {
91 ) -> Self {
93− picker: Picker::new(false, options, format_fn, callback_fn),
93+ picker: Picker::new(options, format_fn, callback_fn),
94 truncate_start: true,
94 truncate_start: true,
95 preview_cache: HashMap::new(),
95 preview_cache: HashMap::new(),
96 read_buffer: Vec::with_capacity(1024),
96 read_buffer: Vec::with_capacity(1024),
@@ -160,8 +160,7 @@ impl<T: 'static> Component for FilePicker<T> {
160 // | | | |
160 // | | | |
161 // +---------+ +---------+
161 // +---------+ +---------+
163− let render_preview = area.width > MIN_SCREEN_WIDTH_FOR_PREVIEW;
163+ let render_preview = area.width > MIN_AREA_WIDTH_FOR_PREVIEW;
164− let area = inner_rect(area);
165 // -- Render the frame:
164 // -- Render the frame:
166 // clear area
165 // clear area
167 let background = cx.editor.theme.get("ui.background");
166 let background = cx.editor.theme.get("ui.background");
@@ -260,6 +259,16 @@ impl<T: 'static> Component for FilePicker<T> {
260 fn cursor(&self, area: Rect, ctx: &Editor) -> (Option<Position>, CursorKind) {
259 fn cursor(&self, area: Rect, ctx: &Editor) -> (Option<Position>, CursorKind) {
261 self.picker.cursor(area, ctx)
260 self.picker.cursor(area, ctx)
263+ fn required_size(&mut self, (width, height): (u16, u16)) -> Option<(u16, u16)> {
264+ let picker_width = if width > MIN_AREA_WIDTH_FOR_PREVIEW {
269+ self.picker.required_size((picker_width, height))?;
270+ Some((width, height))
265pub struct Picker<T> {
274pub struct Picker<T> {
@@ -271,11 +280,12 @@ pub struct Picker<T> {
271 /// Filter over original options.
280 /// Filter over original options.
272 filters: Vec<usize>, // could be optimized into bit but not worth it now
281 filters: Vec<usize>, // could be optimized into bit but not worth it now
283+ /// Current height of the completions box
284+ completion_height: u16,
274 cursor: usize,
286 cursor: usize,
275 // pattern: String,
287 // pattern: String,
276 prompt: Prompt,
288 prompt: Prompt,
277− /// Whether to render in the middle of the area
278− render_centered: bool,
279 /// Wheather to truncate the start (default true)
289 /// Wheather to truncate the start (default true)
280 pub truncate_start: bool,
290 pub truncate_start: bool,
@@ -285,7 +295,6 @@ pub struct Picker<T> {
286impl<T> Picker<T> {
296impl<T> Picker<T> {
287 pub fn new(
297 pub fn new(
288− render_centered: bool,
289 options: Vec<T>,
298 options: Vec<T>,
290 format_fn: impl Fn(&T) -> Cow<str> + 'static,
299 format_fn: impl Fn(&T) -> Cow<str> + 'static,
291 callback_fn: impl Fn(&mut Context, &T, Action) + 'static,
300 callback_fn: impl Fn(&mut Context, &T, Action) + 'static,
@@ -306,10 +315,10 @@ impl<T> Picker<T> {
306 filters: Vec::new(),
315 filters: Vec::new(),
307 cursor: 0,
316 cursor: 0,
310 truncate_start: true,
318 truncate_start: true,
311 format_fn: Box::new(format_fn),
319 format_fn: Box::new(format_fn),
312 callback_fn: Box::new(callback_fn),
320 callback_fn: Box::new(callback_fn),
321+ completion_height: 0,
315 // TODO: scoring on empty input should just use a fastpath
324 // TODO: scoring on empty input should just use a fastpath
@@ -346,22 +355,38 @@ impl<T> Picker<T> {
346 self.cursor = 0;
355 self.cursor = 0;
349− pub fn move_up(&mut self) {
358+ /// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`)
350− if self.matches.is_empty() {
359+ pub fn move_by(&mut self, amount: usize, direction: Direction) {
353 let len = self.matches.len();
360 let len = self.matches.len();
354− let pos = ((self.cursor + len.saturating_sub(1)) % len) % len;
358− pub fn move_down(&mut self) {
362+ match direction {
359− if self.matches.is_empty() {
363+ Direction::Forward => {
360− return;
364+ self.cursor = self.cursor.saturating_add(amount) % len;
366+ Direction::Backward => {
367+ self.cursor = self.cursor.saturating_add(len).saturating_sub(amount) % len;
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);
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);
382+ /// Move the cursor to the first entry
383+ pub fn to_start(&mut self) {
387+ /// Move the cursor to the last entry
388+ pub fn to_end(&mut self) {
362− let len = self.matches.len();
389+ self.cursor = self.matches.len().saturating_sub(1);
363− let pos = (self.cursor + 1) % len;
367 pub fn selection(&self) -> Option<&T> {
392 pub fn selection(&self) -> Option<&T> {
@@ -384,23 +409,10 @@ impl<T> Picker<T> {
384// - on input change:
409// - on input change:
385// - score all the names in relation to input
410// - score all the names in relation to input
387−fn inner_rect(area: Rect) -> Rect {
388− let margin = Margin {
389− vertical: area.height * 10 / 100,
390− horizontal: area.width * 10 / 100,
395impl<T: 'static> Component for Picker<T> {
412impl<T: 'static> Component for Picker<T> {
396 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
413 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
414+ self.completion_height = viewport.1.saturating_sub(4);
400− let height = (self.options.len() as u16 + 4) // add some spacing for input + padding
402− let width = max_width;
403− Some((width, height))
406 fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
418 fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
@@ -417,10 +429,22 @@ impl<T: 'static> Component for Picker<T> {
418 match key_event.into() {
430 match key_event.into() {
419 shift!(Tab) | key!(Up) | ctrl!('p') | ctrl!('k') => {
431 shift!(Tab) | key!(Up) | ctrl!('p') | ctrl!('k') => {
420− self.move_up();
432+ self.move_by(1, Direction::Backward);
422 key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => {
434 key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => {
423− self.move_down();
435+ self.move_by(1, Direction::Forward);
437+ key!(PageDown) | ctrl!('f') => {
440+ key!(PageUp) | ctrl!('b') => {
425 key!(Esc) | ctrl!('c') => {
449 key!(Esc) | ctrl!('c') => {
426 return close_fn;
450 return close_fn;
@@ -458,12 +482,6 @@ impl<T: 'static> Component for Picker<T> {
460 fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
484 fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
461− let area = if self.render_centered {
467 let text_style = cx.editor.theme.get("ui.text");
485 let text_style = cx.editor.theme.get("ui.text");
469 // -- Render the frame:
487 // -- Render the frame:
@@ -538,8 +556,6 @@ impl<T: 'static> Component for Picker<T> {
540 fn cursor(&self, area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
558 fn cursor(&self, area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
541− // TODO: this is mostly duplicate code
542− let area = inner_rect(area);
543 let block = Block::default().borders(Borders::ALL);
559 let block = Block::default().borders(Borders::ALL);
544 // calculate the inner area inside the box
560 // calculate the inner area inside the box
545 let inner = block.inner(area);
561 let inner = block.inner(area);