rustyline/
hint.rs

1//! Hints (suggestions at the right of the prompt as you type).
2
3use crate::history::SearchDirection;
4use crate::Context;
5
6/// A hint returned by Hinter
7pub trait Hint {
8    /// Text to display when hint is active
9    fn display(&self) -> &str;
10    /// Text to insert in line when right arrow is pressed
11    fn completion(&self) -> Option<&str>;
12}
13
14impl Hint for String {
15    fn display(&self) -> &str {
16        self.as_str()
17    }
18
19    fn completion(&self) -> Option<&str> {
20        Some(self.as_str())
21    }
22}
23
24/// Hints provider
25pub trait Hinter {
26    /// Specific hint type
27    type Hint: Hint + 'static;
28
29    /// Takes the currently edited `line` with the cursor `pos`ition and
30    /// returns the string that should be displayed or `None`
31    /// if no hint is available for the text the user currently typed.
32    // TODO Validate: called while editing line but not while moving cursor.
33    fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option<Self::Hint> {
34        let _ = (line, pos, ctx);
35        None
36    }
37}
38
39impl Hinter for () {
40    type Hint = String;
41}
42
43impl<'r, H: ?Sized + Hinter> Hinter for &'r H {
44    type Hint = H::Hint;
45
46    fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option<Self::Hint> {
47        (**self).hint(line, pos, ctx)
48    }
49}
50
51/// Add suggestion based on previous history entries matching current user
52/// input.
53#[derive(Default)]
54pub struct HistoryHinter {}
55
56impl HistoryHinter {
57    /// Create a new `HistoryHinter`
58    pub fn new() -> HistoryHinter {
59        HistoryHinter::default()
60    }
61}
62
63impl Hinter for HistoryHinter {
64    type Hint = String;
65
66    fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option<String> {
67        if line.is_empty() || pos < line.len() {
68            return None;
69        }
70        let start = if ctx.history_index() == ctx.history().len() {
71            ctx.history_index().saturating_sub(1)
72        } else {
73            ctx.history_index()
74        };
75        if let Some(sr) = ctx
76            .history
77            .starts_with(line, start, SearchDirection::Reverse)
78            .unwrap_or(None)
79        {
80            if sr.entry == line {
81                return None;
82            }
83            return Some(sr.entry[pos..].to_owned());
84        }
85        None
86    }
87}
88
89#[cfg(test)]
90mod test {
91    use super::{Hinter, HistoryHinter};
92    use crate::history::DefaultHistory;
93    use crate::Context;
94
95    #[test]
96    pub fn empty_history() {
97        let history = DefaultHistory::new();
98        let ctx = Context::new(&history);
99        let hinter = HistoryHinter {};
100        let hint = hinter.hint("test", 4, &ctx);
101        assert_eq!(None, hint);
102    }
103}