refactor(core): Split up the large menu trait.

This commit is contained in:
2026-03-14 11:48:27 -04:00
parent a27d529fa7
commit bb3f200141
4 changed files with 38 additions and 28 deletions

1
Cargo.lock generated
View File

@@ -1117,6 +1117,7 @@ dependencies = [
"pikl-core", "pikl-core",
"ratatui", "ratatui",
"tokio", "tokio",
"tracing",
] ]
[[package]] [[package]]

View File

@@ -32,9 +32,6 @@ pub trait Menu: Send + 'static {
/// position in the filtered results. /// position in the filtered results.
fn filtered_label(&self, filtered_index: usize) -> Option<&str>; fn filtered_label(&self, filtered_index: usize) -> Option<&str>;
/// Add raw values from streaming input or AddItems actions.
fn add_raw(&mut self, values: Vec<serde_json::Value>);
/// Get the JSON value of a filtered item for output. /// Get the JSON value of a filtered item for output.
/// Returns a reference to the stored value. /// Returns a reference to the stored value.
fn serialize_filtered(&self, filtered_index: usize) -> Option<&serde_json::Value>; fn serialize_filtered(&self, filtered_index: usize) -> Option<&serde_json::Value>;
@@ -44,14 +41,6 @@ pub trait Menu: Send + 'static {
/// for output and hook events. /// for output and hook events.
fn original_index(&self, filtered_index: usize) -> Option<usize>; fn original_index(&self, filtered_index: usize) -> Option<usize>;
/// Replace all items with a new set of values. Used by
/// handler hook `replace_items` responses.
fn replace_all(&mut self, values: Vec<serde_json::Value>);
/// Remove items at the given original indices. Used by
/// handler hook `remove_items` responses.
fn remove_by_indices(&mut self, indices: Vec<usize>);
/// Get the formatted display text for a filtered item, /// Get the formatted display text for a filtered item,
/// if a format template is configured. Returns None if /// if a format template is configured. Returns None if
/// no template is set, in which case the raw label is /// no template is set, in which case the raw label is
@@ -73,3 +62,20 @@ pub trait Menu: Send + 'static {
.collect() .collect()
} }
} }
/// Extension of [`Menu`] with mutation methods. Required by
/// [`MenuRunner`] which needs to add, replace, and remove
/// items. Embedders that only need read-only access can
/// depend on `Menu` alone.
pub trait MutableMenu: Menu {
/// Add raw values from streaming input or AddItems actions.
fn add_raw(&mut self, values: Vec<serde_json::Value>);
/// Replace all items with a new set of values. Used by
/// handler hook `replace_items` responses.
fn replace_all(&mut self, values: Vec<serde_json::Value>);
/// Remove items at the given original indices. Used by
/// handler hook `remove_items` responses.
fn remove_by_indices(&mut self, indices: Vec<usize>);
}

View File

@@ -5,7 +5,7 @@
use crate::filter::Filter; use crate::filter::Filter;
use crate::format::FormatTemplate; use crate::format::FormatTemplate;
use crate::item::Item; use crate::item::Item;
use crate::model::traits::Menu; use crate::model::traits::{Menu, MutableMenu};
use crate::pipeline::FilterPipeline; use crate::pipeline::FilterPipeline;
/// A menu backed by a flat list of JSON items. Handles /// A menu backed by a flat list of JSON items. Handles
@@ -120,16 +120,6 @@ impl Menu for JsonMenu {
.map(|idx| self.items[idx].label()) .map(|idx| self.items[idx].label())
} }
fn add_raw(&mut self, values: Vec<serde_json::Value>) {
for value in values {
let idx = self.items.len();
let item = Item::new(value, &self.label_key);
let text = self.extract_filter_text(&item);
self.filter.push_with_value(idx, &text, &item.value);
self.items.push(item);
}
}
fn serialize_filtered(&self, filtered_index: usize) -> Option<&serde_json::Value> { fn serialize_filtered(&self, filtered_index: usize) -> Option<&serde_json::Value> {
self.filter self.filter
.matched_index(filtered_index) .matched_index(filtered_index)
@@ -140,6 +130,24 @@ impl Menu for JsonMenu {
self.filter.matched_index(filtered_index) self.filter.matched_index(filtered_index)
} }
fn formatted_label(&self, filtered_index: usize) -> Option<String> {
let template = self.format_template.as_ref()?;
let orig_idx = self.filter.matched_index(filtered_index)?;
Some(template.render(&self.items[orig_idx].value))
}
}
impl MutableMenu for JsonMenu {
fn add_raw(&mut self, values: Vec<serde_json::Value>) {
for value in values {
let idx = self.items.len();
let item = Item::new(value, &self.label_key);
let text = self.extract_filter_text(&item);
self.filter.push_with_value(idx, &text, &item.value);
self.items.push(item);
}
}
fn replace_all(&mut self, values: Vec<serde_json::Value>) { fn replace_all(&mut self, values: Vec<serde_json::Value>) {
self.items = values self.items = values
.into_iter() .into_iter()
@@ -161,10 +169,4 @@ impl Menu for JsonMenu {
} }
self.rebuild_pipeline(); self.rebuild_pipeline();
} }
fn formatted_label(&self, filtered_index: usize) -> Option<String> {
let template = self.format_template.as_ref()?;
let orig_idx = self.filter.matched_index(filtered_index)?;
Some(template.render(&self.items[orig_idx].value))
}
} }

View File

@@ -14,3 +14,4 @@ ratatui = "0.30"
crossterm = { version = "0.29", features = ["event-stream"] } crossterm = { version = "0.29", features = ["event-stream"] }
tokio = { version = "1", features = ["sync", "macros", "rt"] } tokio = { version = "1", features = ["sync", "macros", "rt"] }
futures = "0.3" futures = "0.3"
tracing = "0.1"