kure2_cli/
repl_helper.rs

1use std::{cell::RefCell, rc::Rc};
2
3use rustyline::{
4    Helper, Result,
5    completion::{Completer, FilenameCompleter, Pair},
6    highlight::Highlighter,
7    hint::Hinter,
8    validate::Validator,
9};
10
11use crate::{Repl, commands::Edge};
12
13pub struct ReplHelper {
14    filename_completer: FilenameCompleter,
15    repl: Rc<RefCell<Repl>>,
16}
17
18impl ReplHelper {
19    pub fn new(repl: Rc<RefCell<Repl>>) -> Self {
20        Self {
21            filename_completer: FilenameCompleter::new(),
22            repl,
23        }
24    }
25
26    fn complete_command(&self, line: &str, pos: usize) -> Result<(usize, Vec<Pair>)> {
27        let repl = self.repl.borrow();
28
29        let line = &line[..pos];
30        let mut args = line.split_whitespace().collect::<Vec<_>>();
31        // There is at-least one char because the line starts with a dot.
32        if char::is_whitespace(line.chars().last().unwrap()) {
33            // If the last character is whitespace, we should complete a new empty part.
34            args.push("");
35        }
36
37        // There is at-least one part because the line starts with a dot.
38        let Some(node) = repl.commands.traverse(&args[..args.len() - 1]) else {
39            return Ok((pos, Vec::new()));
40        };
41
42        let last_part = args.last().unwrap();
43        let last_part_start = line.rfind(last_part).unwrap_or(0);
44        let mut suggestions = Vec::new();
45
46        let keywords = node.next_keywords_by_prefix(last_part).map(|k| Pair {
47            display: k.to_owned(),
48            replacement: k.to_owned(),
49        });
50        suggestions.extend(keywords);
51
52        if node.edges.contains_key(&Edge::Filename) {
53            let (start, filenames) = self.filename_completer.complete_path_unsorted(line, pos)?;
54            debug_assert_eq!(start, last_part_start);
55            suggestions.extend(filenames);
56        }
57
58        if node.edges.contains_key(&Edge::Variable) {
59            // TODO: Autocomplete variable names.
60        }
61
62        suggestions.sort_by(|a, b| a.display.cmp(&b.display));
63        Ok((last_part_start, suggestions))
64    }
65}
66
67impl Completer for ReplHelper {
68    type Candidate = Pair;
69
70    fn complete(
71        &self,
72        line: &str,
73        pos: usize,
74        _ctx: &rustyline::Context,
75    ) -> Result<(usize, Vec<Self::Candidate>)> {
76        if line.starts_with('.') {
77            self.complete_command(line, pos)
78        } else {
79            // TODO: Handle variable/function name completion
80            Ok((0, Vec::new()))
81        }
82    }
83}
84
85impl Hinter for ReplHelper {
86    type Hint = String;
87}
88
89impl Highlighter for ReplHelper {}
90
91impl Validator for ReplHelper {}
92
93impl Helper for ReplHelper {}