1use crate::history::SearchDirection;
4use crate::Context;
5
6pub trait Hint {
8 fn display(&self) -> &str;
10 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
24pub trait Hinter {
26 type Hint: Hint + 'static;
28
29 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
51pub struct HistoryHinter {}
54
55impl Hinter for HistoryHinter {
56 type Hint = String;
57
58 fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option<String> {
59 if line.is_empty() || pos < line.len() {
60 return None;
61 }
62 let start = if ctx.history_index() == ctx.history().len() {
63 ctx.history_index().saturating_sub(1)
64 } else {
65 ctx.history_index()
66 };
67 if let Some(sr) = ctx
68 .history
69 .starts_with(line, start, SearchDirection::Reverse)
70 {
71 if sr.entry == line {
72 return None;
73 }
74 return Some(sr.entry[pos..].to_owned());
75 }
76 None
77 }
78}
79
80#[cfg(test)]
81mod test {
82 use super::{Hinter, HistoryHinter};
83 use crate::history::History;
84 use crate::Context;
85
86 #[test]
87 pub fn empty_history() {
88 let history = History::new();
89 let ctx = Context::new(&history);
90 let hinter = HistoryHinter {};
91 let hint = hinter.hint("test", 4, &ctx);
92 assert_eq!(None, hint);
93 }
94}