Picker: Skip dynamic query debounce for pastes (#11211)

Pastes are probably the last edit one means to make before the query
should run so it doesn't need to be debounced.
This makes global search much snappier for example when accepting the
history suggestion from the '/' register or pasting a pattern from the
clipboard or a register.
pull/11241/head
Michael Davis 2 months ago committed by GitHub
parent 748a9cf022
commit dbaa636683
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -52,7 +52,7 @@ use helix_view::{
Document, DocumentId, Editor, Document, DocumentId, Editor,
}; };
use self::handlers::{DynamicQueryHandler, PreviewHighlightHandler}; use self::handlers::{DynamicQueryChange, DynamicQueryHandler, PreviewHighlightHandler};
pub const ID: &str = "picker"; pub const ID: &str = "picker";
@ -272,7 +272,7 @@ pub struct Picker<T: 'static + Send + Sync, D: 'static> {
file_fn: Option<FileCallback<T>>, file_fn: Option<FileCallback<T>>,
/// An event handler for syntax highlighting the currently previewed file. /// An event handler for syntax highlighting the currently previewed file.
preview_highlight_handler: Sender<Arc<Path>>, preview_highlight_handler: Sender<Arc<Path>>,
dynamic_query_handler: Option<Sender<Arc<str>>>, dynamic_query_handler: Option<Sender<DynamicQueryChange>>,
} }
impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> { impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
@ -435,7 +435,12 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
debounce_ms: Option<u64>, debounce_ms: Option<u64>,
) -> Self { ) -> Self {
let handler = DynamicQueryHandler::new(callback, debounce_ms).spawn(); let handler = DynamicQueryHandler::new(callback, debounce_ms).spawn();
helix_event::send_blocking(&handler, self.primary_query()); let event = DynamicQueryChange {
query: self.primary_query(),
// Treat the initial query as a paste.
is_paste: true,
};
helix_event::send_blocking(&handler, event);
self.dynamic_query_handler = Some(handler); self.dynamic_query_handler = Some(handler);
self self
} }
@ -511,12 +516,12 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
fn prompt_handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { fn prompt_handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) { if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) {
self.handle_prompt_change(); self.handle_prompt_change(matches!(event, Event::Paste(_)));
} }
EventResult::Consumed(None) EventResult::Consumed(None)
} }
fn handle_prompt_change(&mut self) { fn handle_prompt_change(&mut self, is_paste: bool) {
// TODO: better track how the pattern has changed // TODO: better track how the pattern has changed
let line = self.prompt.line(); let line = self.prompt.line();
let old_query = self.query.parse(line); let old_query = self.query.parse(line);
@ -557,7 +562,11 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
// If this is a dynamic picker, notify the query hook that the primary // If this is a dynamic picker, notify the query hook that the primary
// query might have been updated. // query might have been updated.
if let Some(handler) = &self.dynamic_query_handler { if let Some(handler) = &self.dynamic_query_handler {
helix_event::send_blocking(handler, self.primary_query()); let event = DynamicQueryChange {
query: self.primary_query(),
is_paste,
};
helix_event::send_blocking(handler, event);
} }
} }
@ -1028,7 +1037,8 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
.filter(|_| self.prompt.line().is_empty()) .filter(|_| self.prompt.line().is_empty())
{ {
self.prompt.set_line(completion.to_string(), ctx.editor); self.prompt.set_line(completion.to_string(), ctx.editor);
self.handle_prompt_change(); // Inserting from the history register is a paste.
self.handle_prompt_change(true);
} else { } else {
if let Some(option) = self.selection() { if let Some(option) = self.selection() {
(self.callback_fn)(ctx, option, Action::Replace); (self.callback_fn)(ctx, option, Action::Replace);

@ -115,6 +115,11 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> AsyncHook
} }
} }
pub(super) struct DynamicQueryChange {
pub query: Arc<str>,
pub is_paste: bool,
}
pub(super) struct DynamicQueryHandler<T: 'static + Send + Sync, D: 'static + Send + Sync> { pub(super) struct DynamicQueryHandler<T: 'static + Send + Sync, D: 'static + Send + Sync> {
callback: Arc<DynQueryCallback<T, D>>, callback: Arc<DynQueryCallback<T, D>>,
// Duration used as a debounce. // Duration used as a debounce.
@ -137,9 +142,10 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> DynamicQueryHandler<T,
} }
impl<T: 'static + Send + Sync, D: 'static + Send + Sync> AsyncHook for DynamicQueryHandler<T, D> { impl<T: 'static + Send + Sync, D: 'static + Send + Sync> AsyncHook for DynamicQueryHandler<T, D> {
type Event = Arc<str>; type Event = DynamicQueryChange;
fn handle_event(&mut self, query: Self::Event, _timeout: Option<Instant>) -> Option<Instant> { fn handle_event(&mut self, change: Self::Event, _timeout: Option<Instant>) -> Option<Instant> {
let DynamicQueryChange { query, is_paste } = change;
if query == self.last_query { if query == self.last_query {
// If the search query reverts to the last one we requested, no need to // If the search query reverts to the last one we requested, no need to
// make a new request. // make a new request.
@ -147,9 +153,14 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> AsyncHook for DynamicQu
None None
} else { } else {
self.query = Some(query); self.query = Some(query);
if is_paste {
self.finish_debounce();
None
} else {
Some(Instant::now() + self.debounce) Some(Instant::now() + self.debounce)
} }
} }
}
fn finish_debounce(&mut self) { fn finish_debounce(&mut self) {
let Some(query) = self.query.take() else { let Some(query) = self.query.take() else {

Loading…
Cancel
Save