Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/ui/picker/handlers.rs')
-rw-r--r--helix-term/src/ui/picker/handlers.rs99
1 files changed, 84 insertions, 15 deletions
diff --git a/helix-term/src/ui/picker/handlers.rs b/helix-term/src/ui/picker/handlers.rs
index f01c982a..e426adda 100644
--- a/helix-term/src/ui/picker/handlers.rs
+++ b/helix-term/src/ui/picker/handlers.rs
@@ -1,11 +1,15 @@
-use std::{path::Path, sync::Arc, time::Duration};
+use std::{
+ path::Path,
+ sync::{atomic, Arc},
+ time::Duration,
+};
use helix_event::AsyncHook;
use tokio::time::Instant;
use crate::{job, ui::overlay::Overlay};
-use super::{CachedPreview, DynamicPicker, Picker};
+use super::{CachedPreview, DynQueryCallback, Picker};
pub(super) struct PreviewHighlightHandler<T: 'static + Send + Sync, D: 'static + Send + Sync> {
trigger: Option<Arc<Path>>,
@@ -50,12 +54,11 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> AsyncHook
};
job::dispatch_blocking(move |editor, compositor| {
- let picker = match compositor.find::<Overlay<Picker<T, D>>>() {
- Some(Overlay { content, .. }) => content,
- None => match compositor.find::<Overlay<DynamicPicker<T, D>>>() {
- Some(Overlay { content, .. }) => &mut content.file_picker,
- None => return,
- },
+ let Some(Overlay {
+ content: picker, ..
+ }) = compositor.find::<Overlay<Picker<T, D>>>()
+ else {
+ return;
};
let Some(CachedPreview::Document(ref mut doc)) = picker.preview_cache.get_mut(&path)
@@ -87,13 +90,10 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> AsyncHook
};
job::dispatch_blocking(move |editor, compositor| {
- let picker = match compositor.find::<Overlay<Picker<T, D>>>() {
- Some(Overlay { content, .. }) => Some(content),
- None => compositor
- .find::<Overlay<DynamicPicker<T, D>>>()
- .map(|overlay| &mut overlay.content.file_picker),
- };
- let Some(picker) = picker else {
+ let Some(Overlay {
+ content: picker, ..
+ }) = compositor.find::<Overlay<Picker<T, D>>>()
+ else {
log::info!("picker closed before syntax highlighting finished");
return;
};
@@ -114,3 +114,72 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> AsyncHook
});
}
}
+
+pub(super) struct DynamicQueryHandler<T: 'static + Send + Sync, D: 'static + Send + Sync> {
+ callback: Arc<DynQueryCallback<T, D>>,
+ // Duration used as a debounce.
+ // Defaults to 100ms if not provided via `Picker::with_dynamic_query`. Callers may want to set
+ // this higher if the dynamic query is expensive - for example global search.
+ debounce: Duration,
+ last_query: Arc<str>,
+ query: Option<Arc<str>>,
+}
+
+impl<T: 'static + Send + Sync, D: 'static + Send + Sync> DynamicQueryHandler<T, D> {
+ pub(super) fn new(callback: DynQueryCallback<T, D>, duration_ms: Option<u64>) -> Self {
+ Self {
+ callback: Arc::new(callback),
+ debounce: Duration::from_millis(duration_ms.unwrap_or(100)),
+ last_query: "".into(),
+ query: None,
+ }
+ }
+}
+
+impl<T: 'static + Send + Sync, D: 'static + Send + Sync> AsyncHook for DynamicQueryHandler<T, D> {
+ type Event = Arc<str>;
+
+ fn handle_event(&mut self, query: Self::Event, _timeout: Option<Instant>) -> Option<Instant> {
+ if query == self.last_query {
+ // If the search query reverts to the last one we requested, no need to
+ // make a new request.
+ self.query = None;
+ None
+ } else {
+ self.query = Some(query);
+ Some(Instant::now() + self.debounce)
+ }
+ }
+
+ fn finish_debounce(&mut self) {
+ let Some(query) = self.query.take() else {
+ return;
+ };
+ self.last_query = query.clone();
+ let callback = self.callback.clone();
+
+ job::dispatch_blocking(move |editor, compositor| {
+ let Some(Overlay {
+ content: picker, ..
+ }) = compositor.find::<Overlay<Picker<T, D>>>()
+ else {
+ return;
+ };
+ // Increment the version number to cancel any ongoing requests.
+ picker.version.fetch_add(1, atomic::Ordering::Relaxed);
+ picker.matcher.restart(false);
+ let injector = picker.injector();
+ let get_options = (callback)(&query, editor, picker.editor_data.clone(), &injector);
+ tokio::spawn(async move {
+ if let Err(err) = get_options.await {
+ log::info!("Dynamic request failed: {err}");
+ }
+ // The picker's shows its running indicator when there are any active
+ // injectors. When we're done injecting new options, drop the injector
+ // and request a redraw to remove the running indicator.
+ drop(injector);
+ helix_event::request_redraw();
+ });
+ })
+ }
+}