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 if char::is_whitespace(line.chars().last().unwrap()) {
34 args.push("");
36 }
37
38 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 {}