Skip to main content

kure2_cli/
repl_helper.rs

1use std::{cell::RefCell, rc::Rc};
2
3use kure2::lang;
4use rustyline::{
5    Helper, Result,
6    completion::{Completer, FilenameCompleter, Pair},
7    highlight::Highlighter,
8    hint::Hinter,
9    validate::Validator,
10};
11
12use crate::{Repl, commands::Edge};
13
14pub struct ReplHelper {
15    filename_completer: FilenameCompleter,
16    repl: Rc<RefCell<Repl>>,
17}
18
19impl ReplHelper {
20    pub fn new(repl: Rc<RefCell<Repl>>) -> Self {
21        Self {
22            filename_completer: FilenameCompleter::new(),
23            repl,
24        }
25    }
26
27    fn complete_command(&self, line: &str, pos: usize) -> Result<(usize, Vec<Pair>)> {
28        let repl = self.repl.borrow();
29
30        let line = &line[..pos];
31        let mut args = line.split_whitespace().collect::<Vec<_>>();
32        // There is at-least one char because the line starts with a dot.
33        if char::is_whitespace(line.chars().last().unwrap()) {
34            // If the last character is whitespace, we should complete a new empty part.
35            args.push("");
36        }
37
38        // There is at-least one part because the line starts with a dot.
39        let Some(node) = repl.commands.traverse(&args[..args.len() - 1]) else {
40            return Ok((pos, Vec::new()));
41        };
42
43        let last_part = args.last().unwrap();
44        let last_part_start = line.rfind(last_part).unwrap_or(0);
45        let mut suggestions = Vec::new();
46
47        let keywords = node.next_keywords_by_prefix(last_part).map(|k| Pair {
48            display: k.to_owned(),
49            replacement: k.to_owned(),
50        });
51        suggestions.extend(keywords);
52
53        if node.edges.contains_key(&Edge::Filename) {
54            let (start, filenames) = self.filename_completer.complete_path_unsorted(line, pos)?;
55            debug_assert_eq!(start, last_part_start);
56            suggestions.extend(filenames);
57        }
58
59        if node.edges.contains_key(&Edge::Variable) {
60            let variables = repl.state.list_relations();
61            suggestions.extend(
62                variables
63                    .into_iter()
64                    .filter(|v| v.starts_with(last_part))
65                    .map(|v| Pair {
66                        display: v.clone(),
67                        replacement: v,
68                    }),
69            );
70        }
71
72        suggestions.sort_by(|a, b| a.display.cmp(&b.display));
73        Ok((last_part_start, suggestions))
74    }
75
76    fn complete_statement(&self, line: &str, pos: usize) -> Result<(usize, Vec<Pair>)> {
77        let mut word_start = pos;
78        while let Some(c) = line[..word_start]
79            .chars()
80            .last()
81            .filter(|&c| c.is_alphanumeric() || "-_".contains(c))
82        {
83            word_start -= c.len_utf8();
84        }
85        let prefix = &line[word_start..pos];
86
87        let repl = self.repl.borrow();
88        let mut suggestions = lang::list_builtins()
89            .into_iter()
90            .chain(repl.state.list_programs())
91            .chain(repl.state.list_relations())
92            .filter(|a| a.starts_with(prefix))
93            .map(|a| Pair {
94                display: a.clone(),
95                replacement: a,
96            })
97            .collect::<Vec<_>>();
98        suggestions.sort_by_key(|a| a.replacement.clone());
99
100        Ok((word_start, suggestions))
101    }
102}
103
104impl Completer for ReplHelper {
105    type Candidate = Pair;
106
107    fn complete(
108        &self,
109        line: &str,
110        pos: usize,
111        _ctx: &rustyline::Context,
112    ) -> Result<(usize, Vec<Self::Candidate>)> {
113        if line.starts_with('.') {
114            self.complete_command(line, pos)
115        } else {
116            self.complete_statement(line, pos)
117        }
118    }
119}
120
121impl Hinter for ReplHelper {
122    type Hint = String;
123}
124
125impl Highlighter for ReplHelper {}
126
127impl Validator for ReplHelper {}
128
129impl Helper for ReplHelper {}