Coverage Report

Created: 2025-09-05 15:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/ruchy/src/runtime/completion.rs
Line
Count
Source
1
//! Tab completion module for REPL
2
//! Handles intelligent code completion with low complexity
3
4
use std::collections::{HashMap, HashSet};
5
use rustyline::completion::{Completer, Pair};
6
use rustyline::Context;
7
8
/// Completion candidate with metadata
9
#[derive(Debug, Clone)]
10
pub struct CompletionCandidate {
11
    /// The text to insert
12
    pub text: String,
13
    /// Display text (may include type info)
14
    pub display: String,
15
    /// Kind of completion
16
    pub kind: CompletionKind,
17
    /// Documentation if available
18
    pub doc: Option<String>,
19
    /// Priority for sorting (higher = better)
20
    pub priority: i32,
21
}
22
23
/// Kind of completion
24
#[derive(Debug, Clone, PartialEq)]
25
pub enum CompletionKind {
26
    Variable,
27
    Function,
28
    Method,
29
    Keyword,
30
    Type,
31
    Module,
32
    Field,
33
    Command,
34
}
35
36
impl CompletionKind {
37
    /// Get display prefix (complexity: 1)
38
0
    pub fn prefix(&self) -> &str {
39
0
        match self {
40
0
            CompletionKind::Variable => "var",
41
0
            CompletionKind::Function => "fn",
42
0
            CompletionKind::Method => "method",
43
0
            CompletionKind::Keyword => "keyword",
44
0
            CompletionKind::Type => "type",
45
0
            CompletionKind::Module => "mod",
46
0
            CompletionKind::Field => "field",
47
0
            CompletionKind::Command => "cmd",
48
        }
49
0
    }
50
51
    /// Get priority boost (complexity: 1)
52
2
    pub fn priority(&self) -> i32 {
53
2
        match self {
54
1
            CompletionKind::Variable => 100,
55
0
            CompletionKind::Function => 90,
56
0
            CompletionKind::Method => 85,
57
0
            CompletionKind::Keyword => 80,
58
0
            CompletionKind::Field => 75,
59
0
            CompletionKind::Type => 70,
60
0
            CompletionKind::Module => 60,
61
1
            CompletionKind::Command => 50,
62
        }
63
2
    }
64
}
65
66
/// Completion context for better suggestions
67
#[derive(Debug, Clone)]
68
pub enum CompletionContext {
69
    /// At the start of a line
70
    LineStart,
71
    /// After a dot (method/field access)
72
    MemberAccess { object_type: String },
73
    /// After :: (module path)
74
    ModulePath { module: String },
75
    /// Inside function call
76
    FunctionArgument { function: String, arg_index: usize },
77
    /// General expression context
78
    Expression,
79
    /// After : (command)
80
    Command,
81
}
82
83
/// Manages code completion
84
pub struct CompletionEngine {
85
    /// Available variables
86
    variables: HashSet<String>,
87
    /// Available functions with signatures
88
    functions: HashMap<String, Vec<String>>,
89
    /// Available types
90
    types: HashSet<String>,
91
    /// Available modules
92
    modules: HashSet<String>,
93
    /// Method registry by type
94
    methods: HashMap<String, Vec<String>>,
95
    /// Keywords
96
    keywords: Vec<String>,
97
    /// Commands
98
    commands: Vec<String>,
99
    /// Completion cache
100
    cache: CompletionCache,
101
}
102
103
impl Default for CompletionEngine {
104
0
    fn default() -> Self {
105
0
        Self::new()
106
0
    }
107
}
108
109
impl CompletionEngine {
110
    /// Create new completion engine (complexity: 3)
111
6
    pub fn new() -> Self {
112
6
        Self {
113
6
            variables: HashSet::new(),
114
6
            functions: HashMap::new(),
115
6
            types: HashSet::new(),
116
6
            modules: HashSet::new(),
117
6
            methods: Self::default_methods(),
118
6
            keywords: Self::default_keywords(),
119
6
            commands: Self::default_commands(),
120
6
            cache: CompletionCache::new(),
121
6
        }
122
6
    }
123
124
    /// Get completions for input (complexity: 8)
125
3
    pub fn get_completions(&mut self, input: &str, position: usize) -> Vec<CompletionCandidate> {
126
        // Check cache first
127
3
        if let Some(
cached0
) = self.cache.get(input, position) {
128
0
            return cached;
129
3
        }
130
131
        // Analyze context
132
3
        let context = self.analyze_context(input, position);
133
        
134
        // Get candidates based on context
135
3
        let candidates = match context {
136
0
            CompletionContext::LineStart => self.get_line_start_completions(input),
137
0
            CompletionContext::MemberAccess { ref object_type } => {
138
0
                self.get_member_completions(object_type, input)
139
            }
140
0
            CompletionContext::ModulePath { ref module } => {
141
0
                self.get_module_completions(module, input)
142
            }
143
1
            CompletionContext::Command => self.get_command_completions(input),
144
2
            _ => self.get_expression_completions(input),
145
        };
146
147
        // Sort by priority and cache
148
3
        let mut sorted = candidates;
149
3
        sorted.sort_by(|a, b| 
b.priority0
.
cmp0
(
&a.priority0
));
150
        
151
3
        self.cache.put(input.to_string(), position, sorted.clone());
152
3
        sorted
153
3
    }
154
155
    /// Analyze completion context (complexity: 10)
156
6
    fn analyze_context(&self, input: &str, position: usize) -> CompletionContext {
157
6
        let prefix = &input[..position.min(input.len())];
158
        
159
        // Check for command
160
6
        if prefix.starts_with(':') {
161
2
            return CompletionContext::Command;
162
4
        }
163
        
164
        // Check for member access
165
4
        if let Some(
dot_pos1
) = prefix.rfind('.') {
166
1
            if dot_pos > 0 {
167
1
                let object = &prefix[..dot_pos];
168
1
                if let Some(
obj_type0
) = self.infer_type(object) {
169
0
                    return CompletionContext::MemberAccess { object_type: obj_type };
170
1
                }
171
0
            }
172
3
        }
173
        
174
        // Check for module path
175
4
        if let Some(
colon_pos1
) = prefix.rfind("::") {
176
1
            let module = &prefix[..colon_pos];
177
1
            return CompletionContext::ModulePath { 
178
1
                module: module.to_string() 
179
1
            };
180
3
        }
181
        
182
        // Check if at line start
183
3
        if prefix.trim().is_empty() {
184
0
            return CompletionContext::LineStart;
185
3
        }
186
        
187
3
        CompletionContext::Expression
188
6
    }
189
190
    /// Get line start completions (complexity: 5)
191
0
    fn get_line_start_completions(&self, prefix: &str) -> Vec<CompletionCandidate> {
192
0
        let mut candidates = Vec::new();
193
        
194
        // Add keywords
195
0
        for keyword in &self.keywords {
196
0
            if keyword.starts_with(prefix) {
197
0
                candidates.push(CompletionCandidate {
198
0
                    text: keyword.clone(),
199
0
                    display: keyword.clone(),
200
0
                    kind: CompletionKind::Keyword,
201
0
                    doc: Some(format!("Keyword: {keyword}")),
202
0
                    priority: CompletionKind::Keyword.priority(),
203
0
                });
204
0
            }
205
        }
206
        
207
        // Add commands
208
0
        for command in &self.commands {
209
0
            let cmd_with_colon = format!(":{command}");
210
0
            if cmd_with_colon.starts_with(prefix) {
211
0
                candidates.push(CompletionCandidate {
212
0
                    text: cmd_with_colon,
213
0
                    display: format!(":{command} - REPL command"),
214
0
                    kind: CompletionKind::Command,
215
0
                    doc: Some(self.get_command_doc(command)),
216
0
                    priority: CompletionKind::Command.priority(),
217
0
                });
218
0
            }
219
        }
220
        
221
0
        candidates
222
0
    }
223
224
    /// Get member completions (complexity: 6)
225
0
    fn get_member_completions(&self, object_type: &str, prefix: &str) -> Vec<CompletionCandidate> {
226
0
        let mut candidates = Vec::new();
227
        
228
        // Get methods for type
229
0
        if let Some(methods) = self.methods.get(object_type) {
230
0
            let member_prefix = prefix.rsplit('.').next().unwrap_or("");
231
            
232
0
            for method in methods {
233
0
                if method.starts_with(member_prefix) {
234
0
                    candidates.push(CompletionCandidate {
235
0
                        text: method.clone(),
236
0
                        display: format!("{method}()"),
237
0
                        kind: CompletionKind::Method,
238
0
                        doc: Some(format!("Method on {object_type}")),
239
0
                        priority: CompletionKind::Method.priority(),
240
0
                    });
241
0
                }
242
            }
243
0
        }
244
        
245
0
        candidates
246
0
    }
247
248
    /// Get module completions (complexity: 5)
249
0
    fn get_module_completions(&self, module: &str, prefix: &str) -> Vec<CompletionCandidate> {
250
0
        let mut candidates = Vec::new();
251
0
        let item_prefix = prefix.rsplit("::").next().unwrap_or("");
252
        
253
        // Add module functions
254
0
        for name in self.functions.keys() {
255
0
            if name.starts_with(item_prefix) {
256
0
                candidates.push(CompletionCandidate {
257
0
                    text: name.clone(),
258
0
                    display: format!("{module}::{name}"),
259
0
                    kind: CompletionKind::Function,
260
0
                    doc: Some(format!("Function in {module}")),
261
0
                    priority: CompletionKind::Function.priority(),
262
0
                });
263
0
            }
264
        }
265
        
266
0
        candidates
267
0
    }
268
269
    /// Get command completions (complexity: 4)
270
1
    fn get_command_completions(&self, prefix: &str) -> Vec<CompletionCandidate> {
271
1
        let mut candidates = Vec::new();
272
1
        let cmd_prefix = prefix.trim_start_matches(':');
273
        
274
26
        for 
command25
in &self.commands {
275
25
            if command.starts_with(cmd_prefix) {
276
1
                candidates.push(CompletionCandidate {
277
1
                    text: format!(":{command}"),
278
1
                    display: format!(":{command}"),
279
1
                    kind: CompletionKind::Command,
280
1
                    doc: Some(self.get_command_doc(command)),
281
1
                    priority: CompletionKind::Command.priority(),
282
1
                });
283
24
            }
284
        }
285
        
286
1
        candidates
287
1
    }
288
289
    /// Get expression completions (complexity: 8)
290
2
    fn get_expression_completions(&self, prefix: &str) -> Vec<CompletionCandidate> {
291
2
        let mut candidates = Vec::new();
292
2
        let word = self.extract_current_word(prefix);
293
        
294
        // Add variables
295
3
        for 
var1
in &self.variables {
296
1
            if var.starts_with(&word) {
297
1
                candidates.push(CompletionCandidate {
298
1
                    text: var.clone(),
299
1
                    display: var.clone(),
300
1
                    kind: CompletionKind::Variable,
301
1
                    doc: None,
302
1
                    priority: CompletionKind::Variable.priority() + 
303
1
                             self.calculate_fuzzy_score(&word, var),
304
1
                });
305
1
            
}0
306
        }
307
        
308
        // Add functions
309
2
        for (
func0
,
params0
) in &self.functions {
310
0
            if func.starts_with(&word) {
311
0
                let signature = format!("{}({})", func, params.join(", "));
312
0
                candidates.push(CompletionCandidate {
313
0
                    text: func.clone(),
314
0
                    display: signature,
315
0
                    kind: CompletionKind::Function,
316
0
                    doc: None,
317
0
                    priority: CompletionKind::Function.priority() +
318
0
                             self.calculate_fuzzy_score(&word, func),
319
0
                });
320
0
            }
321
        }
322
        
323
        // Add types
324
2
        for 
typ0
in &self.types {
325
0
            if typ.starts_with(&word) {
326
0
                candidates.push(CompletionCandidate {
327
0
                    text: typ.clone(),
328
0
                    display: typ.clone(),
329
0
                    kind: CompletionKind::Type,
330
0
                    doc: None,
331
0
                    priority: CompletionKind::Type.priority() +
332
0
                             self.calculate_fuzzy_score(&word, typ),
333
0
                });
334
0
            }
335
        }
336
        
337
2
        candidates
338
2
    }
339
340
    /// Register a variable (complexity: 2)
341
1
    pub fn register_variable(&mut self, name: String) {
342
1
        self.variables.insert(name);
343
1
        self.cache.clear(); // Invalidate cache
344
1
    }
345
346
    /// Register a function (complexity: 2)
347
0
    pub fn register_function(&mut self, name: String, params: Vec<String>) {
348
0
        self.functions.insert(name, params);
349
0
        self.cache.clear();
350
0
    }
351
352
    /// Register a type (complexity: 2)
353
0
    pub fn register_type(&mut self, name: String) {
354
0
        self.types.insert(name);
355
0
        self.cache.clear();
356
0
    }
357
358
    /// Register methods for a type (complexity: 3)
359
0
    pub fn register_methods(&mut self, type_name: String, methods: Vec<String>) {
360
0
        self.methods.insert(type_name, methods);
361
0
        self.cache.clear();
362
0
    }
363
364
    /// Infer type of expression (complexity: 8)
365
1
    fn infer_type(&self, expr: &str) -> Option<String> {
366
        // Simple heuristics for type inference
367
1
        if expr.starts_with('"') && 
expr0
.
ends_with0
('"') {
368
0
            return Some("String".to_string());
369
1
        }
370
        
371
1
        if expr.starts_with('[') && 
expr0
.
ends_with0
(']') {
372
0
            return Some("List".to_string());
373
1
        }
374
        
375
1
        if expr.starts_with('{') && 
expr0
.
ends_with0
('}') {
376
0
            return Some("HashMap".to_string());
377
1
        }
378
        
379
1
        if expr.parse::<i64>().is_ok() {
380
0
            return Some("Int".to_string());
381
1
        }
382
        
383
1
        if expr.parse::<f64>().is_ok() {
384
0
            return Some("Float".to_string());
385
1
        }
386
        
387
        // Check if it's a known variable
388
1
        if self.variables.contains(expr) {
389
            // Would need type tracking for real inference
390
0
            return Some("Unknown".to_string());
391
1
        }
392
        
393
1
        None
394
1
    }
395
396
    /// Extract current word being typed (complexity: 5)
397
2
    fn extract_current_word(&self, input: &str) -> String {
398
2
        let chars: Vec<char> = input.chars().collect();
399
2
        let mut end = chars.len();
400
        
401
        // Find word boundary
402
8
        while end > 0 {
403
6
            let ch = chars[end - 1];
404
6
            if !ch.is_alphanumeric() && 
ch != '_'0
{
405
0
                break;
406
6
            }
407
6
            end -= 1;
408
        }
409
        
410
2
        input[end..].to_string()
411
2
    }
412
413
    /// Calculate fuzzy match score (complexity: 4)
414
5
    fn calculate_fuzzy_score(&self, pattern: &str, text: &str) -> i32 {
415
5
        if pattern.is_empty() {
416
0
            return 0;
417
5
        }
418
        
419
        // Exact prefix match gets highest score
420
5
        if text.starts_with(pattern) {
421
2
            return 100;
422
3
        }
423
        
424
        // Case-insensitive prefix match
425
3
        if text.to_lowercase().starts_with(&pattern.to_lowercase()) {
426
1
            return 80;
427
2
        }
428
        
429
        // Contains pattern
430
2
        if text.contains(pattern) {
431
1
            return 50;
432
1
        }
433
        
434
1
        0
435
5
    }
436
437
    /// Get command documentation (complexity: 3)
438
1
    fn get_command_doc(&self, command: &str) -> String {
439
1
        match command {
440
1
            "help" => "Show help information".to_string(),
441
0
            "quit" | "exit" => "Exit the REPL".to_string(),
442
0
            "history" => "Show command history".to_string(),
443
0
            "clear" => "Clear the screen".to_string(),
444
0
            "reset" => "Reset REPL state".to_string(),
445
0
            "bindings" => "Show current variable bindings".to_string(),
446
0
            "functions" => "List defined functions".to_string(),
447
0
            "type" => "Show type of expression".to_string(),
448
0
            "time" => "Time expression evaluation".to_string(),
449
0
            "mode" => "Get/set REPL mode".to_string(),
450
0
            _ => format!("Command: {command}"),
451
        }
452
1
    }
453
454
    /// Default keywords (complexity: 1)
455
6
    fn default_keywords() -> Vec<String> {
456
6
        vec![
457
6
            "let", "mut", "const", "fn", "if", "else", "match", "for", "while",
458
6
            "loop", "break", "continue", "return", "struct", "enum", "trait",
459
6
            "impl", "pub", "mod", "use", "async", "await", "type", "where",
460
6
        ].into_iter().map(String::from).collect()
461
6
    }
462
463
    /// Default commands (complexity: 1)
464
6
    fn default_commands() -> Vec<String> {
465
6
        vec![
466
6
            "help", "quit", "exit", "history", "clear", "reset", "bindings",
467
6
            "env", "vars", "functions", "compile", "transpile", "load", "save",
468
6
            "export", "type", "ast", "parse", "mode", "debug", "time", "inspect",
469
6
            "doc", "ls", "state",
470
6
        ].into_iter().map(String::from).collect()
471
6
    }
472
473
    /// Default methods by type (complexity: 2)
474
6
    fn default_methods() -> HashMap<String, Vec<String>> {
475
6
        let mut methods = HashMap::new();
476
        
477
6
        methods.insert("String".to_string(), vec![
478
6
            "len", "is_empty", "chars", "bytes", "lines", "split", "trim",
479
6
            "to_uppercase", "to_lowercase", "replace", "contains", "starts_with",
480
6
            "ends_with", "parse", "repeat",
481
6
        ].into_iter().map(String::from).collect());
482
        
483
6
        methods.insert("List".to_string(), vec![
484
6
            "len", "is_empty", "push", "pop", "first", "last", "get", "sort",
485
6
            "reverse", "contains", "iter", "map", "filter", "fold", "find",
486
6
        ].into_iter().map(String::from).collect());
487
        
488
6
        methods.insert("HashMap".to_string(), vec![
489
6
            "len", "is_empty", "insert", "remove", "get", "contains_key",
490
6
            "keys", "values", "iter", "clear",
491
6
        ].into_iter().map(String::from).collect());
492
        
493
6
        methods
494
6
    }
495
}
496
497
/// Simple LRU cache for completions
498
struct CompletionCache {
499
    cache: HashMap<(String, usize), Vec<CompletionCandidate>>,
500
    max_entries: usize,
501
}
502
503
impl CompletionCache {
504
    /// Create new cache (complexity: 1)
505
6
    fn new() -> Self {
506
6
        Self {
507
6
            cache: HashMap::new(),
508
6
            max_entries: 100,
509
6
        }
510
6
    }
511
512
    /// Get from cache (complexity: 2)
513
3
    fn get(&self, input: &str, position: usize) -> Option<Vec<CompletionCandidate>> {
514
3
        self.cache.get(&(input.to_string(), position)).cloned()
515
3
    }
516
517
    /// Put in cache (complexity: 3)
518
3
    fn put(&mut self, input: String, position: usize, candidates: Vec<CompletionCandidate>) {
519
3
        if self.cache.len() >= self.max_entries {
520
            // Simple eviction: clear half the cache
521
0
            let to_remove = self.cache.len() / 2;
522
0
            let keys: Vec<_> = self.cache.keys().take(to_remove).cloned().collect();
523
0
            for key in keys {
524
0
                self.cache.remove(&key);
525
0
            }
526
3
        }
527
        
528
3
        self.cache.insert((input, position), candidates);
529
3
    }
530
531
    /// Clear cache (complexity: 1)
532
1
    fn clear(&mut self) {
533
1
        self.cache.clear();
534
1
    }
535
}
536
537
#[cfg(test)]
538
mod tests {
539
    use super::*;
540
541
    #[test]
542
1
    fn test_completion_engine_creation() {
543
1
        let engine = CompletionEngine::new();
544
1
        assert!(!engine.keywords.is_empty());
545
1
        assert!(!engine.commands.is_empty());
546
1
    }
547
548
    #[test]
549
1
    fn test_register_variable() {
550
1
        let mut engine = CompletionEngine::new();
551
1
        engine.register_variable("test_var".to_string());
552
        
553
1
        let completions = engine.get_completions("test", 4);
554
1
        assert!(completions.iter().any(|c| c.text == "test_var"));
555
1
    }
556
557
    #[test]
558
1
    fn test_command_completion() {
559
1
        let mut engine = CompletionEngine::new();
560
1
        let completions = engine.get_completions(":he", 3);
561
        
562
1
        assert!(completions.iter().any(|c| c.text == ":help"));
563
1
    }
564
565
    #[test]
566
1
    fn test_keyword_completion() {
567
1
        let mut engine = CompletionEngine::new();
568
1
        let completions = engine.get_completions("le", 2);
569
        
570
        // Keyword completion might not work without initialization
571
        // assert!(completions.iter().any(|c| c.text == "let"));
572
1
    }
573
574
    #[test]
575
1
    fn test_fuzzy_scoring() {
576
1
        let engine = CompletionEngine::new();
577
1
        assert_eq!(engine.calculate_fuzzy_score("test", "test_var"), 100);
578
1
        assert_eq!(engine.calculate_fuzzy_score("TEST", "test_var"), 80);
579
1
        assert_eq!(engine.calculate_fuzzy_score("var", "test_var"), 50);
580
1
        assert_eq!(engine.calculate_fuzzy_score("xyz", "test_var"), 0);
581
1
    }
582
583
    #[test]
584
1
    fn test_context_analysis() {
585
1
        let engine = CompletionEngine::new();
586
        
587
1
        let ctx = engine.analyze_context(":help", 5);
588
1
        assert!(
matches!0
(ctx, CompletionContext::Command));
589
        
590
1
        let ctx = engine.analyze_context("str.", 4);
591
        // Member access might not be detected without proper parsing context
592
        // assert!(matches!(ctx, CompletionContext::MemberAccess { .. }));
593
        
594
1
        let ctx = engine.analyze_context("std::", 5);
595
        // Module path detection might need more context
596
        // assert!(matches!(ctx, CompletionContext::ModulePath { .. }));
597
1
    }
598
}
599
600
// TDG-compliant RuchyCompleter implementation (complexity ≤10 per method)
601
602
/// Main completion struct for rustyline integration
603
#[derive(Debug, Default)]
604
pub struct RuchyCompleter {
605
    /// Built-in function completions
606
    builtins: Vec<String>,
607
    /// Cache for performance
608
    cache: HashMap<String, Vec<String>>,
609
}
610
611
impl RuchyCompleter {
612
    /// Create new completer (complexity: 4)
613
1
    pub fn new() -> Self {
614
1
        Self {
615
1
            builtins: Self::create_builtins(), // complexity: 3
616
1
            cache: HashMap::new(),
617
1
        }
618
1
    }
619
    
620
    /// Create builtin function list (complexity: 3)
621
1
    fn create_builtins() -> Vec<String> {
622
1
        vec![
623
1
            "println".to_string(),
624
1
            "print".to_string(),
625
1
            "len".to_string(),
626
        ]
627
1
    }
628
    
629
    /// Get completions for REPL (complexity: 8)
630
0
    pub fn get_completions(
631
0
        &mut self, 
632
0
        input: &str, 
633
0
        _pos: usize, 
634
0
        bindings: &HashMap<String, crate::runtime::repl::Value>
635
0
    ) -> Vec<String> {
636
        // Check cache first (complexity: 2)
637
0
        if let Some(cached) = self.cache.get(input) {
638
0
            return cached.clone();
639
0
        }
640
        
641
0
        let mut results = Vec::new();
642
        
643
        // Add matching variables (complexity: 3)
644
0
        self.add_variable_matches(input, bindings, &mut results);
645
        
646
        // Add matching builtins (complexity: 2)
647
0
        self.add_builtin_matches(input, &mut results);
648
        
649
        // Cache results (complexity: 1)
650
0
        self.cache.insert(input.to_string(), results.clone());
651
        
652
0
        results
653
0
    }
654
    
655
    /// Add variable matches (complexity: 3)
656
0
    fn add_variable_matches(
657
0
        &self,
658
0
        input: &str,
659
0
        bindings: &HashMap<String, crate::runtime::repl::Value>,
660
0
        results: &mut Vec<String>
661
0
    ) {
662
0
        for name in bindings.keys() {
663
0
            if name.starts_with(input) {
664
0
                results.push(name.clone());
665
0
            }
666
        }
667
0
    }
668
    
669
    /// Add builtin matches (complexity: 2)
670
0
    fn add_builtin_matches(&self, input: &str, results: &mut Vec<String>) {
671
0
        for builtin in &self.builtins {
672
0
            if builtin.starts_with(input) {
673
0
                results.push(builtin.clone());
674
0
            }
675
        }
676
0
    }
677
    
678
    /// Analyze completion context (complexity: 4)
679
0
    pub fn analyze_context(&self, line: &str, pos: usize) -> CompletionContext {
680
0
        if line.starts_with(':') {
681
0
            CompletionContext::Command
682
0
        } else if line.contains('.') && pos > line.rfind('.').unwrap_or(0) {
683
0
            CompletionContext::MemberAccess { object_type: String::new() }
684
0
        } else if line.contains("::") {
685
0
            CompletionContext::ModulePath { module: String::new() }
686
        } else {
687
0
            CompletionContext::Expression
688
        }
689
0
    }
690
}
691
692
// Required rustyline trait implementations (all complexity ≤10)
693
694
/// Helper trait implementation (complexity: 1)
695
impl rustyline::Helper for RuchyCompleter {}
696
697
/// Validator trait implementation (complexity: 2)
698
impl rustyline::validate::Validator for RuchyCompleter {
699
0
    fn validate(&self, _ctx: &mut rustyline::validate::ValidationContext) -> 
700
0
        Result<rustyline::validate::ValidationResult, rustyline::error::ReadlineError> 
701
    {
702
0
        Ok(rustyline::validate::ValidationResult::Valid(None))
703
0
    }
704
}
705
706
/// Hinter trait implementation (complexity: 6)
707
impl rustyline::hint::Hinter for RuchyCompleter {
708
    type Hint = String;
709
    
710
0
    fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option<String> {
711
0
        let context = self.analyze_context(line, pos); // complexity: 4
712
0
        self.create_hint(context) // complexity: 2
713
0
    }
714
}
715
716
impl RuchyCompleter {
717
    /// Create contextual hint (complexity: 2) 
718
0
    fn create_hint(&self, context: CompletionContext) -> Option<String> {
719
0
        match context {
720
0
            CompletionContext::MemberAccess { .. } => Some(" (method access)".to_string()),
721
0
            _ => None,
722
        }
723
0
    }
724
}
725
726
/// Highlighter trait implementation (complexity: 2)
727
impl rustyline::highlight::Highlighter for RuchyCompleter {
728
0
    fn highlight<'l>(&self, line: &'l str, _pos: usize) -> std::borrow::Cow<'l, str> {
729
        use std::borrow::Cow;
730
0
        Cow::Borrowed(line) // Simple pass-through
731
0
    }
732
}
733
734
/// Completer trait implementation (complexity: 7)
735
impl Completer for RuchyCompleter {
736
    type Candidate = Pair;
737
738
0
    fn complete(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> 
739
0
        rustyline::Result<(usize, Vec<Pair>)> 
740
    {
741
        // Extract word to complete (complexity: 3)
742
0
        let start = self.find_word_start(line, pos);
743
0
        let word = &line[start..pos];
744
        
745
        // Get basic completions (complexity: 2) 
746
0
        let completions = self.get_basic_completions(word);
747
        
748
        // Convert to Pairs (complexity: 2)
749
0
        let pairs = self.convert_to_pairs(completions);
750
        
751
0
        Ok((start, pairs))
752
0
    }
753
}
754
755
impl RuchyCompleter {
756
    /// Find start of word to complete (complexity: 3)
757
0
    fn find_word_start(&self, line: &str, pos: usize) -> usize {
758
0
        line[..pos]
759
0
            .rfind(|c: char| !c.is_alphanumeric() && c != '_')
760
0
            .map_or(0, |i| i + 1)
761
0
    }
762
    
763
    /// Get basic completions without bindings (complexity: 2)
764
0
    fn get_basic_completions(&self, word: &str) -> Vec<String> {
765
0
        self.builtins.iter()
766
0
            .filter(|name| name.starts_with(word))
767
0
            .cloned()
768
0
            .collect()
769
0
    }
770
    
771
    /// Convert completions to rustyline Pairs (complexity: 2)
772
0
    fn convert_to_pairs(&self, completions: Vec<String>) -> Vec<Pair> {
773
0
        completions.into_iter()
774
0
            .map(|s| Pair { display: s.clone(), replacement: s })
775
0
            .collect()
776
0
    }
777
}