ricecoder_completion/
ghost_text_state.rs

1/// Ghost text state management for acceptance and dismissal
2///
3/// Manages the lifecycle of ghost text suggestions, including acceptance,
4/// dismissal, and updates based on context changes.
5use crate::types::GhostText;
6
7/// Represents the state of ghost text
8#[derive(Debug, Clone, PartialEq, Eq, Default)]
9pub enum GhostTextState {
10    /// No ghost text is currently displayed
11    #[default]
12    Dismissed,
13    /// Ghost text is displayed and can be accepted
14    Displayed(GhostText),
15    /// Ghost text has been accepted
16    Accepted(GhostText),
17}
18
19/// Partial acceptance mode for ghost text
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum PartialAcceptanceMode {
22    /// Accept the entire ghost text
23    Full,
24    /// Accept only the current word
25    Word,
26    /// Accept only the current line
27    Line,
28    /// Accept a specific number of characters
29    Characters(usize),
30}
31
32/// Ghost text state manager
33pub trait GhostTextStateManager: Send + Sync {
34    /// Display ghost text
35    fn display(&mut self, ghost_text: GhostText);
36
37    /// Dismiss ghost text
38    fn dismiss(&mut self);
39
40    /// Accept ghost text (full acceptance)
41    fn accept(&mut self) -> Option<GhostText>;
42
43    /// Accept ghost text partially
44    fn accept_partial(&mut self, mode: PartialAcceptanceMode) -> Option<String>;
45
46    /// Update ghost text based on context change
47    fn update(&mut self, new_ghost_text: GhostText);
48
49    /// Get current ghost text state
50    fn get_state(&self) -> &GhostTextState;
51
52    /// Check if ghost text is currently displayed
53    fn is_displayed(&self) -> bool;
54}
55
56/// Basic ghost text state manager implementation
57pub 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                        // Accept until the first space or special character
101                        ghost_text
102                            .text
103                            .split_whitespace()
104                            .next()
105                            .unwrap_or("")
106                            .to_string()
107                    }
108                    PartialAcceptanceMode::Line => {
109                        // Accept until the first newline
110                        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
137/// Key handler for ghost text acceptance and dismissal
138pub trait GhostTextKeyHandler: Send + Sync {
139    /// Handle Tab key press (accept ghost text)
140    fn handle_tab(&mut self) -> Option<String>;
141
142    /// Handle Escape key press (dismiss ghost text)
143    fn handle_escape(&mut self);
144
145    /// Handle character input (update ghost text)
146    fn handle_character_input(&mut self, _char: char) {
147        // Default: dismiss ghost text on any character input
148        self.handle_escape();
149    }
150}
151
152/// Basic ghost text key handler
153pub 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        // Dismiss ghost text on character input
174        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        // We need to manually set up the state since we can't access the manager directly
294        // This test demonstrates the interface
295        let result = handler.handle_tab();
296        assert_eq!(result, None); // No ghost text displayed yet
297    }
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        // Should not panic
305        handler.handle_escape();
306    }
307}