Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/ui/picker.rs')
| -rw-r--r-- | helix-term/src/ui/picker.rs | 112 |
1 files changed, 28 insertions, 84 deletions
diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index fc1eba6e..bfec9ddb 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -4,9 +4,7 @@ mod query; use crate::{ alt, compositor::{self, Component, Compositor, Context, Event, EventResult}, - ctrl, - job::Callback, - key, shift, + ctrl, key, shift, ui::{ self, document::{render_document, LineDecoration, LinePos, TextRenderer}, @@ -53,9 +51,7 @@ use helix_view::{ Document, DocumentId, Editor, }; -use super::overlay::Overlay; - -use self::handlers::PreviewHighlightHandler; +use self::handlers::{DynamicQueryHandler, PreviewHighlightHandler}; pub const ID: &str = "picker"; @@ -221,6 +217,11 @@ impl<T, D> Column<T, D> { } } +/// Returns a new list of options to replace the contents of the picker +/// when called with the current picker query, +type DynQueryCallback<T, D> = + fn(&str, &mut Editor, Arc<D>, &Injector<T, D>) -> BoxFuture<'static, anyhow::Result<()>>; + pub struct Picker<T: 'static + Send + Sync, D: 'static> { columns: Arc<[Column<T, D>]>, primary_column: usize, @@ -250,6 +251,7 @@ pub struct Picker<T: 'static + Send + Sync, D: 'static> { file_fn: Option<FileCallback<T>>, /// An event handler for syntax highlighting the currently previewed file. preview_highlight_handler: Sender<Arc<Path>>, + dynamic_query_handler: Option<Sender<Arc<str>>>, } impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> { @@ -359,6 +361,7 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> { read_buffer: Vec::with_capacity(1024), file_fn: None, preview_highlight_handler: PreviewHighlightHandler::<T, D>::default().spawn(), + dynamic_query_handler: None, } } @@ -394,12 +397,15 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> { self } - pub fn set_options(&mut self, new_options: Vec<T>) { - self.matcher.restart(false); - let injector = self.matcher.injector(); - for item in new_options { - inject_nucleo_item(&injector, &self.columns, item, &self.editor_data); - } + pub fn with_dynamic_query( + mut self, + callback: DynQueryCallback<T, D>, + debounce_ms: Option<u64>, + ) -> Self { + let handler = DynamicQueryHandler::new(callback, debounce_ms).spawn(); + helix_event::send_blocking(&handler, self.primary_query()); + self.dynamic_query_handler = Some(handler); + self } /// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`) @@ -514,6 +520,11 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> { is_append, ); } + // If this is a dynamic picker, notify the query hook that the primary + // query might have been updated. + if let Some(handler) = &self.dynamic_query_handler { + helix_event::send_blocking(handler, self.primary_query()); + } } fn current_file(&self, editor: &Editor) -> Option<FileLocation> { @@ -621,7 +632,11 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> { let count = format!( "{}{}/{}", - if status.running { "(running) " } else { "" }, + if status.running || self.matcher.active_injectors() > 0 { + "(running) " + } else { + "" + }, snapshot.matched_item_count(), snapshot.item_count(), ); @@ -1018,74 +1033,3 @@ impl<T: 'static + Send + Sync, D> Drop for Picker<T, D> { } type PickerCallback<T> = Box<dyn Fn(&mut Context, &T, Action)>; - -/// 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: 'static + Send + Sync, D: 'static + Send + Sync> { - file_picker: Picker<T, D>, - query_callback: DynQueryCallback<T>, - query: String, -} - -impl<T: Send + Sync, D: Send + Sync> DynamicPicker<T, D> { - pub fn new(file_picker: Picker<T, D>, query_callback: DynQueryCallback<T>) -> Self { - Self { - file_picker, - query_callback, - query: String::new(), - } - } -} - -impl<T: Send + Sync + 'static, D: Send + Sync + 'static> Component for DynamicPicker<T, D> { - 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 event_result = self.file_picker.handle_event(event, cx); - let Some(current_query) = self.file_picker.primary_query() else { - return event_result; - }; - - if !matches!(event, Event::IdleTimeout) || self.query == *current_query { - return event_result; - } - - self.query = current_query.to_string(); - - 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 = 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<Self>>(ID) { - Some(overlay) => &mut overlay.content.file_picker, - None => return, - }; - picker.set_options(new_options); - })); - 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(ID) - } -} |