feat: Add streaming pipe support for async loading in.

This commit is contained in:
2026-03-15 01:33:38 -04:00
parent 3b574e89ab
commit e77ecb447e
8 changed files with 271 additions and 31 deletions

View File

@@ -154,6 +154,7 @@ pub struct MenuRunner<M: MutableMenu> {
generation: u64,
selection: SelectionState,
visual_anchor: Option<usize>,
streaming: bool,
}
impl<M: MutableMenu> MenuRunner<M> {
@@ -180,6 +181,7 @@ impl<M: MutableMenu> MenuRunner<M> {
generation: 0,
selection: SelectionState::new(),
visual_anchor: None,
streaming: false,
};
(runner, action_tx)
}
@@ -312,6 +314,7 @@ impl<M: MutableMenu> MenuRunner<M> {
mode: self.mode,
selection_count: self.selection.count(),
multi_enabled: self.selection.multi_enabled,
streaming: self.streaming,
columns,
generation: self.generation,
}
@@ -557,6 +560,11 @@ impl<M: MutableMenu> MenuRunner<M> {
self.apply_action(action)
}
Action::CloseMenu => ActionOutcome::Closed,
Action::StreamingDone => {
debug!("streaming done");
self.streaming = false;
ActionOutcome::Broadcast
}
}
}
@@ -566,6 +574,11 @@ impl<M: MutableMenu> MenuRunner<M> {
self.mode = mode;
}
/// Mark the menu as actively streaming items from stdin.
pub fn set_streaming(&mut self, v: bool) {
self.streaming = v;
}
/// Run the menu event loop. Consumes actions and
/// broadcasts events.
///
@@ -1824,4 +1837,54 @@ mod tests {
// City "toronto" (7) vs header "City" (4): width = 7
assert_eq!(cols[2].width, 7);
}
// -- Streaming tests --
#[test]
fn streaming_done_sets_streaming_false() {
let (mut m, _tx) = test_menu();
m.set_streaming(true);
m.run_filter();
assert!(m.build_view_state().streaming);
let outcome = m.apply_action(Action::StreamingDone);
assert!(matches!(outcome, ActionOutcome::Broadcast));
assert!(!m.build_view_state().streaming);
}
#[test]
fn add_items_on_empty_streaming_menu() {
let (mut m, _tx) = MenuRunner::new(JsonMenu::new(vec![], "label".to_string()));
m.set_streaming(true);
m.run_filter();
m.apply_action(Action::Resize { height: 20 });
assert_eq!(m.menu.total(), 0);
assert!(m.build_view_state().streaming);
m.apply_action(Action::AddItems(vec![
Value::String("one".into()),
Value::String("two".into()),
]));
assert_eq!(m.menu.total(), 2);
assert_eq!(m.menu.filtered_count(), 2);
// Confirm on non-empty streaming menu works
let outcome = m.apply_action(Action::Confirm);
assert!(matches!(outcome, ActionOutcome::Selected { .. }));
}
#[test]
fn confirm_on_empty_streaming_menu_is_noop() {
let (mut m, _tx) = MenuRunner::new(JsonMenu::new(vec![], "label".to_string()));
m.set_streaming(true);
m.run_filter();
let outcome = m.apply_action(Action::Confirm);
assert!(matches!(outcome, ActionOutcome::NoOp));
}
#[test]
fn view_state_streaming_defaults_false() {
let mut m = ready_menu();
assert!(!m.build_view_state().streaming);
}
}