1use mur_core::workflow::parser;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Suggestion {
9 pub text: String,
10 pub description: String,
11 pub category: SuggestionCategory,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum SuggestionCategory {
17 Workflow,
18 Command,
19 Variable,
20}
21
22pub struct AutocompleteEngine {
24 workflows: Vec<(String, String)>, commands: Vec<(String, String)>,
26}
27
28impl AutocompleteEngine {
29 pub fn new() -> Self {
30 let workflows = parser::load_all_workflows()
31 .unwrap_or_default()
32 .into_iter()
33 .map(|w| (w.id, w.description))
34 .collect();
35
36 let commands = vec![
37 ("/run".into(), "Execute a workflow".into()),
38 ("/workflows".into(), "List workflows".into()),
39 ("/status".into(), "Check daemon status".into()),
40 ("/audit".into(), "View audit log".into()),
41 ("/stop".into(), "Stop execution".into()),
42 ("/help".into(), "Show help".into()),
43 ];
44
45 Self {
46 workflows,
47 commands,
48 }
49 }
50
51 pub fn refresh(&mut self) {
53 self.workflows = parser::load_all_workflows()
54 .unwrap_or_default()
55 .into_iter()
56 .map(|w| (w.id, w.description))
57 .collect();
58 }
59
60 pub fn suggest(&self, prefix: &str, limit: usize) -> Vec<Suggestion> {
62 let prefix_lower = prefix.to_lowercase();
63 let mut suggestions = Vec::new();
64
65 if prefix.starts_with('/') {
67 for (cmd, desc) in &self.commands {
68 if cmd.to_lowercase().starts_with(&prefix_lower) {
69 suggestions.push(Suggestion {
70 text: cmd.clone(),
71 description: desc.clone(),
72 category: SuggestionCategory::Command,
73 });
74 }
75 }
76 }
77
78 for (id, desc) in &self.workflows {
80 if id.to_lowercase().contains(&prefix_lower) {
81 suggestions.push(Suggestion {
82 text: id.clone(),
83 description: desc.clone(),
84 category: SuggestionCategory::Workflow,
85 });
86 }
87 }
88
89 suggestions.truncate(limit);
90 suggestions
91 }
92}
93
94impl Default for AutocompleteEngine {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_command_suggestions() {
106 let engine = AutocompleteEngine::new();
107 let results = engine.suggest("/ru", 5);
108 assert!(!results.is_empty());
109 assert!(results.iter().any(|s| s.text == "/run"));
110 }
111
112 #[test]
113 fn test_empty_prefix() {
114 let engine = AutocompleteEngine::new();
115 let results = engine.suggest("", 10);
116 assert!(results.len() <= 10);
119 }
120}