Skip to main content

rucline/prompt/
context.rs

1use super::{Buffer, Completer, Direction, Range, Scope, Suggester, Writer};
2
3use crate::Error;
4
5pub(super) struct Context<'c, 's, C, S>
6where
7    C: Completer + ?Sized,
8    S: Suggester + ?Sized,
9{
10    writer: Writer,
11    buffer: Buffer,
12    completer: Option<&'c C>,
13    completion: Option<std::borrow::Cow<'c, str>>,
14    suggester: Option<&'s S>,
15    suggestions: Option<Suggestions<'s>>,
16}
17
18impl<'c, 's, C, S> Context<'c, 's, C, S>
19where
20    C: Completer + ?Sized,
21    S: Suggester + ?Sized,
22{
23    pub(super) fn new(
24        erase_on_drop: bool,
25        prompt: Option<&str>,
26        buffer: Option<Buffer>,
27        completer: Option<&'c C>,
28        suggester: Option<&'s S>,
29    ) -> Result<Self, Error> {
30        Ok(Self {
31            writer: Writer::new(erase_on_drop, prompt)?,
32            buffer: buffer.unwrap_or_else(Buffer::new),
33            completer,
34            completion: None,
35            suggester,
36            suggestions: None,
37        })
38    }
39
40    pub(super) fn buffer_as_string(&mut self) -> String {
41        self.try_take_suggestion();
42        self.buffer.to_string()
43    }
44
45    pub(super) fn print(&mut self) -> Result<(), Error> {
46        self.writer.print(&self.buffer, self.completion.as_deref())
47    }
48
49    pub(super) fn write(&mut self, c: char) -> Result<(), Error> {
50        self.try_take_suggestion();
51        self.buffer.write(c);
52        self.update_completion();
53        self.writer.print(&self.buffer, self.completion.as_deref())
54    }
55
56    pub(super) fn delete(&mut self, scope: Scope) -> Result<(), Error> {
57        self.try_take_suggestion();
58        self.buffer.delete(scope);
59        self.update_completion();
60        self.writer.print(&self.buffer, self.completion.as_deref())
61    }
62
63    pub(super) fn move_cursor(&mut self, range: Range, direction: Direction) -> Result<(), Error> {
64        self.try_take_suggestion();
65        self.buffer.move_cursor(range, direction);
66        self.writer.print(&self.buffer, self.completion.as_deref())
67    }
68
69    // Allowed because using map requires a `self` borrow
70    #[allow(clippy::option_if_let_else)]
71    pub(super) fn complete(&mut self, range: Range) -> Result<(), Error> {
72        self.buffer.go_to_end();
73        if let Some(completion) = &self.completion {
74            if completion.is_empty() {
75                Ok(())
76            } else {
77                self.buffer.write_range(&completion, range);
78                self.update_completion();
79                self.writer.print(&self.buffer, self.completion.as_deref())
80            }
81        } else {
82            Ok(())
83        }
84    }
85
86    fn update_completion(&mut self) {
87        if let Some(completer) = self.completer {
88            self.completion = completer.complete_for(self);
89        }
90    }
91
92    pub(super) fn suggest(&mut self, direction: Direction) -> Result<(), Error> {
93        if let Some(suggester) = &self.suggester {
94            if let Some(suggestions) = &mut self.suggestions {
95                suggestions.cycle(direction);
96                if let Some(index) = suggestions.index {
97                    return self
98                        .writer
99                        .print_suggestions(index, suggestions.options.as_ref());
100                }
101            } else {
102                let options = suggester.suggest_for(self);
103                if !options.is_empty() {
104                    self.suggestions = Some(Suggestions::new(options, direction));
105                    let suggestions = self.suggestions.as_ref().unwrap();
106                    return self
107                        .writer
108                        .print_suggestions(suggestions.index.unwrap(), &suggestions.options);
109                }
110            }
111        }
112
113        self.writer.print(&self.buffer, self.completion.as_deref())
114    }
115
116    pub(super) fn is_suggesting(&self) -> bool {
117        self.suggestions.is_some()
118    }
119
120    pub(super) fn cancel_suggestion(&mut self) -> Result<(), Error> {
121        self.suggestions = None;
122        self.writer.print(&self.buffer, self.completion.as_deref())
123    }
124
125    fn try_take_suggestion(&mut self) {
126        if let Some(suggestion) = self.suggestions.take().and_then(Suggestions::take) {
127            self.buffer = suggestion;
128        }
129    }
130}
131
132impl<C, S> std::convert::Into<Buffer> for Context<'_, '_, C, S>
133where
134    C: Completer + ?Sized,
135    S: Suggester + ?Sized,
136{
137    fn into(self) -> Buffer {
138        self.buffer
139    }
140}
141
142impl<C, S> std::ops::Deref for Context<'_, '_, C, S>
143where
144    C: Completer + ?Sized,
145    S: Suggester + ?Sized,
146{
147    type Target = Buffer;
148    fn deref(&self) -> &Self::Target {
149        &self.buffer
150    }
151}
152
153struct Suggestions<'a> {
154    index: Option<usize>,
155    options: Vec<std::borrow::Cow<'a, str>>,
156}
157
158impl<'a> Suggestions<'a> {
159    fn new(options: Vec<std::borrow::Cow<'a, str>>, direction: Direction) -> Self {
160        let index = match direction {
161            Direction::Forward => 0,
162            Direction::Backward => options.len() - 1,
163        };
164
165        Self {
166            options,
167            index: Some(index),
168        }
169    }
170
171    // Allowed because it is more readable
172    #[allow(clippy::match_same_arms)]
173    fn cycle(&mut self, direction: Direction) {
174        use Direction::{Backward, Forward};
175
176        let last_index = self.options.len() - 1;
177
178        self.index = match (direction, self.index) {
179            (Forward, None) => Some(0),
180            (Forward, Some(index)) if index < last_index => Some(index + 1),
181            (Forward, _) => None,
182            (Backward, None) => Some(last_index),
183            (Backward, Some(0)) => None,
184            (Backward, Some(index)) => Some(index - 1),
185        };
186    }
187
188    fn take(mut self) -> Option<Buffer> {
189        self.index
190            .map(|index| Buffer::from(self.options.swap_remove(index)))
191    }
192}