Skip to content

Commit

Permalink
Remove debounce on query box
Browse files Browse the repository at this point in the history
It's convenient in some scenarios, but with command-based filtering it ends up just being really annoying.
  • Loading branch information
LucasPickering committed Feb 3, 2025
1 parent 752b7af commit ed457e6
Show file tree
Hide file tree
Showing 3 changed files with 8 additions and 112 deletions.
65 changes: 4 additions & 61 deletions crates/tui/src/view/common/text_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use crate::{
context::UpdateContext,
draw::{Draw, DrawMetadata},
event::{Emitter, Event, EventHandler, OptionEvent, ToEmitter},
util::Debounce,
},
};
use crossterm::event::{KeyCode, KeyModifiers};
Expand All @@ -18,9 +17,7 @@ use ratatui::{
Frame,
};
use slumber_config::Action;
use std::{mem, time::Duration};

const DEBOUNCE: Duration = Duration::from_millis(500);
use std::mem;

/// Single line text submission component
#[derive(derive_more::Debug, Default)]
Expand All @@ -39,7 +36,6 @@ pub struct TextBox {

// State
state: TextState,
on_change_debounce: Option<Debounce>,
}

type Validator = Box<dyn Fn(&str) -> bool>;
Expand Down Expand Up @@ -85,13 +81,6 @@ impl TextBox {
self
}

/// Enable debouncing on the change event, meaning the user has to stop
/// inputting for a certain delay before the event is emitted
pub fn debounce(mut self) -> Self {
self.on_change_debounce = Some(Debounce::new(DEBOUNCE));
self
}

/// Get current text
pub fn text(&self) -> &str {
&self.state.text
Expand Down Expand Up @@ -176,35 +165,17 @@ impl TextBox {

/// Emit a change event. Should be called whenever text _content_ is changed
fn change(&mut self) {
let is_valid = self.is_valid();
if let Some(debounce) = &mut self.on_change_debounce {
if is_valid {
// Defer the change event until after the debounce period
let emitter = self.emitter;
debounce.start(move || emitter.emit(TextBoxEvent::Change));
} else {
debounce.cancel();
}
} else if is_valid {
if self.is_valid() {
self.emitter.emit(TextBoxEvent::Change);
}
}

/// Emit a submit event
fn submit(&mut self) {
if self.is_valid() {
self.cancel_debounce();
self.emitter.emit(TextBoxEvent::Submit);
}
}

/// Cancel any pending debounce. Should be called on submit or cancel, when
/// the user is no longer making changes
fn cancel_debounce(&mut self) {
if let Some(debounce) = &mut self.on_change_debounce {
debounce.cancel();
}
}
}

impl EventHandler for TextBox {
Expand All @@ -213,10 +184,7 @@ impl EventHandler for TextBox {
.opt()
.action(|action, propagate| match action {
Action::Submit => self.submit(),
Action::Cancel => {
self.cancel_debounce();
self.emitter.emit(TextBoxEvent::Cancel);
}
Action::Cancel => self.emitter.emit(TextBoxEvent::Cancel),
Action::LeftClick => self.emitter.emit(TextBoxEvent::Focus),
_ => propagate.set(),
})
Expand Down Expand Up @@ -427,7 +395,7 @@ pub enum TextBoxEvent {
mod tests {
use super::*;
use crate::{
test_util::{harness, run_local, terminal, TestHarness, TestTerminal},
test_util::{harness, terminal, TestHarness, TestTerminal},
view::test_util::TestComponent,
};
use ratatui::text::Span;
Expand Down Expand Up @@ -519,31 +487,6 @@ mod tests {
.assert_emitted([TextBoxEvent::Cancel]);
}

/// Test on_change debouncing
#[rstest]
#[tokio::test]
async fn test_debounce(
harness: TestHarness,
#[with(10, 1)] terminal: TestTerminal,
) {
// Local task set needed for the debounce task
let mut component = TestComponent::new(
&harness,
&terminal,
TextBox::default().debounce(),
);
run_local(async {
// Type some text. Change event isn't emitted immediately
component.int().send_text("hi").assert_emitted([]);
})
.await;
// Once the debounce task is done, event is emitted
component
.int()
.drain_draw()
.assert_emitted([TextBoxEvent::Change]);
}

/// Test text navigation and deleting. [TextState] has its own tests so
/// we're mostly just testing that keys are mapped correctly
#[rstest]
Expand Down
5 changes: 2 additions & 3 deletions crates/tui/src/view/component/queryable_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ impl QueryableBody {
"{query_bind} to query, {export_bind} to export"
))
.placeholder_focused("Enter query command (ex: `jq .results`)")
.default_value(default_query.clone().unwrap_or_default())
.debounce();
.default_value(default_query.clone().unwrap_or_default());
// Don't use a debounce on this one, because we don't want to
// auto-execute commands that will have a side effect
let export_text_box = TextBox::default().placeholder_focused(
Expand Down Expand Up @@ -244,7 +243,7 @@ impl EventHandler for QueryableBody {
})
.emitted(self.query_text_box.to_emitter(), |event| match event {
TextBoxEvent::Focus => self.focus(CommandFocus::Query),
TextBoxEvent::Change => self.update_query(),
TextBoxEvent::Change => {}
TextBoxEvent::Cancel => {
// Reset text to whatever was submitted last
self.query_text_box.data_mut().set_text(
Expand Down
50 changes: 2 additions & 48 deletions crates/tui/src/view/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
pub mod highlight;
pub mod persistence;

use crate::{
message::Message,
util::{spawn, temp_file},
view::ViewContext,
};
use crate::{message::Message, util::temp_file, view::ViewContext};
use anyhow::Context;
use itertools::Itertools;
use mime::Mime;
Expand All @@ -19,8 +15,7 @@ use slumber_core::{
template::{Prompt, PromptChannel, Prompter, Select},
util::ResultTraced,
};
use std::{io::Write, path::Path, time::Duration};
use tokio::{task::AbortHandle, time};
use std::{io::Write, path::Path};

/// A data structure for representation a yes/no confirmation. This is similar
/// to [Prompt], but it only asks a yes/no question.
Expand All @@ -47,47 +42,6 @@ impl Prompter for PreviewPrompter {
}
}

/// Utility for debouncing repeated calls to a callback
#[derive(Debug)]
pub struct Debounce {
duration: Duration,
abort_handle: Option<AbortHandle>,
}

impl Debounce {
pub fn new(duration: Duration) -> Self {
Self {
duration,
abort_handle: None,
}
}

/// Trigger a debounced callback. The given callback will be invoked after
/// the debounce period _if_ this method is not called again during the
/// debounce period.
pub fn start(&mut self, on_complete: impl 'static + Fn()) {
// Cancel the existing debounce, if any
self.cancel();

// Run debounce in a local task so component behavior can access the
// view context, e.g. to push events
let duration = self.duration;
let handle = spawn(async move {
time::sleep(duration).await;
on_complete();
});
self.abort_handle = Some(handle.abort_handle());
}

/// Cancel the current pending callback (if any) without registering a new
/// one
pub fn cancel(&mut self) {
if let Some(abort_handle) = self.abort_handle.take() {
abort_handle.abort();
}
}
}

/// Created a rectangle centered on the given `Rect`.
pub fn centered_rect(
width: Constraint,
Expand Down

0 comments on commit ed457e6

Please sign in to comment.