this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 506 lines 22 kB view raw
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}