rush_sync_server/input/
input.rs1use crate::commands::handler::CommandHandler;
6use crate::commands::history::{
7 HistoryAction, HistoryConfig, HistoryEvent, HistoryEventHandler, HistoryKeyboardHandler,
8 HistoryManager,
9};
10use crate::core::prelude::*;
11use crate::input::keyboard::{KeyAction, KeyboardManager};
12use crate::ui::cursor::CursorState;
13use crate::ui::widget::{InputWidget, Widget};
14use ratatui::{
15 style::Style,
16 text::{Line, Span},
17 widgets::{Block, Borders, Padding, Paragraph},
18};
19use unicode_segmentation::UnicodeSegmentation;
20
21pub struct InputState<'a> {
22 content: String,
23 cursor: CursorState,
24 prompt: String,
25 history_manager: HistoryManager,
26 config: &'a Config,
27 command_handler: CommandHandler,
28 keyboard_manager: KeyboardManager,
29 waiting_for_exit_confirmation: bool,
30 waiting_for_restart_confirmation: bool, }
32
33impl<'a> InputState<'a> {
34 pub fn new(prompt: &str, config: &'a Config) -> Self {
35 let history_config = HistoryConfig::from_main_config(config);
36
37 Self {
38 content: String::with_capacity(100),
39 cursor: CursorState::new(),
40 prompt: prompt.to_string(),
41 history_manager: HistoryManager::new(history_config.max_entries),
42 config,
43 command_handler: CommandHandler::new(),
44 keyboard_manager: KeyboardManager::new(),
45 waiting_for_exit_confirmation: false,
46 waiting_for_restart_confirmation: false, }
48 }
49
50 pub fn validate_input(&self, input: &str) -> crate::core::error::Result<()> {
51 if input.trim().is_empty() {
52 return Err(AppError::Validation(get_translation(
53 "system.input.empty",
54 &[],
55 )));
56 }
57
58 let grapheme_count = input.graphemes(true).count();
59 let max_length = 1024;
60
61 if grapheme_count > max_length {
62 return Err(AppError::Validation(get_translation(
63 "system.input.too_long",
64 &[&max_length.to_string()],
65 )));
66 }
67
68 Ok(())
69 }
70
71 pub fn reset_for_language_change(&mut self) {
72 self.waiting_for_exit_confirmation = false;
73 self.waiting_for_restart_confirmation = false; self.content.clear();
75 self.history_manager.reset_position();
76 self.cursor.move_to_start();
77 log::debug!("InputState reset for language change");
78 }
79
80 fn handle_exit_confirmation(&mut self, action: KeyAction) -> Option<String> {
81 match action {
82 KeyAction::Submit => {
83 self.waiting_for_exit_confirmation = false;
84
85 let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
86 let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
87
88 match self.content.trim().to_lowercase().as_str() {
89 input if input == confirm_short.to_lowercase() => {
90 self.content.clear();
91 Some("__EXIT__".to_string())
92 }
93 input if input == cancel_short.to_lowercase() => {
94 self.clear_input();
95 Some(crate::i18n::get_translation("system.input.cancelled", &[]))
96 }
97 _ => {
98 self.clear_input();
99 Some(crate::i18n::get_translation("system.input.cancelled", &[]))
100 }
101 }
102 }
103 KeyAction::InsertChar(c) => {
104 let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
105 let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
106
107 if c.to_lowercase().to_string() == confirm_short.to_lowercase()
108 || c.to_lowercase().to_string() == cancel_short.to_lowercase()
109 {
110 self.content.clear();
111 self.content.push(c);
112 self.cursor.update_text_length(&self.content);
113 self.cursor.move_to_end();
114 }
115 None
116 }
117 KeyAction::Backspace | KeyAction::Delete | KeyAction::ClearLine => {
118 self.clear_input();
119 None
120 }
121 _ => None,
122 }
123 }
124
125 fn handle_restart_confirmation(&mut self, action: KeyAction) -> Option<String> {
127 match action {
128 KeyAction::Submit => {
129 self.waiting_for_restart_confirmation = false;
130
131 let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
132 let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
133
134 match self.content.trim().to_lowercase().as_str() {
135 input if input == confirm_short.to_lowercase() => {
136 self.content.clear();
137 Some("__RESTART__".to_string()) }
139 input if input == cancel_short.to_lowercase() => {
140 self.clear_input();
141 Some(crate::i18n::get_translation("system.input.cancelled", &[]))
142 }
143 _ => {
144 self.clear_input();
145 Some(crate::i18n::get_translation("system.input.cancelled", &[]))
146 }
147 }
148 }
149 KeyAction::InsertChar(c) => {
150 let confirm_short = crate::i18n::get_translation("system.input.confirm.short", &[]);
151 let cancel_short = crate::i18n::get_translation("system.input.cancel.short", &[]);
152
153 if c.to_lowercase().to_string() == confirm_short.to_lowercase()
154 || c.to_lowercase().to_string() == cancel_short.to_lowercase()
155 {
156 self.content.clear();
157 self.content.push(c);
158 self.cursor.update_text_length(&self.content);
159 self.cursor.move_to_end();
160 }
161 None
162 }
163 KeyAction::Backspace | KeyAction::Delete | KeyAction::ClearLine => {
164 self.clear_input();
165 None
166 }
167 _ => None,
168 }
169 }
170
171 fn clear_input(&mut self) {
172 self.content.clear();
173 self.history_manager.reset_position();
174 self.cursor.move_to_start();
175 }
176
177 fn handle_history_action(&mut self, action: HistoryAction) -> Option<String> {
178 match action {
179 HistoryAction::NavigatePrevious => {
180 if let Some(entry) = self.history_manager.navigate_previous() {
181 self.content = entry;
182 self.cursor.update_text_length(&self.content);
183 self.cursor.move_to_end();
184 }
185 }
186 HistoryAction::NavigateNext => {
187 if let Some(entry) = self.history_manager.navigate_next() {
188 self.content = entry;
189 self.cursor.update_text_length(&self.content);
190 self.cursor.move_to_end();
191 }
192 }
193 }
194 None
195 }
196
197 fn handle_history_event(&mut self, event: HistoryEvent) -> String {
198 match event {
199 HistoryEvent::Clear => {
200 self.history_manager.clear();
201 HistoryEventHandler::create_clear_response()
202 }
203 HistoryEvent::Add(entry) => {
204 self.history_manager.add_entry(entry);
205 String::new()
206 }
207 _ => String::new(),
208 }
209 }
210
211 pub fn execute(&self) -> crate::core::error::Result<String> {
212 Ok(format!(
213 "__CONFIRM_EXIT__{}",
214 get_translation("system.input.confirm_exit", &[])
215 ))
216 }
217
218 pub fn handle_key_event(&mut self, key: KeyEvent) -> Option<String> {
219 if let Some(history_action) = HistoryKeyboardHandler::get_history_action(&key) {
221 return self.handle_history_action(history_action);
222 }
223
224 if key.code == KeyCode::Esc {
226 return None;
227 }
228
229 let action = self.keyboard_manager.get_action(&key);
231
232 if self.waiting_for_exit_confirmation {
234 return self.handle_exit_confirmation(action);
235 }
236
237 if self.waiting_for_restart_confirmation {
238 return self.handle_restart_confirmation(action);
240 }
241
242 match action {
244 KeyAction::Submit => {
245 if self.content.is_empty() {
246 return None;
247 }
248 if self.validate_input(&self.content).is_ok() {
249 let content = std::mem::take(&mut self.content);
250 self.cursor.reset_for_empty_text();
251 self.history_manager.add_entry(content.clone());
252 let result = self.command_handler.handle_input(&content);
253
254 if let Some(event) = HistoryEventHandler::handle_command_result(&result.message)
255 {
256 return Some(self.handle_history_event(event));
257 }
258
259 if result.message.starts_with("__CONFIRM_EXIT__") {
260 self.waiting_for_exit_confirmation = true;
261 return Some(result.message.replace("__CONFIRM_EXIT__", ""));
262 }
263
264 if result.message.starts_with("__CONFIRM_RESTART__") {
266 self.waiting_for_restart_confirmation = true;
267 return Some(result.message.replace("__CONFIRM_RESTART__", ""));
268 }
269
270 if result.should_exit {
271 return Some(format!("__EXIT__{}", result.message));
272 }
273 return Some(result.message);
274 }
275 None
276 }
277 KeyAction::InsertChar(c) => {
278 if self.content.graphemes(true).count() < self.config.input_max_length {
279 let byte_pos = self.cursor.get_byte_position(&self.content);
280 self.content.insert(byte_pos, c);
281 self.cursor.update_text_length(&self.content);
282 self.cursor.move_right();
283 }
284 None
285 }
286 KeyAction::MoveLeft => {
287 self.cursor.move_left();
288 None
289 }
290 KeyAction::MoveRight => {
291 self.cursor.move_right();
292 None
293 }
294 KeyAction::MoveToStart => {
295 self.cursor.move_to_start();
296 None
297 }
298 KeyAction::MoveToEnd => {
299 self.cursor.move_to_end();
300 None
301 }
302 KeyAction::Backspace => {
303 if self.content.is_empty() || self.cursor.get_position() == 0 {
304 return None;
305 }
306
307 let current_byte_pos = self.cursor.get_byte_position(&self.content);
308 let prev_byte_pos = self.cursor.get_prev_byte_position(&self.content);
309
310 if prev_byte_pos >= current_byte_pos || current_byte_pos > self.content.len() {
311 self.cursor.update_text_length(&self.content);
312 return None;
313 }
314
315 self.cursor.move_left();
316 self.content
317 .replace_range(prev_byte_pos..current_byte_pos, "");
318 self.cursor.update_text_length(&self.content);
319
320 if self.content.is_empty() {
321 self.cursor.reset_for_empty_text();
322 }
323 None
324 }
325 KeyAction::Delete => {
326 let text_length = self.content.graphemes(true).count();
327 if self.cursor.get_position() >= text_length || text_length == 0 {
328 return None;
329 }
330
331 let current_byte_pos = self.cursor.get_byte_position(&self.content);
332 let next_byte_pos = self.cursor.get_next_byte_position(&self.content);
333
334 if current_byte_pos >= next_byte_pos || next_byte_pos > self.content.len() {
335 self.cursor.update_text_length(&self.content);
336 return None;
337 }
338
339 self.content
340 .replace_range(current_byte_pos..next_byte_pos, "");
341 self.cursor.update_text_length(&self.content);
342
343 if self.content.is_empty() {
344 self.cursor.reset_for_empty_text();
345 }
346 None
347 }
348
349 KeyAction::ClearLine
350 | KeyAction::ScrollUp
351 | KeyAction::ScrollDown
352 | KeyAction::PageUp
353 | KeyAction::PageDown
354 | KeyAction::Cancel
355 | KeyAction::Quit
356 | KeyAction::CopySelection
357 | KeyAction::PasteBuffer
358 | KeyAction::NoAction => None,
359 }
360 }
361}
362
363impl Widget for InputState<'_> {
364 fn render(&self) -> Paragraph {
365 let graphemes: Vec<&str> = self.content.graphemes(true).collect();
366 let cursor_pos = self.cursor.get_position();
367 let mut spans = Vec::with_capacity(4);
368
369 spans.push(Span::styled(
370 &self.prompt,
371 Style::default().fg(self.config.prompt.color.into()),
372 ));
373
374 let prompt_width = self.prompt.graphemes(true).count();
375 let available_width = self
376 .config
377 .input_max_length
378 .saturating_sub(prompt_width + 4);
379
380 let viewport_start = if cursor_pos > available_width {
381 cursor_pos - available_width + 10
382 } else {
383 0
384 };
385
386 if cursor_pos > 0 {
387 let visible_text = if viewport_start < cursor_pos {
388 graphemes[viewport_start..cursor_pos].join("")
389 } else {
390 String::new()
391 };
392
393 spans.push(Span::styled(
394 visible_text,
395 Style::default().fg(self.config.theme.input_text.into()),
396 ));
397 }
398
399 let cursor_char = graphemes.get(cursor_pos).map_or(" ", |&c| c);
400 let cursor_style = if self.cursor.is_visible() {
401 Style::default()
402 .fg(self.config.theme.input_text.into())
403 .bg(self.config.theme.cursor.into())
404 } else {
405 Style::default().fg(self.config.theme.input_text.into())
406 };
407 spans.push(Span::styled(cursor_char, cursor_style));
408
409 if cursor_pos < graphemes.len() {
410 let remaining_width = available_width.saturating_sub(cursor_pos - viewport_start);
411 let end_pos = (cursor_pos + 1 + remaining_width).min(graphemes.len());
412
413 if cursor_pos + 1 < end_pos {
414 spans.push(Span::styled(
415 graphemes[cursor_pos + 1..end_pos].join(""),
416 Style::default().fg(self.config.theme.input_text.into()),
417 ));
418 }
419 }
420
421 Paragraph::new(Line::from(spans)).block(
422 Block::default()
423 .padding(Padding::new(3, 1, 1, 1))
424 .borders(Borders::NONE)
425 .style(Style::default().bg(self.config.theme.input_bg.into())),
426 )
427 }
428
429 fn handle_input(&mut self, key: KeyEvent) -> Option<String> {
430 self.handle_key_event(key)
431 }
432
433 fn as_input_state(&mut self) -> Option<&mut dyn InputWidget> {
434 Some(self)
435 }
436}
437
438impl InputWidget for InputState<'_> {
439 fn update_cursor_blink(&mut self) {
440 self.cursor.update_blink();
441 }
442}