Skip to main content

xtask_todo_lib/devshell/completion/
helper.rs

1//! Rustyline `Helper`: `Completer`, `Hinter`, `Highlighter`, `Validator` for devshell.
2
3use std::borrow::Cow;
4use std::cell::RefCell;
5use std::rc::Rc;
6
7use rustyline::completion::Completer;
8use rustyline::highlight::Highlighter;
9use rustyline::hint::{Hint, Hinter};
10use rustyline::validate::{ValidationContext, ValidationResult, Validator};
11use rustyline::Context;
12use rustyline::Helper;
13
14use super::super::vfs::Vfs;
15use super::super::vm::SessionHolder;
16use super::candidates::{complete_commands, complete_path, list_dir_names_for_completion};
17use super::context::{completion_context, CompletionKind};
18
19/// Helper for rustyline: command and path tab-completion using shared [`Vfs`] and [`SessionHolder`].
20pub struct DevShellHelper {
21    pub vfs: Rc<RefCell<Vfs>>,
22    pub vm_session: Rc<RefCell<SessionHolder>>,
23}
24
25impl DevShellHelper {
26    #[must_use]
27    pub const fn new(vfs: Rc<RefCell<Vfs>>, vm_session: Rc<RefCell<SessionHolder>>) -> Self {
28        Self { vfs, vm_session }
29    }
30}
31
32/// Dummy hint type for Hinter; we never return a hint (`hint()` returns None).
33#[derive(Debug)]
34pub struct NoHint;
35impl Hint for NoHint {
36    fn display(&self) -> &'static str {
37        ""
38    }
39    fn completion(&self) -> Option<&str> {
40        None
41    }
42}
43
44impl Completer for DevShellHelper {
45    type Candidate = String;
46
47    fn complete(
48        &self,
49        line: &str,
50        pos: usize,
51        _ctx: &Context<'_>,
52    ) -> Result<(usize, Vec<String>), rustyline::error::ReadlineError> {
53        let Some(ctx) = completion_context(line, pos) else {
54            return Ok((pos, vec![]));
55        };
56        let candidates = match ctx.kind {
57            CompletionKind::Command => complete_commands(&ctx.prefix),
58            CompletionKind::Other => vec![],
59            CompletionKind::Path => {
60                let parent = if ctx.prefix.contains('/') {
61                    let idx = ctx.prefix.rfind('/').unwrap();
62                    if idx == 0 {
63                        "/".to_string()
64                    } else {
65                        ctx.prefix[..idx].to_string()
66                    }
67                } else {
68                    ".".to_string()
69                };
70                let abs_parent = self.vfs.borrow().resolve_to_absolute(&parent);
71                let names = list_dir_names_for_completion(&self.vfs, &self.vm_session, &abs_parent);
72                complete_path(&ctx.prefix, &names)
73            }
74        };
75        Ok((ctx.start, candidates))
76    }
77}
78
79impl Hinter for DevShellHelper {
80    type Hint = NoHint;
81
82    fn hint(&self, _line: &str, _pos: usize, _ctx: &Context<'_>) -> Option<NoHint> {
83        None
84    }
85}
86
87impl Highlighter for DevShellHelper {
88    fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
89        Cow::Borrowed(line)
90    }
91}
92
93impl Validator for DevShellHelper {
94    fn validate(
95        &self,
96        _ctx: &mut ValidationContext<'_>,
97    ) -> Result<ValidationResult, rustyline::error::ReadlineError> {
98        Ok(ValidationResult::Valid(None))
99    }
100}
101
102impl Helper for DevShellHelper {}