From dfd7dbfcc256a733bd462548f1562a11cd55e32e Mon Sep 17 00:00:00 2001 From: Ludwig Stecher Date: Tue, 1 Feb 2022 14:30:52 +0100 Subject: [PATCH 1/6] Add `PageUp`, `PageDown`, `Ctrl-u`, `Ctrl-d`, `Home`, `End` keyboard shortcuts to file picker --- book/src/keymap.md | 4 +++ helix-term/src/ui/picker.rs | 55 +++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/book/src/keymap.md b/book/src/keymap.md index 905ec48fa8d0..25ce817ce1e9 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -304,7 +304,11 @@ Keys to use within picker. Remapping currently not supported. | Key | Description | | ----- | ------------- | | `Up`, `Ctrl-k`, `Ctrl-p` | Previous entry | +| `PageUp`, `Ctrl-u` | Page up | | `Down`, `Ctrl-j`, `Ctrl-n` | Next entry | +| `PageDown`, `Ctrl-d` | Page down | +| `Home` | Go to first entry | +| `End` | Go to last entry | | `Ctrl-space` | Filter options | | `Enter` | Open selected | | `Ctrl-s` | Open horizontally | diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 4068a2d43df1..10dc958b395b 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -271,6 +271,9 @@ pub struct Picker { /// Filter over original options. filters: Vec, // could be optimized into bit but not worth it now + /// Current height of the completions box + completion_height: u16, + cursor: usize, // pattern: String, prompt: Prompt, @@ -310,6 +313,7 @@ impl Picker { truncate_start: true, format_fn: Box::new(format_fn), callback_fn: Box::new(callback_fn), + completion_height: 0, }; // TODO: scoring on empty input should just use a fastpath @@ -364,6 +368,43 @@ impl Picker { self.cursor = pos; } + pub fn page_up(&mut self) { + if self.matches.is_empty() { + return; + } + let len = self.matches.len(); + let pos = ((self.cursor + len.saturating_sub(self.completion_height as usize)) % len) % len; + self.cursor = pos; + } + + pub fn page_down(&mut self) { + if self.matches.is_empty() { + return; + } + let len = self.matches.len(); + let pos = (self.cursor + self.completion_height as usize) % len; + self.cursor = pos; + } + + pub fn to_start(&mut self) { + if self.matches.is_empty() { + return; + } + self.cursor = 0; + } + + pub fn to_end(&mut self) { + if self.matches.is_empty() { + return; + } + self.cursor = self.matches.len() - 1; + } + + pub fn set_height(&mut self, new_height: u16) { + // subtract borders and input line + self.completion_height = new_height - 4; + } + pub fn selection(&self) -> Option<&T> { self.matches .get(self.cursor) @@ -422,6 +463,18 @@ impl Component for Picker { key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => { self.move_down(); } + key!(PageDown) | ctrl!('d') => { + self.page_down(); + } + key!(PageUp) | ctrl!('u') => { + self.page_up(); + } + key!(Home) => { + self.to_start(); + } + key!(End) => { + self.to_end(); + } key!(Esc) | ctrl!('c') => { return close_fn; } @@ -464,6 +517,8 @@ impl Component for Picker { area }; + self.set_height(area.height); + let text_style = cx.editor.theme.get("ui.text"); // -- Render the frame: From ce8bc197388b842aa41e291116479257725870e8 Mon Sep 17 00:00:00 2001 From: Ludwig Stecher Date: Wed, 2 Feb 2022 11:19:55 +0100 Subject: [PATCH 2/6] Refactor file picker paging logic --- helix-term/src/ui/picker.rs | 49 +++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 10dc958b395b..f319610056e8 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -350,42 +350,36 @@ impl Picker { self.cursor = 0; } - pub fn move_up(&mut self) { + /// Move the cursor by a number of lines, `amount`. + /// + /// Positive numbers move the cursor down, negative numbers move it up. The cursor cycles + /// through the entries. + pub fn move_by(&mut self, amount: isize) { if self.matches.is_empty() { return; } - let len = self.matches.len(); - let pos = ((self.cursor + len.saturating_sub(1)) % len) % len; - self.cursor = pos; - } - pub fn move_down(&mut self) { - if self.matches.is_empty() { - return; - } - let len = self.matches.len(); - let pos = (self.cursor + 1) % len; - self.cursor = pos; + let cursor: isize = self.cursor.try_into().unwrap(); + let len: isize = self.matches.len().try_into().unwrap(); + + self.cursor = cursor + .saturating_add(amount) + .rem_euclid(len) + .try_into() + .unwrap(); } + /// Move the cursor down by exactly one page. After the last page comes the first page. pub fn page_up(&mut self) { - if self.matches.is_empty() { - return; - } - let len = self.matches.len(); - let pos = ((self.cursor + len.saturating_sub(self.completion_height as usize)) % len) % len; - self.cursor = pos; + self.move_by(-(self.completion_height as isize)); } + /// Move the cursor up by exactly one page. After the first page comes the last page. pub fn page_down(&mut self) { - if self.matches.is_empty() { - return; - } - let len = self.matches.len(); - let pos = (self.cursor + self.completion_height as usize) % len; - self.cursor = pos; + self.move_by(self.completion_height as isize); } + /// Move the cursor to the first entry pub fn to_start(&mut self) { if self.matches.is_empty() { return; @@ -393,6 +387,7 @@ impl Picker { self.cursor = 0; } + /// Move the cursor to the last entry pub fn to_end(&mut self) { if self.matches.is_empty() { return; @@ -400,6 +395,8 @@ impl Picker { self.cursor = self.matches.len() - 1; } + /// Sets the height of the component, which is necessary for [`Picker::page_down`] and + /// [`Picker::page_up`] pub fn set_height(&mut self, new_height: u16) { // subtract borders and input line self.completion_height = new_height - 4; @@ -458,10 +455,10 @@ impl Component for Picker { match key_event.into() { shift!(Tab) | key!(Up) | ctrl!('p') | ctrl!('k') => { - self.move_up(); + self.move_by(-1); } key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => { - self.move_down(); + self.move_by(1); } key!(PageDown) | ctrl!('d') => { self.page_down(); From 256cad62f52dfe661f3df17e4baa30be5e3fbb90 Mon Sep 17 00:00:00 2001 From: Ludwig Stecher Date: Thu, 3 Feb 2022 12:54:28 +0100 Subject: [PATCH 3/6] change key mapping --- book/src/keymap.md | 4 ++-- helix-term/src/ui/picker.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 25ce817ce1e9..0a21aa515d40 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -304,9 +304,9 @@ Keys to use within picker. Remapping currently not supported. | Key | Description | | ----- | ------------- | | `Up`, `Ctrl-k`, `Ctrl-p` | Previous entry | -| `PageUp`, `Ctrl-u` | Page up | +| `PageUp`, `Ctrl-b` | Page up | | `Down`, `Ctrl-j`, `Ctrl-n` | Next entry | -| `PageDown`, `Ctrl-d` | Page down | +| `PageDown`, `Ctrl-f` | Page down | | `Home` | Go to first entry | | `End` | Go to last entry | | `Ctrl-space` | Filter options | diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index f319610056e8..e4ef1dc7f3b5 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -460,10 +460,10 @@ impl Component for Picker { key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => { self.move_by(1); } - key!(PageDown) | ctrl!('d') => { + key!(PageDown) | ctrl!('f') => { self.page_down(); } - key!(PageUp) | ctrl!('u') => { + key!(PageUp) | ctrl!('b') => { self.page_up(); } key!(Home) => { From 915cfb116490a08e477025ba02735530a3125187 Mon Sep 17 00:00:00 2001 From: Ludwig Stecher Date: Thu, 3 Feb 2022 13:13:15 +0100 Subject: [PATCH 4/6] Add overlay component --- helix-term/src/commands.rs | 4 +- helix-term/src/ui/mod.rs | 8 ++- helix-term/src/ui/overlay.rs | 81 ++++++++++++++++++++++++ helix-term/src/ui/picker.rs | 118 +++++++++++++---------------------- 4 files changed, 134 insertions(+), 77 deletions(-) create mode 100644 helix-term/src/ui/overlay.rs diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6bc2b9b6c29b..3756535b8966 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3396,7 +3396,7 @@ fn symbol_picker(cx: &mut Context) { Some((path, line)) }, ); - picker.truncate_start = false; + picker.content.truncate_start = false; compositor.push(Box::new(picker)) } }, @@ -3456,7 +3456,7 @@ fn workspace_symbol_picker(cx: &mut Context) { Some((path, line)) }, ); - picker.truncate_start = false; + picker.content.truncate_start = false; compositor.push(Box::new(picker)) } }, diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 5d650c6592eb..12dde8e09b65 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -3,6 +3,7 @@ pub(crate) mod editor; mod info; mod markdown; pub mod menu; +mod overlay; mod picker; mod popup; mod prompt; @@ -25,6 +26,8 @@ use helix_view::{Document, Editor, View}; use std::path::PathBuf; +use self::overlay::Overlay; + pub fn regex_prompt( cx: &mut crate::commands::Context, prompt: std::borrow::Cow<'static, str>, @@ -93,7 +96,10 @@ pub fn regex_prompt( ) } -pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePicker { +pub fn file_picker( + root: PathBuf, + config: &helix_view::editor::Config, +) -> Overlay> { use ignore::{types::TypesBuilder, WalkBuilder}; use std::time; diff --git a/helix-term/src/ui/overlay.rs b/helix-term/src/ui/overlay.rs new file mode 100644 index 000000000000..d2caf0167f79 --- /dev/null +++ b/helix-term/src/ui/overlay.rs @@ -0,0 +1,81 @@ +use crossterm::event::Event; +use helix_core::Position; +use helix_view::{ + graphics::{CursorKind, Rect}, + Editor, +}; +use tui::buffer::Buffer; + +use crate::compositor::{Component, Context, EventResult}; + +/// Contains a component placed in the center of the parent component +#[derive(Debug)] +pub struct Overlay { + /// Child component + pub content: T, + /// Value between 0 and 100 that indicates how much of the vertical space is used + pub vertical_size: u8, + /// Value between 0 and 100 that indicates how much of the horizontal space is used + pub horizontal_size: u8, +} + +impl Overlay { + const MARGIN_BOTTOM: u16 = 2; + + fn get_dimensions(&self, viewport: (u16, u16)) -> Rect { + fn mul_and_cast(size: u16, factor: u8) -> u16 { + ((size as u32) * (factor as u32) / 100).try_into().unwrap() + } + + let (outer_w, outer_h) = viewport; + let outer_h = outer_h.saturating_sub(Self::MARGIN_BOTTOM); + + let inner_w = mul_and_cast(outer_w, self.horizontal_size); + let inner_h = mul_and_cast(outer_h, self.vertical_size); + + let pos_x = outer_w.saturating_sub(inner_w) / 2; + let pos_y = outer_h.saturating_sub(inner_h) / 2; + + Rect { + x: pos_x, + y: pos_y, + width: inner_w, + height: inner_h, + } + } + + fn get_outer_size_from_inner_size(&self, inner: (u16, u16)) -> (u16, u16) { + fn div_and_cast(size: u16, divisor: u8) -> u16 { + ((size as u32) * 100 / (divisor as u32)).try_into().unwrap() + } + + let (inner_w, inner_h) = inner; + ( + div_and_cast(inner_w, self.horizontal_size), + div_and_cast(inner_h, self.vertical_size) + Self::MARGIN_BOTTOM, + ) + } +} + +impl Component for Overlay { + fn render(&mut self, area: Rect, frame: &mut Buffer, ctx: &mut Context) { + let dimensions = self.get_dimensions((area.width, area.height)); + self.content.render(dimensions, frame, ctx) + } + + fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { + let dimensions = self.get_dimensions(viewport); + let viewport = (dimensions.width, dimensions.height); + let required = self.content.required_size(viewport)?; + Some(self.get_outer_size_from_inner_size(required)) + } + + fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult { + self.content.handle_event(event, ctx) + } + + fn cursor(&self, area: Rect, ctx: &Editor) -> (Option, CursorKind) { + let dimensions = self.get_dimensions((area.width, area.height)); + self.content.cursor(dimensions, ctx) + } +} diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index e4ef1dc7f3b5..4e9a0743ae56 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -21,14 +21,16 @@ use std::{ }; use crate::ui::{Prompt, PromptEvent}; -use helix_core::Position; +use helix_core::{movement::Direction, Position}; use helix_view::{ editor::Action, graphics::{Color, CursorKind, Margin, Rect, Style}, Document, Editor, }; -pub const MIN_SCREEN_WIDTH_FOR_PREVIEW: u16 = 80; +use super::overlay::Overlay; + +pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72; /// Biggest file size to preview in bytes pub const MAX_FILE_SIZE_FOR_PREVIEW: u64 = 10 * 1024 * 1024; @@ -88,13 +90,17 @@ impl FilePicker { format_fn: impl Fn(&T) -> Cow + 'static, callback_fn: impl Fn(&mut Editor, &T, Action) + 'static, preview_fn: impl Fn(&Editor, &T) -> Option + 'static, - ) -> Self { - Self { - picker: Picker::new(false, options, format_fn, callback_fn), - truncate_start: true, - preview_cache: HashMap::new(), - read_buffer: Vec::with_capacity(1024), - file_fn: Box::new(preview_fn), + ) -> Overlay { + Overlay { + content: Self { + picker: Picker::new(options, format_fn, callback_fn), + truncate_start: true, + preview_cache: HashMap::new(), + read_buffer: Vec::with_capacity(1024), + file_fn: Box::new(preview_fn), + }, + vertical_size: 90, // percent + horizontal_size: 90, // percent } } @@ -160,8 +166,7 @@ impl Component for FilePicker { // | | | | // +---------+ +---------+ - let render_preview = area.width > MIN_SCREEN_WIDTH_FOR_PREVIEW; - let area = inner_rect(area); + let render_preview = area.width > MIN_AREA_WIDTH_FOR_PREVIEW; // -- Render the frame: // clear area let background = cx.editor.theme.get("ui.background"); @@ -260,6 +265,16 @@ impl Component for FilePicker { fn cursor(&self, area: Rect, ctx: &Editor) -> (Option, CursorKind) { self.picker.cursor(area, ctx) } + + fn required_size(&mut self, (width, height): (u16, u16)) -> Option<(u16, u16)> { + let picker_width = if width > MIN_AREA_WIDTH_FOR_PREVIEW { + width / 2 + } else { + width + }; + self.picker.required_size((picker_width, height))?; + Some((width, height)) + } } pub struct Picker { @@ -277,8 +292,6 @@ pub struct Picker { cursor: usize, // pattern: String, prompt: Prompt, - /// Whether to render in the middle of the area - render_centered: bool, /// Wheather to truncate the start (default true) pub truncate_start: bool, @@ -288,7 +301,6 @@ pub struct Picker { impl Picker { pub fn new( - render_centered: bool, options: Vec, format_fn: impl Fn(&T) -> Cow + 'static, callback_fn: impl Fn(&mut Editor, &T, Action) + 'static, @@ -309,7 +321,6 @@ impl Picker { filters: Vec::new(), cursor: 0, prompt, - render_centered, truncate_start: true, format_fn: Box::new(format_fn), callback_fn: Box::new(callback_fn), @@ -350,56 +361,38 @@ impl Picker { self.cursor = 0; } - /// Move the cursor by a number of lines, `amount`. - /// - /// Positive numbers move the cursor down, negative numbers move it up. The cursor cycles - /// through the entries. - pub fn move_by(&mut self, amount: isize) { - if self.matches.is_empty() { - return; - } - - let cursor: isize = self.cursor.try_into().unwrap(); - let len: isize = self.matches.len().try_into().unwrap(); + /// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`) + pub fn move_by(&mut self, amount: usize, direction: Direction) { + let len = self.matches.len(); - self.cursor = cursor - .saturating_add(amount) - .rem_euclid(len) - .try_into() - .unwrap(); + match direction { + Direction::Forward => { + self.cursor = self.cursor.saturating_add(amount) % len; + } + Direction::Backward => { + self.cursor = self.cursor.saturating_add(len).saturating_sub(amount) % len; + } + } } /// Move the cursor down by exactly one page. After the last page comes the first page. pub fn page_up(&mut self) { - self.move_by(-(self.completion_height as isize)); + self.move_by(self.completion_height as usize, Direction::Backward); } /// Move the cursor up by exactly one page. After the first page comes the last page. pub fn page_down(&mut self) { - self.move_by(self.completion_height as isize); + self.move_by(self.completion_height as usize, Direction::Forward); } /// Move the cursor to the first entry pub fn to_start(&mut self) { - if self.matches.is_empty() { - return; - } self.cursor = 0; } /// Move the cursor to the last entry pub fn to_end(&mut self) { - if self.matches.is_empty() { - return; - } - self.cursor = self.matches.len() - 1; - } - - /// Sets the height of the component, which is necessary for [`Picker::page_down`] and - /// [`Picker::page_up`] - pub fn set_height(&mut self, new_height: u16) { - // subtract borders and input line - self.completion_height = new_height - 4; + self.cursor = self.matches.len().saturating_sub(1); } pub fn selection(&self) -> Option<&T> { @@ -422,23 +415,10 @@ impl Picker { // - on input change: // - score all the names in relation to input -fn inner_rect(area: Rect) -> Rect { - let margin = Margin { - vertical: area.height * 10 / 100, - horizontal: area.width * 10 / 100, - }; - area.inner(&margin) -} - impl Component for Picker { fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { - let max_width = 50.min(viewport.0); - let max_height = 10.min(viewport.1.saturating_sub(2)); // add some spacing in the viewport - - let height = (self.options.len() as u16 + 4) // add some spacing for input + padding - .min(max_height); - let width = max_width; - Some((width, height)) + self.completion_height = viewport.1.saturating_sub(4); + Some(viewport) } fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { @@ -455,10 +435,10 @@ impl Component for Picker { match key_event.into() { shift!(Tab) | key!(Up) | ctrl!('p') | ctrl!('k') => { - self.move_by(-1); + self.move_by(1, Direction::Backward); } key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => { - self.move_by(1); + self.move_by(1, Direction::Forward); } key!(PageDown) | ctrl!('f') => { self.page_down(); @@ -508,14 +488,6 @@ impl Component for Picker { } fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { - let area = if self.render_centered { - inner_rect(area) - } else { - area - }; - - self.set_height(area.height); - let text_style = cx.editor.theme.get("ui.text"); // -- Render the frame: @@ -590,8 +562,6 @@ impl Component for Picker { } fn cursor(&self, area: Rect, editor: &Editor) -> (Option, CursorKind) { - // TODO: this is mostly duplicate code - let area = inner_rect(area); let block = Block::default().borders(Borders::ALL); // calculate the inner area inside the box let inner = block.inner(area); From a59dc11523b15c946d5dac2bab8b7525785b5842 Mon Sep 17 00:00:00 2001 From: Ludwig Stecher Date: Thu, 3 Feb 2022 19:09:06 +0100 Subject: [PATCH 5/6] Use closure instead of margin to calculate size --- helix-term/src/ui/overlay.rs | 70 ++++++++++++++---------------------- helix-term/src/ui/picker.rs | 5 ++- 2 files changed, 29 insertions(+), 46 deletions(-) diff --git a/helix-term/src/ui/overlay.rs b/helix-term/src/ui/overlay.rs index d2caf0167f79..537684b78336 100644 --- a/helix-term/src/ui/overlay.rs +++ b/helix-term/src/ui/overlay.rs @@ -9,65 +9,49 @@ use tui::buffer::Buffer; use crate::compositor::{Component, Context, EventResult}; /// Contains a component placed in the center of the parent component -#[derive(Debug)] pub struct Overlay { /// Child component pub content: T, - /// Value between 0 and 100 that indicates how much of the vertical space is used - pub vertical_size: u8, - /// Value between 0 and 100 that indicates how much of the horizontal space is used - pub horizontal_size: u8, + /// Function to compute the size and position of the child component + pub calc_child_size: Box Rect>, } -impl Overlay { - const MARGIN_BOTTOM: u16 = 2; - - fn get_dimensions(&self, viewport: (u16, u16)) -> Rect { - fn mul_and_cast(size: u16, factor: u8) -> u16 { - ((size as u32) * (factor as u32) / 100).try_into().unwrap() - } - - let (outer_w, outer_h) = viewport; - let outer_h = outer_h.saturating_sub(Self::MARGIN_BOTTOM); - - let inner_w = mul_and_cast(outer_w, self.horizontal_size); - let inner_h = mul_and_cast(outer_h, self.vertical_size); - - let pos_x = outer_w.saturating_sub(inner_w) / 2; - let pos_y = outer_h.saturating_sub(inner_h) / 2; - - Rect { - x: pos_x, - y: pos_y, - width: inner_w, - height: inner_h, - } +pub(super) fn clip_rect_relative(rect: Rect, percent_horizontal: u8, percent_vertical: u8) -> Rect { + fn mul_and_cast(size: u16, factor: u8) -> u16 { + ((size as u32) * (factor as u32) / 100).try_into().unwrap() } - fn get_outer_size_from_inner_size(&self, inner: (u16, u16)) -> (u16, u16) { - fn div_and_cast(size: u16, divisor: u8) -> u16 { - ((size as u32) * 100 / (divisor as u32)).try_into().unwrap() - } + let inner_w = mul_and_cast(rect.width, percent_horizontal); + let inner_h = mul_and_cast(rect.height, percent_vertical); + + let offset_x = rect.width.saturating_sub(inner_w) / 2; + let offset_y = rect.height.saturating_sub(inner_h) / 2; - let (inner_w, inner_h) = inner; - ( - div_and_cast(inner_w, self.horizontal_size), - div_and_cast(inner_h, self.vertical_size) + Self::MARGIN_BOTTOM, - ) + Rect { + x: rect.x + offset_x, + y: rect.y + offset_y, + width: inner_w, + height: inner_h, } } impl Component for Overlay { fn render(&mut self, area: Rect, frame: &mut Buffer, ctx: &mut Context) { - let dimensions = self.get_dimensions((area.width, area.height)); + let dimensions = (self.calc_child_size)(area); self.content.render(dimensions, frame, ctx) } - fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { - let dimensions = self.get_dimensions(viewport); + fn required_size(&mut self, (width, height): (u16, u16)) -> Option<(u16, u16)> { + let area = Rect { + x: 0, + y: 0, + width, + height, + }; + let dimensions = (self.calc_child_size)(area); let viewport = (dimensions.width, dimensions.height); - let required = self.content.required_size(viewport)?; - Some(self.get_outer_size_from_inner_size(required)) + let _ = self.content.required_size(viewport)?; + Some((width, height)) } fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult { @@ -75,7 +59,7 @@ impl Component for Overlay { } fn cursor(&self, area: Rect, ctx: &Editor) -> (Option, CursorKind) { - let dimensions = self.get_dimensions((area.width, area.height)); + let dimensions = (self.calc_child_size)(area); self.content.cursor(dimensions, ctx) } } diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 4e9a0743ae56..6ef30d4d2042 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -28,7 +28,7 @@ use helix_view::{ Document, Editor, }; -use super::overlay::Overlay; +use super::overlay::{clip_rect_relative, Overlay}; pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72; /// Biggest file size to preview in bytes @@ -99,8 +99,7 @@ impl FilePicker { read_buffer: Vec::with_capacity(1024), file_fn: Box::new(preview_fn), }, - vertical_size: 90, // percent - horizontal_size: 90, // percent + calc_child_size: Box::new(|rect: Rect| clip_rect_relative(rect.clip_bottom(2), 90, 90)), } } From 66f7bc92c69632bc84ebdd5e5c8818f4b06f103e Mon Sep 17 00:00:00 2001 From: Ludwig Stecher Date: Sun, 13 Feb 2022 01:55:14 +0100 Subject: [PATCH 6/6] Don't wrap file picker in `Overlay` automatically --- helix-term/src/application.rs | 5 +++-- helix-term/src/commands.rs | 18 +++++++++--------- helix-term/src/ui/mod.rs | 9 ++------- helix-term/src/ui/overlay.rs | 10 +++++++++- helix-term/src/ui/picker.rs | 19 +++++++------------ 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index ae154a24d406..50600df4fdb8 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -9,7 +9,7 @@ use crate::{ compositor::Compositor, config::Config, job::Jobs, - ui, + ui::{self, overlay::overlayed}, }; use log::{error, warn}; @@ -138,7 +138,8 @@ impl Application { if first.is_dir() { std::env::set_current_dir(&first)?; editor.new_file(Action::VerticalSplit); - compositor.push(Box::new(ui::file_picker(".".into(), &config.editor))); + let picker = ui::file_picker(".".into(), &config.editor); + compositor.push(Box::new(overlayed(picker))); } else { let nr_of_files = args.files.len(); editor.open(first.to_path_buf(), Action::VerticalSplit)?; diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 3756535b8966..4ad0fa42ccdd 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -39,7 +39,7 @@ use movement::Movement; use crate::{ args, compositor::{self, Component, Compositor}, - ui::{self, FilePicker, Popup, Prompt, PromptEvent}, + ui::{self, overlay::overlayed, FilePicker, Popup, Prompt, PromptEvent}, }; use crate::job::{self, Job, Jobs}; @@ -1785,7 +1785,7 @@ fn global_search(cx: &mut Context) { }, |_editor, (line_num, path)| Some((path.clone(), Some((*line_num, *line_num)))), ); - compositor.push(Box::new(picker)); + compositor.push(Box::new(overlayed(picker))); }); Ok(call) }; @@ -3251,7 +3251,7 @@ fn file_picker(cx: &mut Context) { // We don't specify language markers, root will be the root of the current git repo let root = find_root(None, &[]).unwrap_or_else(|| PathBuf::from("./")); let picker = ui::file_picker(root, &cx.editor.config); - cx.push_layer(Box::new(picker)); + cx.push_layer(Box::new(overlayed(picker))); } fn buffer_picker(cx: &mut Context) { @@ -3319,7 +3319,7 @@ fn buffer_picker(cx: &mut Context) { Some((meta.path.clone()?, Some((line, line)))) }, ); - cx.push_layer(Box::new(picker)); + cx.push_layer(Box::new(overlayed(picker))); } fn symbol_picker(cx: &mut Context) { @@ -3396,8 +3396,8 @@ fn symbol_picker(cx: &mut Context) { Some((path, line)) }, ); - picker.content.truncate_start = false; - compositor.push(Box::new(picker)) + picker.truncate_start = false; + compositor.push(Box::new(overlayed(picker))) } }, ) @@ -3456,8 +3456,8 @@ fn workspace_symbol_picker(cx: &mut Context) { Some((path, line)) }, ); - picker.content.truncate_start = false; - compositor.push(Box::new(picker)) + picker.truncate_start = false; + compositor.push(Box::new(overlayed(picker))) } }, ) @@ -4117,7 +4117,7 @@ fn goto_impl( Some((path, line)) }, ); - compositor.push(Box::new(picker)); + compositor.push(Box::new(overlayed(picker))); } } } diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 12dde8e09b65..89a14e4b885e 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -3,7 +3,7 @@ pub(crate) mod editor; mod info; mod markdown; pub mod menu; -mod overlay; +pub mod overlay; mod picker; mod popup; mod prompt; @@ -26,8 +26,6 @@ use helix_view::{Document, Editor, View}; use std::path::PathBuf; -use self::overlay::Overlay; - pub fn regex_prompt( cx: &mut crate::commands::Context, prompt: std::borrow::Cow<'static, str>, @@ -96,10 +94,7 @@ pub fn regex_prompt( ) } -pub fn file_picker( - root: PathBuf, - config: &helix_view::editor::Config, -) -> Overlay> { +pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePicker { use ignore::{types::TypesBuilder, WalkBuilder}; use std::time; diff --git a/helix-term/src/ui/overlay.rs b/helix-term/src/ui/overlay.rs index 537684b78336..9f522e355547 100644 --- a/helix-term/src/ui/overlay.rs +++ b/helix-term/src/ui/overlay.rs @@ -16,7 +16,15 @@ pub struct Overlay { pub calc_child_size: Box Rect>, } -pub(super) fn clip_rect_relative(rect: Rect, percent_horizontal: u8, percent_vertical: u8) -> Rect { +/// Surrounds the component with a margin of 5% on each side, and an additional 2 rows at the bottom +pub fn overlayed(content: T) -> Overlay { + Overlay { + content, + calc_child_size: Box::new(|rect: Rect| clip_rect_relative(rect.clip_bottom(2), 90, 90)), + } +} + +fn clip_rect_relative(rect: Rect, percent_horizontal: u8, percent_vertical: u8) -> Rect { fn mul_and_cast(size: u16, factor: u8) -> u16 { ((size as u32) * (factor as u32) / 100).try_into().unwrap() } diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 6ef30d4d2042..334f8410e93d 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -28,8 +28,6 @@ use helix_view::{ Document, Editor, }; -use super::overlay::{clip_rect_relative, Overlay}; - pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72; /// Biggest file size to preview in bytes pub const MAX_FILE_SIZE_FOR_PREVIEW: u64 = 10 * 1024 * 1024; @@ -90,16 +88,13 @@ impl FilePicker { format_fn: impl Fn(&T) -> Cow + 'static, callback_fn: impl Fn(&mut Editor, &T, Action) + 'static, preview_fn: impl Fn(&Editor, &T) -> Option + 'static, - ) -> Overlay { - Overlay { - content: Self { - picker: Picker::new(options, format_fn, callback_fn), - truncate_start: true, - preview_cache: HashMap::new(), - read_buffer: Vec::with_capacity(1024), - file_fn: Box::new(preview_fn), - }, - calc_child_size: Box::new(|rect: Rect| clip_rect_relative(rect.clip_bottom(2), 90, 90)), + ) -> Self { + Self { + picker: Picker::new(options, format_fn, callback_fn), + truncate_start: true, + preview_cache: HashMap::new(), + read_buffer: Vec::with_capacity(1024), + file_fn: Box::new(preview_fn), } }