aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src/ui
diff options
context:
space:
mode:
authorGokul Soumya2022-07-19 16:19:02 +0000
committerBlaž Hrastnik2022-12-15 08:52:44 +0000
commit914d2944997e11cf76eeabfe43f9031aeb2b1721 (patch)
tree52641d59df67d3a7c3a5b120b012ed5e46eae892 /helix-term/src/ui
parentc64debc7412c0769db8186694bd89f85ea057b1b (diff)
Add DynamicPicker for updating options on every key
Diffstat (limited to 'helix-term/src/ui')
-rw-r--r--helix-term/src/ui/mod.rs2
-rw-r--r--helix-term/src/ui/overlay.rs4
-rw-r--r--helix-term/src/ui/picker.rs74
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)
+ }
+}