ricecoder_completion/
ghost_text_state.rs1use crate::types::GhostText;
6
7#[derive(Debug, Clone, PartialEq, Eq, Default)]
9pub enum GhostTextState {
10 #[default]
12 Dismissed,
13 Displayed(GhostText),
15 Accepted(GhostText),
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum PartialAcceptanceMode {
22 Full,
24 Word,
26 Line,
28 Characters(usize),
30}
31
32pub trait GhostTextStateManager: Send + Sync {
34 fn display(&mut self, ghost_text: GhostText);
36
37 fn dismiss(&mut self);
39
40 fn accept(&mut self) -> Option<GhostText>;
42
43 fn accept_partial(&mut self, mode: PartialAcceptanceMode) -> Option<String>;
45
46 fn update(&mut self, new_ghost_text: GhostText);
48
49 fn get_state(&self) -> &GhostTextState;
51
52 fn is_displayed(&self) -> bool;
54}
55
56pub struct BasicGhostTextStateManager {
58 state: GhostTextState,
59}
60
61impl BasicGhostTextStateManager {
62 pub fn new() -> Self {
63 Self {
64 state: GhostTextState::Dismissed,
65 }
66 }
67}
68
69impl Default for BasicGhostTextStateManager {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl GhostTextStateManager for BasicGhostTextStateManager {
76 fn display(&mut self, ghost_text: GhostText) {
77 self.state = GhostTextState::Displayed(ghost_text);
78 }
79
80 fn dismiss(&mut self) {
81 self.state = GhostTextState::Dismissed;
82 }
83
84 fn accept(&mut self) -> Option<GhostText> {
85 match self.state.clone() {
86 GhostTextState::Displayed(ghost_text) => {
87 self.state = GhostTextState::Accepted(ghost_text.clone());
88 Some(ghost_text)
89 }
90 _ => None,
91 }
92 }
93
94 fn accept_partial(&mut self, mode: PartialAcceptanceMode) -> Option<String> {
95 match &self.state {
96 GhostTextState::Displayed(ghost_text) => {
97 let text = match mode {
98 PartialAcceptanceMode::Full => ghost_text.text.clone(),
99 PartialAcceptanceMode::Word => {
100 ghost_text
102 .text
103 .split_whitespace()
104 .next()
105 .unwrap_or("")
106 .to_string()
107 }
108 PartialAcceptanceMode::Line => {
109 ghost_text.text.lines().next().unwrap_or("").to_string()
111 }
112 PartialAcceptanceMode::Characters(n) => {
113 ghost_text.text.chars().take(n).collect()
114 }
115 };
116 Some(text)
117 }
118 _ => None,
119 }
120 }
121
122 fn update(&mut self, new_ghost_text: GhostText) {
123 if matches!(self.state, GhostTextState::Displayed(_)) {
124 self.state = GhostTextState::Displayed(new_ghost_text);
125 }
126 }
127
128 fn get_state(&self) -> &GhostTextState {
129 &self.state
130 }
131
132 fn is_displayed(&self) -> bool {
133 matches!(self.state, GhostTextState::Displayed(_))
134 }
135}
136
137pub trait GhostTextKeyHandler: Send + Sync {
139 fn handle_tab(&mut self) -> Option<String>;
141
142 fn handle_escape(&mut self);
144
145 fn handle_character_input(&mut self, _char: char) {
147 self.handle_escape();
149 }
150}
151
152pub struct BasicGhostTextKeyHandler {
154 state_manager: Box<dyn GhostTextStateManager>,
155}
156
157impl BasicGhostTextKeyHandler {
158 pub fn new(state_manager: Box<dyn GhostTextStateManager>) -> Self {
159 Self { state_manager }
160 }
161}
162
163impl GhostTextKeyHandler for BasicGhostTextKeyHandler {
164 fn handle_tab(&mut self) -> Option<String> {
165 self.state_manager.accept().map(|gt| gt.text)
166 }
167
168 fn handle_escape(&mut self) {
169 self.state_manager.dismiss();
170 }
171
172 fn handle_character_input(&mut self, _char: char) {
173 self.handle_escape();
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::types::{Position, Range};
182
183 #[test]
184 fn test_ghost_text_state_display() {
185 let mut manager = BasicGhostTextStateManager::new();
186 let ghost_text = GhostText::new(
187 "test".to_string(),
188 Range::new(Position::new(0, 0), Position::new(0, 4)),
189 );
190
191 manager.display(ghost_text.clone());
192 assert!(manager.is_displayed());
193 assert_eq!(manager.get_state(), &GhostTextState::Displayed(ghost_text));
194 }
195
196 #[test]
197 fn test_ghost_text_state_dismiss() {
198 let mut manager = BasicGhostTextStateManager::new();
199 let ghost_text = GhostText::new(
200 "test".to_string(),
201 Range::new(Position::new(0, 0), Position::new(0, 4)),
202 );
203
204 manager.display(ghost_text);
205 assert!(manager.is_displayed());
206
207 manager.dismiss();
208 assert!(!manager.is_displayed());
209 assert_eq!(manager.get_state(), &GhostTextState::Dismissed);
210 }
211
212 #[test]
213 fn test_ghost_text_state_accept() {
214 let mut manager = BasicGhostTextStateManager::new();
215 let ghost_text = GhostText::new(
216 "test".to_string(),
217 Range::new(Position::new(0, 0), Position::new(0, 4)),
218 );
219
220 manager.display(ghost_text.clone());
221 let accepted = manager.accept();
222
223 assert!(accepted.is_some());
224 assert_eq!(accepted.unwrap().text, "test");
225 assert_eq!(manager.get_state(), &GhostTextState::Accepted(ghost_text));
226 }
227
228 #[test]
229 fn test_ghost_text_partial_acceptance_word() {
230 let mut manager = BasicGhostTextStateManager::new();
231 let ghost_text = GhostText::new(
232 "hello world".to_string(),
233 Range::new(Position::new(0, 0), Position::new(0, 11)),
234 );
235
236 manager.display(ghost_text);
237 let partial = manager.accept_partial(PartialAcceptanceMode::Word);
238
239 assert_eq!(partial, Some("hello".to_string()));
240 }
241
242 #[test]
243 fn test_ghost_text_partial_acceptance_line() {
244 let mut manager = BasicGhostTextStateManager::new();
245 let ghost_text = GhostText::new(
246 "hello\nworld".to_string(),
247 Range::new(Position::new(0, 0), Position::new(1, 5)),
248 );
249
250 manager.display(ghost_text);
251 let partial = manager.accept_partial(PartialAcceptanceMode::Line);
252
253 assert_eq!(partial, Some("hello".to_string()));
254 }
255
256 #[test]
257 fn test_ghost_text_partial_acceptance_characters() {
258 let mut manager = BasicGhostTextStateManager::new();
259 let ghost_text = GhostText::new(
260 "hello world".to_string(),
261 Range::new(Position::new(0, 0), Position::new(0, 11)),
262 );
263
264 manager.display(ghost_text);
265 let partial = manager.accept_partial(PartialAcceptanceMode::Characters(5));
266
267 assert_eq!(partial, Some("hello".to_string()));
268 }
269
270 #[test]
271 fn test_ghost_text_update() {
272 let mut manager = BasicGhostTextStateManager::new();
273 let ghost_text1 = GhostText::new(
274 "test".to_string(),
275 Range::new(Position::new(0, 0), Position::new(0, 4)),
276 );
277 let ghost_text2 = GhostText::new(
278 "updated".to_string(),
279 Range::new(Position::new(0, 0), Position::new(0, 7)),
280 );
281
282 manager.display(ghost_text1);
283 manager.update(ghost_text2.clone());
284
285 assert_eq!(manager.get_state(), &GhostTextState::Displayed(ghost_text2));
286 }
287
288 #[test]
289 fn test_key_handler_tab() {
290 let state_manager = Box::new(BasicGhostTextStateManager::new());
291 let mut handler = BasicGhostTextKeyHandler::new(state_manager);
292
293 let result = handler.handle_tab();
296 assert_eq!(result, None); }
298
299 #[test]
300 fn test_key_handler_escape() {
301 let state_manager = Box::new(BasicGhostTextStateManager::new());
302 let mut handler = BasicGhostTextKeyHandler::new(state_manager);
303
304 handler.handle_escape();
306 }
307}