refactor: Eliminate unnecessary empty Vec constructions.

This commit is contained in:
2026-03-14 11:43:48 -04:00
parent 0ebb5e79dd
commit e89c6cb16f
7 changed files with 40 additions and 49 deletions

View File

@@ -29,7 +29,7 @@ pub enum DebounceMode {
/// behavior. Each event kind can have its own mode.
pub struct DebouncedDispatcher {
handler: Arc<dyn HookHandler>,
action_tx: mpsc::Sender<Action>,
_action_tx: mpsc::Sender<Action>,
modes: HashMap<HookEventKind, DebounceMode>,
in_flight: HashMap<HookEventKind, JoinHandle<()>>,
}
@@ -41,7 +41,7 @@ impl DebouncedDispatcher {
) -> Self {
Self {
handler,
action_tx,
_action_tx: action_tx,
modes: HashMap::new(),
in_flight: HashMap::new(),
}
@@ -95,17 +95,9 @@ impl DebouncedDispatcher {
fn fire_now(&self, event: HookEvent) {
let handler = Arc::clone(&self.handler);
let action_tx = self.action_tx.clone();
tokio::spawn(async move {
match handler.handle(event) {
Ok(responses) => {
for resp in responses {
let _ = action_tx.send(Action::ProcessHookResponse(resp)).await;
}
}
Err(e) => {
tracing::warn!(error = %e, "hook handler error");
}
if let Err(e) = handler.handle(event) {
tracing::warn!(error = %e, "hook handler error");
}
});
}
@@ -117,18 +109,10 @@ impl DebouncedDispatcher {
}
let handler = Arc::clone(&self.handler);
let action_tx = self.action_tx.clone();
let handle = tokio::spawn(async move {
tokio::time::sleep(delay).await;
match handler.handle(event) {
Ok(responses) => {
for resp in responses {
let _ = action_tx.send(Action::ProcessHookResponse(resp)).await;
}
}
Err(e) => {
tracing::warn!(error = %e, "hook handler error");
}
if let Err(e) = handler.handle(event) {
tracing::warn!(error = %e, "hook handler error");
}
});
@@ -165,11 +149,11 @@ mod tests {
}
impl HookHandler for RecordingHandler {
fn handle(&self, event: HookEvent) -> Result<Vec<HookResponse>, PiklError> {
fn handle(&self, event: HookEvent) -> Result<(), PiklError> {
if let Ok(mut events) = self.events.lock() {
events.push(event.kind());
}
Ok(vec![])
Ok(())
}
}

View File

@@ -65,16 +65,15 @@ pub enum HookResponse {
}
/// Handler trait for lifecycle hooks. Implementations
/// receive events and optionally return responses.
/// Exec hooks return empty vecs. Handler hooks send
/// responses back through the action channel asynchronously
/// and also return empty vecs.
/// receive events and may produce side effects (spawning
/// processes, sending to channels). Responses flow through
/// the action channel, not the return value.
///
/// This is deliberately synchronous for dyn-compatibility.
/// Implementations that need async work (spawning processes,
/// writing to channels) should use `tokio::spawn` internally.
pub trait HookHandler: Send + Sync {
fn handle(&self, event: HookEvent) -> Result<Vec<HookResponse>, PiklError>;
fn handle(&self, event: HookEvent) -> Result<(), PiklError>;
}
/// Parse a single line of JSON as a [`HookResponse`].

View File

@@ -11,7 +11,7 @@ use crate::debounce::{hook_response_to_action, DebouncedDispatcher};
use crate::error::PiklError;
use crate::event::{Action, MenuEvent, MenuResult, Mode, ViewState, VisibleItem};
use crate::hook::{HookEvent, HookHandler};
use crate::model::traits::Menu;
use crate::model::traits::MutableMenu;
use crate::navigation::Viewport;
use serde_json::Value;
@@ -37,7 +37,7 @@ pub enum ActionOutcome {
/// drives it with an action/event channel loop. Create one,
/// grab the action sender and event subscriber, then call
/// [`MenuRunner::run`] to start the event loop.
pub struct MenuRunner<M: Menu> {
pub struct MenuRunner<M: MutableMenu> {
menu: M,
viewport: Viewport,
filter_text: Arc<str>,
@@ -46,9 +46,10 @@ pub struct MenuRunner<M: Menu> {
event_tx: broadcast::Sender<MenuEvent>,
dispatcher: Option<DebouncedDispatcher>,
previous_cursor: Option<usize>,
generation: u64,
}
impl<M: Menu> MenuRunner<M> {
impl<M: MutableMenu> MenuRunner<M> {
/// Create a menu runner wrapping the given menu backend.
/// Returns the runner and an action sender. Call
/// [`subscribe`](Self::subscribe) to get an event handle,
@@ -69,6 +70,7 @@ impl<M: Menu> MenuRunner<M> {
event_tx,
dispatcher: None,
previous_cursor: None,
generation: 0,
};
(runner, action_tx)
}
@@ -105,7 +107,8 @@ impl<M: Menu> MenuRunner<M> {
/// Build a [`ViewState`] snapshot from the current filter
/// results and viewport position.
fn build_view_state(&self) -> ViewState {
fn build_view_state(&mut self) -> ViewState {
self.generation += 1;
let range = self.viewport.visible_range();
let visible_items: Vec<VisibleItem> = range
.clone()
@@ -134,14 +137,14 @@ impl<M: Menu> MenuRunner<M> {
total_items: self.menu.total(),
total_filtered: self.menu.filtered_count(),
mode: self.mode,
generation: self.generation,
}
}
/// Send the current view state to all subscribers.
fn broadcast_state(&self) {
let _ = self
.event_tx
.send(MenuEvent::StateChanged(self.build_view_state()));
fn broadcast_state(&mut self) {
let vs = self.build_view_state();
let _ = self.event_tx.send(MenuEvent::StateChanged(vs));
}
/// Emit a hook event through the dispatcher, if one is set.
@@ -390,6 +393,7 @@ mod tests {
use super::*;
use crate::event::MenuEvent;
use crate::item::Item;
use crate::model::traits::Menu;
use crate::runtime::json_menu::JsonMenu;
fn test_menu() -> (MenuRunner<JsonMenu>, mpsc::Sender<Action>) {
@@ -1099,16 +1103,16 @@ mod tests {
#[tokio::test]
async fn hook_events_fire_on_lifecycle() {
use crate::hook::{HookEvent, HookEventKind, HookHandler, HookResponse};
use crate::hook::{HookEvent, HookEventKind, HookHandler};
use std::sync::Mutex;
struct Recorder(Mutex<Vec<HookEventKind>>);
impl HookHandler for Recorder {
fn handle(&self, event: HookEvent) -> Result<Vec<HookResponse>, PiklError> {
fn handle(&self, event: HookEvent) -> Result<(), PiklError> {
if let Ok(mut v) = self.0.lock() {
v.push(event.kind());
}
Ok(vec![])
Ok(())
}
}