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 #[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 #[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}