diff options
author | Gokul Soumya | 2022-07-19 16:19:02 +0000 |
---|---|---|
committer | Blaž Hrastnik | 2022-12-15 08:52:44 +0000 |
commit | 914d2944997e11cf76eeabfe43f9031aeb2b1721 (patch) | |
tree | 52641d59df67d3a7c3a5b120b012ed5e46eae892 /helix-term/src/ui | |
parent | c64debc7412c0769db8186694bd89f85ea057b1b (diff) |
Add DynamicPicker for updating options on every key
Diffstat (limited to 'helix-term/src/ui')
-rw-r--r-- | helix-term/src/ui/mod.rs | 2 | ||||
-rw-r--r-- | helix-term/src/ui/overlay.rs | 4 | ||||
-rw-r--r-- | helix-term/src/ui/picker.rs | 74 |
3 files changed, 78 insertions, 2 deletions
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 107e48dd..5b5924bf 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -19,7 +19,7 @@ pub use completion::Completion; pub use editor::EditorView; pub use markdown::Markdown; pub use menu::Menu; -pub use picker::{FileLocation, FilePicker, Picker}; +pub use picker::{DynamicPicker, FileLocation, FilePicker, Picker}; pub use popup::Popup; pub use prompt::{Prompt, PromptEvent}; pub use spinner::{ProgressSpinners, Spinner}; diff --git a/helix-term/src/ui/overlay.rs b/helix-term/src/ui/overlay.rs index 0b8a93ae..5b2bc806 100644 --- a/helix-term/src/ui/overlay.rs +++ b/helix-term/src/ui/overlay.rs @@ -69,4 +69,8 @@ impl<T: Component + 'static> Component for Overlay<T> { let dimensions = (self.calc_child_size)(area); self.content.cursor(dimensions, ctx) } + + fn id(&self) -> Option<&'static str> { + self.content.id() + } } diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 8b5d20ec..821a282c 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -3,6 +3,7 @@ use crate::{ ctrl, key, shift, ui::{self, fuzzy_match::FuzzyQuery, EditorView}, }; +use futures_util::future::BoxFuture; use tui::{ buffer::Buffer as Surface, widgets::{Block, BorderType, Borders}, @@ -22,7 +23,7 @@ use helix_view::{ Document, DocumentId, Editor, }; -use super::menu::Item; +use super::{menu::Item, overlay::Overlay}; pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72; /// Biggest file size to preview in bytes @@ -751,3 +752,74 @@ impl<T: Item + 'static> Component for Picker<T> { self.prompt.cursor(area, editor) } } + +/// Returns a new list of options to replace the contents of the picker +/// when called with the current picker query, +pub type DynQueryCallback<T> = + Box<dyn Fn(String, &mut Editor) -> BoxFuture<'static, anyhow::Result<Vec<T>>>>; + +/// A picker that updates its contents via a callback whenever the +/// query string changes. Useful for live grep, workspace symbols, etc. +pub struct DynamicPicker<T: ui::menu::Item + Send> { + file_picker: FilePicker<T>, + query_callback: DynQueryCallback<T>, +} + +impl<T: ui::menu::Item + Send> DynamicPicker<T> { + pub const ID: &'static str = "dynamic-picker"; + + pub fn new(file_picker: FilePicker<T>, query_callback: DynQueryCallback<T>) -> Self { + Self { + file_picker, + query_callback, + } + } +} + +impl<T: Item + Send + 'static> Component for DynamicPicker<T> { + fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { + self.file_picker.render(area, surface, cx); + } + + fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { + let prev_query = self.file_picker.picker.prompt.line().to_owned(); + let event_result = self.file_picker.handle_event(event, cx); + let current_query = self.file_picker.picker.prompt.line(); + + if *current_query == prev_query || matches!(event_result, EventResult::Ignored(_)) { + return event_result; + } + + let new_options = (self.query_callback)(current_query.to_owned(), cx.editor); + + cx.jobs.callback(async move { + let new_options = new_options.await?; + let callback = + crate::job::Callback::EditorCompositor(Box::new(move |_editor, compositor| { + // Wrapping of pickers in overlay is done outside the picker code, + // so this is fragile and will break if wrapped in some other widget. + let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(Self::ID) { + Some(overlay) => &mut overlay.content.file_picker.picker, + None => return, + }; + picker.options = new_options; + picker.cursor = 0; + picker.force_score(); + })); + anyhow::Ok(callback) + }); + EventResult::Consumed(None) + } + + fn cursor(&self, area: Rect, ctx: &Editor) -> (Option<Position>, CursorKind) { + self.file_picker.cursor(area, ctx) + } + + fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { + self.file_picker.required_size(viewport) + } + + fn id(&self) -> Option<&'static str> { + Some(Self::ID) + } +} |