Skip to main content

flag_rs/
completion_optimized.rs

1//! Memory-optimized completion result structure
2//!
3//! This module provides a memory-efficient alternative to CompletionResult
4//! that reduces allocations and memory usage for large CLIs.
5
6use crate::active_help::ActiveHelp;
7use crate::completion_item::CompletionItem;
8use crate::context::Context;
9use crate::error::Result;
10use std::borrow::Cow;
11
12/// Memory-optimized completion result
13///
14/// This structure uses `CompletionItem` instead of parallel vectors,
15/// reducing memory fragmentation and improving cache locality.
16#[derive(Clone, Debug)]
17pub struct CompletionResultOptimized {
18    /// The completion items to suggest
19    pub items: Vec<CompletionItem>,
20    /// `ActiveHelp` messages to display
21    pub active_help: Vec<ActiveHelp>,
22}
23
24impl CompletionResultOptimized {
25    /// Creates a new empty completion result
26    #[must_use]
27    pub fn new() -> Self {
28        Self {
29            items: Vec::new(),
30            active_help: Vec::new(),
31        }
32    }
33
34    /// Adds a completion value without a description
35    #[allow(clippy::should_implement_trait)]
36    #[must_use]
37    pub fn add(mut self, value: impl Into<Cow<'static, str>>) -> Self {
38        self.items.push(CompletionItem::new(value));
39        self
40    }
41
42    /// Adds a completion value with a description
43    #[must_use]
44    pub fn add_with_description(
45        mut self,
46        value: impl Into<Cow<'static, str>>,
47        desc: impl Into<Cow<'static, str>>,
48    ) -> Self {
49        self.items
50            .push(CompletionItem::with_description(value, desc));
51        self
52    }
53
54    /// Adds multiple completion values without descriptions
55    #[must_use]
56    pub fn extend<I, S>(mut self, values: I) -> Self
57    where
58        I: IntoIterator<Item = S>,
59        S: Into<Cow<'static, str>>,
60    {
61        self.items
62            .extend(values.into_iter().map(|v| CompletionItem::new(v)));
63        self
64    }
65
66    /// Adds multiple completion items
67    #[must_use]
68    pub fn extend_items<I>(mut self, items: I) -> Self
69    where
70        I: IntoIterator<Item = CompletionItem>,
71    {
72        self.items.extend(items);
73        self
74    }
75
76    /// Adds an `ActiveHelp` message
77    #[must_use]
78    pub fn add_help(mut self, help: ActiveHelp) -> Self {
79        self.active_help.push(help);
80        self
81    }
82
83    /// Adds an `ActiveHelp` message from a string
84    #[must_use]
85    pub fn add_help_text<S: Into<String>>(mut self, message: S) -> Self {
86        self.active_help.push(ActiveHelp::new(message));
87        self
88    }
89
90    /// Adds a conditional `ActiveHelp` message
91    #[must_use]
92    pub fn add_conditional_help<S, F>(mut self, message: S, condition: F) -> Self
93    where
94        S: Into<String>,
95        F: Fn(&Context) -> bool + Send + Sync + 'static,
96    {
97        self.active_help
98            .push(ActiveHelp::with_condition(message, condition));
99        self
100    }
101
102    /// Merges two completion results
103    #[must_use]
104    pub fn merge(mut self, other: Self) -> Self {
105        self.items.extend(other.items);
106        self.active_help.extend(other.active_help);
107        self
108    }
109
110    /// Converts to the old `CompletionResult` format for compatibility
111    #[must_use]
112    pub fn into_legacy(self) -> crate::completion::CompletionResult {
113        let mut result = crate::completion::CompletionResult::new();
114        for item in self.items {
115            result.values.push(item.value.into_owned());
116            result
117                .descriptions
118                .push(item.description.map_or(String::new(), Cow::into_owned));
119        }
120        result.active_help = self.active_help;
121        result
122    }
123
124    /// Creates from the old `CompletionResult` format
125    #[must_use]
126    pub fn from_legacy(legacy: crate::completion::CompletionResult) -> Self {
127        let items = legacy
128            .values
129            .into_iter()
130            .zip(legacy.descriptions)
131            .map(|(value, desc)| {
132                if desc.is_empty() {
133                    CompletionItem::new(value)
134                } else {
135                    CompletionItem::with_description(value, desc)
136                }
137            })
138            .collect();
139
140        Self {
141            items,
142            active_help: legacy.active_help,
143        }
144    }
145}
146
147impl Default for CompletionResultOptimized {
148    fn default() -> Self {
149        Self::new()
150    }
151}
152
153/// Type alias for optimized completion functions
154pub type CompletionFuncOptimized =
155    Box<dyn Fn(&Context, &str) -> Result<CompletionResultOptimized> + Send + Sync>;
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_optimized_completion_result() {
163        let result = CompletionResultOptimized::new()
164            .add("option1")
165            .add_with_description("option2", "Description for option2")
166            .extend(["option3", "option4"]);
167
168        assert_eq!(result.items.len(), 4);
169        assert_eq!(result.items[0].value, "option1");
170        assert_eq!(result.items[0].description, None);
171        assert_eq!(result.items[1].value, "option2");
172        assert_eq!(
173            result.items[1].description.as_deref(),
174            Some("Description for option2")
175        );
176    }
177
178    #[test]
179    fn test_memory_efficiency() {
180        // Test that we can use static strings without allocation
181        let result = CompletionResultOptimized::new()
182            .add("static1")
183            .add_with_description("static2", "static description");
184
185        // These should be using Cow::Borrowed, not Cow::Owned
186        assert!(matches!(result.items[0].value, Cow::Borrowed(_)));
187        assert!(matches!(
188            result.items[1].description.as_ref().unwrap(),
189            Cow::Borrowed(_)
190        ));
191    }
192
193    #[test]
194    fn test_legacy_conversion() {
195        let optimized = CompletionResultOptimized::new()
196            .add("val1")
197            .add_with_description("val2", "desc2");
198
199        let legacy = optimized.clone().into_legacy();
200        assert_eq!(legacy.values, vec!["val1", "val2"]);
201        assert_eq!(legacy.descriptions, vec!["", "desc2"]);
202
203        let back = CompletionResultOptimized::from_legacy(legacy);
204        assert_eq!(back.items.len(), optimized.items.len());
205        assert_eq!(back.items[0].value, optimized.items[0].value);
206    }
207}