this repo has no description
1use std::collections::{HashMap, HashSet};
2use crate::Graph;
3use std::sync::Arc;
4
5/// Advanced command completion system for CLI
6#[derive(Debug)]
7pub struct CommandCompletion {
8 /// Cypher keywords and their contexts
9 cypher_keywords: HashMap<String, Vec<String>>,
10 /// Function names and signatures
11 functions: HashMap<String, String>,
12 /// Common patterns and templates
13 patterns: Vec<CompletionPattern>,
14 /// User-defined shortcuts
15 shortcuts: HashMap<String, String>,
16}
17
18impl CommandCompletion {
19 pub fn new() -> Self {
20 let mut completion = Self {
21 cypher_keywords: HashMap::new(),
22 functions: HashMap::new(),
23 patterns: Vec::new(),
24 shortcuts: HashMap::new(),
25 };
26
27 completion.initialize_cypher_keywords();
28 completion.initialize_functions();
29 completion.initialize_patterns();
30 completion.initialize_shortcuts();
31
32 completion
33 }
34
35 /// Initialize Cypher keywords with their valid contexts
36 fn initialize_cypher_keywords(&mut self) {
37 // Core Cypher keywords
38 self.cypher_keywords.insert("MATCH".to_string(), vec![
39 "(n)".to_string(),
40 "(n:Label)".to_string(),
41 "(a)-[r]->(b)".to_string(),
42 "(a)-[:RELATION]->(b)".to_string(),
43 ]);
44
45 self.cypher_keywords.insert("CREATE".to_string(), vec![
46 "(n)".to_string(),
47 "(n:Label)".to_string(),
48 "(n:Label {property: 'value'})".to_string(),
49 "(a)-[:RELATION]->(b)".to_string(),
50 ]);
51
52 self.cypher_keywords.insert("RETURN".to_string(), vec![
53 "n".to_string(),
54 "n.property".to_string(),
55 "count(n)".to_string(),
56 "DISTINCT n".to_string(),
57 ]);
58
59 self.cypher_keywords.insert("WHERE".to_string(), vec![
60 "n.property = 'value'".to_string(),
61 "n.age > 18".to_string(),
62 "n.name CONTAINS 'text'".to_string(),
63 "n.name IN ['value1', 'value2']".to_string(),
64 ]);
65
66 self.cypher_keywords.insert("SET".to_string(), vec![
67 "n.property = 'value'".to_string(),
68 "n += {property: 'value'}".to_string(),
69 "n:Label".to_string(),
70 ]);
71
72 self.cypher_keywords.insert("DELETE".to_string(), vec![
73 "n".to_string(),
74 "r".to_string(),
75 ]);
76
77 self.cypher_keywords.insert("MERGE".to_string(), vec![
78 "(n:Label {property: 'value'})".to_string(),
79 "(a)-[:RELATION]->(b)".to_string(),
80 ]);
81
82 self.cypher_keywords.insert("WITH".to_string(), vec![
83 "n".to_string(),
84 "n, count(r) AS rel_count".to_string(),
85 "DISTINCT n".to_string(),
86 ]);
87
88 self.cypher_keywords.insert("ORDER BY".to_string(), vec![
89 "n.property".to_string(),
90 "n.property DESC".to_string(),
91 "count(n) DESC".to_string(),
92 ]);
93
94 self.cypher_keywords.insert("LIMIT".to_string(), vec![
95 "10".to_string(),
96 "100".to_string(),
97 "1000".to_string(),
98 ]);
99 }
100
101 /// Initialize function completions
102 fn initialize_functions(&mut self) {
103 // String functions
104 self.functions.insert("toUpper".to_string(), "toUpper(string) - Convert to uppercase".to_string());
105 self.functions.insert("toLower".to_string(), "toLower(string) - Convert to lowercase".to_string());
106 self.functions.insert("substring".to_string(), "substring(string, start, length) - Extract substring".to_string());
107 self.functions.insert("replace".to_string(), "replace(string, search, replacement) - Replace text".to_string());
108 self.functions.insert("split".to_string(), "split(string, delimiter) - Split string".to_string());
109
110 // Numeric functions
111 self.functions.insert("abs".to_string(), "abs(number) - Absolute value".to_string());
112 self.functions.insert("ceil".to_string(), "ceil(number) - Round up".to_string());
113 self.functions.insert("floor".to_string(), "floor(number) - Round down".to_string());
114 self.functions.insert("round".to_string(), "round(number) - Round to nearest".to_string());
115 self.functions.insert("sqrt".to_string(), "sqrt(number) - Square root".to_string());
116
117 // Aggregation functions
118 self.functions.insert("count".to_string(), "count(expression) - Count values".to_string());
119 self.functions.insert("sum".to_string(), "sum(expression) - Sum values".to_string());
120 self.functions.insert("avg".to_string(), "avg(expression) - Average values".to_string());
121 self.functions.insert("min".to_string(), "min(expression) - Minimum value".to_string());
122 self.functions.insert("max".to_string(), "max(expression) - Maximum value".to_string());
123 self.functions.insert("collect".to_string(), "collect(expression) - Collect into list".to_string());
124
125 // List functions
126 self.functions.insert("size".to_string(), "size(list) - Get list size".to_string());
127 self.functions.insert("head".to_string(), "head(list) - Get first element".to_string());
128 self.functions.insert("tail".to_string(), "tail(list) - Get all but first element".to_string());
129 self.functions.insert("last".to_string(), "last(list) - Get last element".to_string());
130
131 // Date/Time functions
132 self.functions.insert("timestamp".to_string(), "timestamp() - Current timestamp".to_string());
133 self.functions.insert("date".to_string(), "date() - Current date".to_string());
134 self.functions.insert("datetime".to_string(), "datetime() - Current datetime".to_string());
135
136 // Type functions
137 self.functions.insert("type".to_string(), "type(relationship) - Get relationship type".to_string());
138 self.functions.insert("labels".to_string(), "labels(node) - Get node labels".to_string());
139 self.functions.insert("keys".to_string(), "keys(map) - Get map keys".to_string());
140 self.functions.insert("properties".to_string(), "properties(entity) - Get all properties".to_string());
141
142 // Path functions
143 self.functions.insert("length".to_string(), "length(path) - Get path length".to_string());
144 self.functions.insert("nodes".to_string(), "nodes(path) - Get nodes in path".to_string());
145 self.functions.insert("relationships".to_string(), "relationships(path) - Get relationships in path".to_string());
146
147 // Predicate functions
148 self.functions.insert("exists".to_string(), "exists(property) - Check if property exists".to_string());
149 self.functions.insert("all".to_string(), "all(variable IN list WHERE predicate) - Check all elements".to_string());
150 self.functions.insert("any".to_string(), "any(variable IN list WHERE predicate) - Check any element".to_string());
151 self.functions.insert("none".to_string(), "none(variable IN list WHERE predicate) - Check no elements".to_string());
152 self.functions.insert("single".to_string(), "single(variable IN list WHERE predicate) - Check single element".to_string());
153 }
154
155 /// Initialize common patterns
156 fn initialize_patterns(&mut self) {
157 self.patterns = vec![
158 CompletionPattern {
159 name: "Basic node query".to_string(),
160 pattern: "MATCH (n:${Label}) RETURN n".to_string(),
161 description: "Find all nodes with a specific label".to_string(),
162 variables: vec!["Label".to_string()],
163 },
164 CompletionPattern {
165 name: "Node with properties".to_string(),
166 pattern: "MATCH (n:${Label} {${property}: '${value}'}) RETURN n".to_string(),
167 description: "Find nodes by label and property".to_string(),
168 variables: vec!["Label".to_string(), "property".to_string(), "value".to_string()],
169 },
170 CompletionPattern {
171 name: "Relationship query".to_string(),
172 pattern: "MATCH (a:${Label1})-[r:${RelType}]->(b:${Label2}) RETURN a, r, b".to_string(),
173 description: "Find relationships between node types".to_string(),
174 variables: vec!["Label1".to_string(), "RelType".to_string(), "Label2".to_string()],
175 },
176 CompletionPattern {
177 name: "Create node".to_string(),
178 pattern: "CREATE (n:${Label} {${property}: '${value}'}) RETURN n".to_string(),
179 description: "Create a new node with properties".to_string(),
180 variables: vec!["Label".to_string(), "property".to_string(), "value".to_string()],
181 },
182 CompletionPattern {
183 name: "Create relationship".to_string(),
184 pattern: "MATCH (a:${Label1}), (b:${Label2}) WHERE a.${prop1} = '${val1}' AND b.${prop2} = '${val2}' CREATE (a)-[:${RelType}]->(b)".to_string(),
185 description: "Create relationship between existing nodes".to_string(),
186 variables: vec!["Label1".to_string(), "Label2".to_string(), "prop1".to_string(), "val1".to_string(), "prop2".to_string(), "val2".to_string(), "RelType".to_string()],
187 },
188 CompletionPattern {
189 name: "Update properties".to_string(),
190 pattern: "MATCH (n:${Label}) WHERE n.${filter_prop} = '${filter_val}' SET n.${update_prop} = '${update_val}' RETURN n".to_string(),
191 description: "Update node properties with filtering".to_string(),
192 variables: vec!["Label".to_string(), "filter_prop".to_string(), "filter_val".to_string(), "update_prop".to_string(), "update_val".to_string()],
193 },
194 CompletionPattern {
195 name: "Delete nodes".to_string(),
196 pattern: "MATCH (n:${Label}) WHERE n.${property} = '${value}' DELETE n".to_string(),
197 description: "Delete nodes with specific criteria".to_string(),
198 variables: vec!["Label".to_string(), "property".to_string(), "value".to_string()],
199 },
200 CompletionPattern {
201 name: "Shortest path".to_string(),
202 pattern: "MATCH path = shortestPath((a:${Label1})-[*..${max_depth}]-(b:${Label2})) WHERE a.${prop1} = '${val1}' AND b.${prop2} = '${val2}' RETURN path".to_string(),
203 description: "Find shortest path between nodes".to_string(),
204 variables: vec!["Label1".to_string(), "Label2".to_string(), "max_depth".to_string(), "prop1".to_string(), "val1".to_string(), "prop2".to_string(), "val2".to_string()],
205 },
206 CompletionPattern {
207 name: "Aggregation query".to_string(),
208 pattern: "MATCH (n:${Label}) RETURN n.${group_prop}, count(*) AS count ORDER BY count DESC".to_string(),
209 description: "Group and count nodes by property".to_string(),
210 variables: vec!["Label".to_string(), "group_prop".to_string()],
211 },
212 CompletionPattern {
213 name: "Complex traversal".to_string(),
214 pattern: "MATCH (start:${Label})-[*${min_depth}..${max_depth}]->(end) WHERE start.${prop} = '${value}' RETURN DISTINCT end".to_string(),
215 description: "Traverse graph with depth limits".to_string(),
216 variables: vec!["Label".to_string(), "min_depth".to_string(), "max_depth".to_string(), "prop".to_string(), "value".to_string()],
217 },
218 ];
219 }
220
221 /// Initialize user shortcuts
222 fn initialize_shortcuts(&mut self) {
223 self.shortcuts.insert("an".to_string(), "MATCH (n) RETURN n".to_string());
224 self.shortcuts.insert("ar".to_string(), "MATCH (a)-[r]->(b) RETURN a, r, b".to_string());
225 self.shortcuts.insert("cn".to_string(), "MATCH (n) RETURN count(n)".to_string());
226 self.shortcuts.insert("dn".to_string(), "MATCH (n) DELETE n".to_string());
227 self.shortcuts.insert("schema".to_string(), ":show schema".to_string());
228 self.shortcuts.insert("stats".to_string(), ":stats".to_string());
229 }
230
231 /// Get completions for partial input
232 pub fn get_completions(&self, input: &str, graph: &Arc<Graph>) -> Vec<Completion> {
233 let mut completions = Vec::new();
234 let input_lower = input.to_lowercase();
235
236 // Handle shortcuts first
237 if let Some(expanded) = self.shortcuts.get(&input_lower) {
238 completions.push(Completion {
239 text: expanded.clone(),
240 completion_type: CompletionType::Shortcut,
241 description: Some(format!("Shortcut: {}", input)),
242 priority: 10,
243 });
244 }
245
246 // Meta command completions
247 if input.starts_with(':') {
248 completions.extend(self.get_meta_command_completions(input));
249 } else {
250 // Cypher completions
251 completions.extend(self.get_cypher_completions(input, graph));
252 }
253
254 // Sort by priority
255 completions.sort_by(|a, b| b.priority.cmp(&a.priority));
256 completions.truncate(20); // Limit results
257
258 completions
259 }
260
261 /// Get meta command completions
262 fn get_meta_command_completions(&self, input: &str) -> Vec<Completion> {
263 let mut completions = Vec::new();
264 let input_lower = input.to_lowercase();
265
266 let meta_commands = vec![
267 (":help", "Show help information"),
268 (":exit", "Exit the CLI"),
269 (":quit", "Exit the CLI"),
270 (":stats", "Show graph statistics"),
271 (":show nodes", "Show all nodes"),
272 (":show relationships", "Show all relationships"),
273 (":show schema", "Show schema information"),
274 (":format table", "Set output format to table"),
275 (":format json", "Set output format to JSON"),
276 (":format csv", "Set output format to CSV"),
277 (":format plain", "Set output format to plain text"),
278 (":timing", "Toggle timing display"),
279 (":history", "Show command history"),
280 (":clear", "Clear the screen"),
281 (":export <file>", "Export graph data"),
282 (":import <file>", "Import graph data"),
283 ];
284
285 for (cmd, desc) in meta_commands {
286 if cmd.starts_with(&input_lower) {
287 completions.push(Completion {
288 text: cmd.to_string(),
289 completion_type: CompletionType::MetaCommand,
290 description: Some(desc.to_string()),
291 priority: 8,
292 });
293 }
294 }
295
296 completions
297 }
298
299 /// Get Cypher command completions
300 fn get_cypher_completions(&self, input: &str, graph: &Arc<Graph>) -> Vec<Completion> {
301 let mut completions = Vec::new();
302 let tokens = self.tokenize_input(input);
303 let last_token = tokens.last().unwrap_or(&"".to_string()).to_lowercase();
304
305 // Keyword completions
306 for (keyword, examples) in &self.cypher_keywords {
307 if keyword.to_lowercase().starts_with(&last_token) {
308 completions.push(Completion {
309 text: keyword.clone(),
310 completion_type: CompletionType::Keyword,
311 description: Some(format!("Cypher keyword - examples: {}", examples.join(", "))),
312 priority: 7,
313 });
314 }
315 }
316
317 // Function completions
318 for (func, desc) in &self.functions {
319 if func.to_lowercase().starts_with(&last_token) {
320 completions.push(Completion {
321 text: format!("{}()", func),
322 completion_type: CompletionType::Function,
323 description: Some(desc.clone()),
324 priority: 6,
325 });
326 }
327 }
328
329 // Pattern completions
330 for pattern in &self.patterns {
331 if pattern.name.to_lowercase().contains(&last_token) ||
332 pattern.pattern.to_lowercase().contains(&last_token) {
333 completions.push(Completion {
334 text: pattern.pattern.clone(),
335 completion_type: CompletionType::Pattern,
336 description: Some(pattern.description.clone()),
337 priority: 5,
338 });
339 }
340 }
341
342 // Schema-based completions
343 completions.extend(self.get_schema_completions(&last_token, graph));
344
345 completions
346 }
347
348 /// Get schema-based completions (labels, properties, relationship types)
349 fn get_schema_completions(&self, token: &str, graph: &Arc<Graph>) -> Vec<Completion> {
350 let mut completions = Vec::new();
351 let schema = graph.schema().read();
352
353 // Label completions
354 for label_name in schema.labels.keys() {
355 if label_name.to_lowercase().contains(token) {
356 completions.push(Completion {
357 text: label_name.clone(),
358 completion_type: CompletionType::Label,
359 description: Some("Node label".to_string()),
360 priority: 4,
361 });
362 }
363 }
364
365 // Property key completions
366 for prop_key_name in schema.property_keys.keys() {
367 if prop_key_name.to_lowercase().contains(token) {
368 completions.push(Completion {
369 text: prop_key_name.clone(),
370 completion_type: CompletionType::Property,
371 description: Some("Property key".to_string()),
372 priority: 4,
373 });
374 }
375 }
376
377 // Relationship type completions
378 for rel_type_name in schema.relationship_types.keys() {
379 if rel_type_name.to_lowercase().contains(token) {
380 completions.push(Completion {
381 text: rel_type_name.clone(),
382 completion_type: CompletionType::RelationshipType,
383 description: Some("Relationship type".to_string()),
384 priority: 4,
385 });
386 }
387 }
388
389 completions
390 }
391
392 /// Tokenize input for context-aware completion
393 fn tokenize_input(&self, input: &str) -> Vec<String> {
394 input.split_whitespace()
395 .map(|s| s.to_string())
396 .collect()
397 }
398
399 /// Add a user-defined shortcut
400 pub fn add_shortcut(&mut self, shortcut: String, expansion: String) {
401 self.shortcuts.insert(shortcut, expansion);
402 }
403
404 /// Remove a user-defined shortcut
405 pub fn remove_shortcut(&mut self, shortcut: &str) -> bool {
406 self.shortcuts.remove(shortcut).is_some()
407 }
408
409 /// Get all shortcuts
410 pub fn get_shortcuts(&self) -> &HashMap<String, String> {
411 &self.shortcuts
412 }
413
414 /// Get pattern by name
415 pub fn get_pattern(&self, name: &str) -> Option<&CompletionPattern> {
416 self.patterns.iter().find(|p| p.name.to_lowercase() == name.to_lowercase())
417 }
418
419 /// Expand pattern with variables
420 pub fn expand_pattern(&self, pattern: &CompletionPattern, variables: HashMap<String, String>) -> String {
421 let mut expanded = pattern.pattern.clone();
422
423 for (var, value) in variables {
424 let placeholder = format!("${{{}}}", var);
425 expanded = expanded.replace(&placeholder, &value);
426 }
427
428 expanded
429 }
430}
431
432/// Completion item
433#[derive(Debug, Clone)]
434pub struct Completion {
435 pub text: String,
436 pub completion_type: CompletionType,
437 pub description: Option<String>,
438 pub priority: u8,
439}
440
441/// Types of completions
442#[derive(Debug, Clone, PartialEq)]
443pub enum CompletionType {
444 Keyword,
445 Function,
446 MetaCommand,
447 Label,
448 Property,
449 RelationshipType,
450 Pattern,
451 Shortcut,
452}
453
454/// Completion pattern with variables
455#[derive(Debug, Clone)]
456pub struct CompletionPattern {
457 pub name: String,
458 pub pattern: String,
459 pub description: String,
460 pub variables: Vec<String>,
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466 use crate::Graph;
467 use std::sync::Arc;
468
469 #[test]
470 fn test_completion_creation() {
471 let completion = CommandCompletion::new();
472 assert!(!completion.cypher_keywords.is_empty());
473 assert!(!completion.functions.is_empty());
474 assert!(!completion.patterns.is_empty());
475 }
476
477 #[test]
478 fn test_meta_command_completions() {
479 let completion = CommandCompletion::new();
480 let results = completion.get_meta_command_completions(":h");
481
482 assert!(results.iter().any(|c| c.text == ":help"));
483 assert!(results.iter().any(|c| c.text == ":history"));
484 }
485
486 #[test]
487 fn test_shortcut_expansion() {
488 let completion = CommandCompletion::new();
489 let graph = Arc::new(Graph::new());
490 let results = completion.get_completions("an", &graph);
491
492 assert!(results.iter().any(|c| c.completion_type == CompletionType::Shortcut));
493 }
494
495 #[test]
496 fn test_pattern_expansion() {
497 let completion = CommandCompletion::new();
498 let pattern = completion.get_pattern("Basic node query").unwrap();
499
500 let mut vars = HashMap::new();
501 vars.insert("Label".to_string(), "Person".to_string());
502
503 let expanded = completion.expand_pattern(pattern, vars);
504 assert_eq!(expanded, "MATCH (n:Person) RETURN n");
505 }
506}