on master help sign in
Add DynamicPicker for updating options on every key
914d294Gokul Soumya
helix-term/src/ui/mod.rs+1−1
@@ -19,7 +19,7 @@ pub use completion::Completion;
19pub use editor::EditorView;
19pub use editor::EditorView;
20pub use markdown::Markdown;
20pub use markdown::Markdown;
21pub use menu::Menu;
21pub use menu::Menu;
22−pub use picker::{FileLocation, FilePicker, Picker};
22+pub use picker::{DynamicPicker, FileLocation, FilePicker, Picker};
23pub use popup::Popup;
23pub use popup::Popup;
24pub use prompt::{Prompt, PromptEvent};
24pub use prompt::{Prompt, PromptEvent};
25pub use spinner::{ProgressSpinners, Spinner};
25pub use spinner::{ProgressSpinners, Spinner};
helix-term/src/ui/overlay.rs+4
@@ -69,4 +69,8 @@ impl<T: Component + 'static> Component for Overlay<T> {
69 let dimensions = (self.calc_child_size)(area);
69 let dimensions = (self.calc_child_size)(area);
70 self.content.cursor(dimensions, ctx)
70 self.content.cursor(dimensions, ctx)
71 }
71 }
72+
73+ fn id(&self) -> Option<&'static str> {
74+ self.content.id()
75+ }
72}
76}
helix-term/src/ui/picker.rs+73−1
@@ -3,6 +3,7 @@ use crate::{
3 ctrl, key, shift,
3 ctrl, key, shift,
4 ui::{self, fuzzy_match::FuzzyQuery, EditorView},
4 ui::{self, fuzzy_match::FuzzyQuery, EditorView},
5};
5};
6+use futures_util::future::BoxFuture;
6use tui::{
7use tui::{
7 buffer::Buffer as Surface,
8 buffer::Buffer as Surface,
8 widgets::{Block, BorderType, Borders},
9 widgets::{Block, BorderType, Borders},
@@ -22,7 +23,7 @@ use helix_view::{
22 Document, DocumentId, Editor,
23 Document, DocumentId, Editor,
23};
24};
24
25
25−use super::menu::Item;
26+use super::{menu::Item, overlay::Overlay};
26
27
27pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72;
28pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72;
28/// Biggest file size to preview in bytes
29/// Biggest file size to preview in bytes
@@ -751,3 +752,74 @@ impl<T: Item + 'static> Component for Picker<T> {
751 self.prompt.cursor(area, editor)
752 self.prompt.cursor(area, editor)
752 }
753 }
753}
754}
755+
756+/// Returns a new list of options to replace the contents of the picker
757+/// when called with the current picker query,
758+pub type DynQueryCallback<T> =
759+ Box<dyn Fn(String, &mut Editor) -> BoxFuture<'static, anyhow::Result<Vec<T>>>>;
760+
761+/// A picker that updates its contents via a callback whenever the
762+/// query string changes. Useful for live grep, workspace symbols, etc.
763+pub struct DynamicPicker<T: ui::menu::Item + Send> {
764+ file_picker: FilePicker<T>,
765+ query_callback: DynQueryCallback<T>,
766+}
767+
768+impl<T: ui::menu::Item + Send> DynamicPicker<T> {
769+ pub const ID: &'static str = "dynamic-picker";
770+
771+ pub fn new(file_picker: FilePicker<T>, query_callback: DynQueryCallback<T>) -> Self {
772+ Self {
773+ file_picker,
774+ query_callback,
775+ }
776+ }
777+}
778+
779+impl<T: Item + Send + 'static> Component for DynamicPicker<T> {
780+ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
781+ self.file_picker.render(area, surface, cx);
782+ }
783+
784+ fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
785+ let prev_query = self.file_picker.picker.prompt.line().to_owned();
786+ let event_result = self.file_picker.handle_event(event, cx);
787+ let current_query = self.file_picker.picker.prompt.line();
788+
789+ if *current_query == prev_query || matches!(event_result, EventResult::Ignored(_)) {
790+ return event_result;
791+ }
792+
793+ let new_options = (self.query_callback)(current_query.to_owned(), cx.editor);
794+
795+ cx.jobs.callback(async move {
796+ let new_options = new_options.await?;
797+ let callback =
798+ crate::job::Callback::EditorCompositor(Box::new(move |_editor, compositor| {
799+ // Wrapping of pickers in overlay is done outside the picker code,
800+ // so this is fragile and will break if wrapped in some other widget.
801+ let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(Self::ID) {
802+ Some(overlay) => &mut overlay.content.file_picker.picker,
803+ None => return,
804+ };
805+ picker.options = new_options;
806+ picker.cursor = 0;
807+ picker.force_score();
808+ }));
809+ anyhow::Ok(callback)
810+ });
811+ EventResult::Consumed(None)
812+ }
813+
814+ fn cursor(&self, area: Rect, ctx: &Editor) -> (Option<Position>, CursorKind) {
815+ self.file_picker.cursor(area, ctx)
816+ }
817+
818+ fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
819+ self.file_picker.required_size(viewport)
820+ }
821+
822+ fn id(&self) -> Option<&'static str> {
823+ Some(Self::ID)
824+ }
825+}