Skip to main content

mdvault_core/scripting/
selector.rs

1//! Interactive selector types for Lua scripting.
2//!
3//! This module provides types for interactive selection prompts
4//! that can be triggered from Lua scripts via `mdv.selector()`.
5
6use std::sync::Arc;
7
8/// An item that can be displayed in a selector.
9#[derive(Debug, Clone)]
10pub struct SelectorItem {
11    /// Display label shown to the user.
12    pub label: String,
13    /// Value returned when this item is selected.
14    pub value: String,
15    /// Optional description shown below the label.
16    pub description: Option<String>,
17}
18
19impl SelectorItem {
20    /// Create a new selector item.
21    pub fn new(label: impl Into<String>, value: impl Into<String>) -> Self {
22        Self { label: label.into(), value: value.into(), description: None }
23    }
24
25    /// Create a selector item with description.
26    pub fn with_description(
27        label: impl Into<String>,
28        value: impl Into<String>,
29        description: impl Into<String>,
30    ) -> Self {
31        Self {
32            label: label.into(),
33            value: value.into(),
34            description: Some(description.into()),
35        }
36    }
37}
38
39/// Options for the selector prompt.
40#[derive(Debug, Clone, Default)]
41pub struct SelectorOptions {
42    /// Prompt text shown to the user.
43    pub prompt: String,
44    /// Whether fuzzy search is enabled.
45    pub fuzzy: bool,
46    /// Default selection index.
47    pub default: Option<usize>,
48    /// Whether to allow cancellation (returns None).
49    pub allow_cancel: bool,
50}
51
52impl SelectorOptions {
53    /// Create new selector options with a prompt.
54    pub fn new(prompt: impl Into<String>) -> Self {
55        Self {
56            prompt: prompt.into(),
57            fuzzy: true, // Enable fuzzy search by default
58            default: Some(0),
59            allow_cancel: true,
60        }
61    }
62
63    /// Set whether fuzzy search is enabled.
64    pub fn with_fuzzy(mut self, fuzzy: bool) -> Self {
65        self.fuzzy = fuzzy;
66        self
67    }
68
69    /// Set the default selection index.
70    pub fn with_default(mut self, index: usize) -> Self {
71        self.default = Some(index);
72        self
73    }
74}
75
76/// Type alias for the selector callback function.
77///
78/// The callback receives a list of items and options, and returns
79/// the selected item's value (or None if cancelled).
80pub type SelectorCallback =
81    Arc<dyn Fn(&[SelectorItem], &SelectorOptions) -> Option<String> + Send + Sync>;
82
83/// A no-op selector that always returns None.
84/// Used when no interactive selector is available.
85pub fn noop_selector(
86    _items: &[SelectorItem],
87    _options: &SelectorOptions,
88) -> Option<String> {
89    None
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_selector_item_new() {
98        let item = SelectorItem::new("Label", "value");
99        assert_eq!(item.label, "Label");
100        assert_eq!(item.value, "value");
101        assert!(item.description.is_none());
102    }
103
104    #[test]
105    fn test_selector_item_with_description() {
106        let item = SelectorItem::with_description("Label", "value", "A description");
107        assert_eq!(item.label, "Label");
108        assert_eq!(item.value, "value");
109        assert_eq!(item.description, Some("A description".to_string()));
110    }
111
112    #[test]
113    fn test_selector_options_defaults() {
114        let opts = SelectorOptions::new("Select an item");
115        assert_eq!(opts.prompt, "Select an item");
116        assert!(opts.fuzzy);
117        assert_eq!(opts.default, Some(0));
118        assert!(opts.allow_cancel);
119    }
120
121    #[test]
122    fn test_selector_options_with_fuzzy() {
123        let opts = SelectorOptions::new("Select").with_fuzzy(false);
124        assert!(!opts.fuzzy);
125    }
126
127    #[test]
128    fn test_selector_options_with_default() {
129        let opts = SelectorOptions::new("Select").with_default(5);
130        assert_eq!(opts.default, Some(5));
131    }
132
133    #[test]
134    fn test_noop_selector() {
135        let items = vec![
136            SelectorItem::new("Item 1", "val1"),
137            SelectorItem::new("Item 2", "val2"),
138        ];
139        let opts = SelectorOptions::new("Select");
140        assert_eq!(noop_selector(&items, &opts), None);
141    }
142
143    #[test]
144    fn test_selector_callback_type() {
145        // Verify the callback type can be constructed
146        let callback: SelectorCallback = Arc::new(|items, _opts| {
147            // Return first item's value if available
148            items.first().map(|i| i.value.clone())
149        });
150
151        let items = vec![SelectorItem::new("First", "first_value")];
152        let opts = SelectorOptions::new("Test");
153        let result = callback(&items, &opts);
154        assert_eq!(result, Some("first_value".to_string()));
155    }
156}