p2p_chat/ui/chat_mode/
input.rs1use super::super::{UIAction, UIState};
3use super::ChatMode;
4use anyhow::Result;
5use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
6use tokio::sync::mpsc;
7use tracing::debug;
8
9impl ChatMode {
10 pub async fn handle_key(
25 &mut self,
26 state: &mut UIState,
27 key: KeyEvent,
28 action_tx: &mpsc::UnboundedSender<UIAction>,
29 ) -> Result<()> {
30 match key.code {
31 KeyCode::Enter => {
32 if !state.input_buffer.trim().is_empty() {
33 let input = state.input_buffer.clone();
34 self.input_history.push(input.clone());
35 self.history_index = None;
36
37 if let Err(e) = self.execute_command(&input, action_tx).await {
38 debug!("Error executing command '{}': {}", input, e);
39 }
40
41 state.input_buffer.clear();
42 state.cursor_pos = 0;
43 }
44 }
45 KeyCode::Char(c) => {
46 state.safe_insert_char(c);
47 self.history_index = None;
48 self.update_suggestion(state);
49 }
50 KeyCode::Backspace => {
51 if state.safe_remove_char_before() {
52 self.history_index = None;
53 self.update_suggestion(state);
54 }
55 }
56 KeyCode::Delete => {
57 state.safe_remove_char_at();
58 }
59 KeyCode::Left => {
60 state.safe_cursor_left();
61 }
62 KeyCode::Right => {
63 let char_count = state.input_buffer.chars().count();
64 if state.cursor_pos == char_count {
65 if let Some(suggestion) = &self.current_suggestion {
66 state.input_buffer = suggestion.clone();
67 state.safe_cursor_end();
68 self.current_suggestion = None;
69 }
70 } else {
71 state.safe_cursor_right();
72 }
73 }
74 KeyCode::Home => {
75 if key.modifiers.contains(KeyModifiers::CONTROL) {
76 state.horizontal_scroll_offset =
77 state.horizontal_scroll_offset.saturating_sub(10);
78 } else {
79 state.safe_cursor_home();
80 }
81 }
82 KeyCode::End => {
83 if key.modifiers.contains(KeyModifiers::CONTROL) {
84 state.horizontal_scroll_offset =
85 state.horizontal_scroll_offset.saturating_add(10);
86 } else {
87 state.safe_cursor_end();
88 }
89 }
90 KeyCode::Up => {
91 self.navigate_history(state, true);
92 }
93 KeyCode::Down => {
94 self.navigate_history(state, false);
95 }
96 KeyCode::PageUp => {
97 state.scroll_offset = state.scroll_offset.saturating_add(10);
98 state.update_chat_scroll_state(state.terminal_size.1 as usize);
99 }
100 KeyCode::PageDown => {
101 state.scroll_offset = state.scroll_offset.saturating_sub(10);
102 state.update_chat_scroll_state(state.terminal_size.1 as usize);
103 }
104 KeyCode::Esc => {
105 state.jump_to_bottom_chat();
106 }
107 KeyCode::Tab => {
108 let suggestions = self.completer.get_suggestions(&state.input_buffer);
109 if let Some(first) = suggestions.first() {
110 state.input_buffer = first.clone();
111 state.safe_cursor_end();
112 self.current_suggestion = None;
113 }
114 }
115 _ => {}
116 }
117
118 Ok(())
119 }
120
121 fn update_suggestion(&mut self, state: &UIState) {
125 let char_count = state.input_buffer.chars().count();
126 if state.cursor_pos == char_count && !state.input_buffer.trim().is_empty() {
127 let suggestions = self.completer.get_suggestions(&state.input_buffer);
128 if let Some(suggestion) = suggestions.first() {
129 if suggestion.starts_with(&state.input_buffer) && suggestion != &state.input_buffer
130 {
131 self.current_suggestion = Some(suggestion.clone());
132 } else {
133 self.current_suggestion = None;
134 }
135 } else {
136 self.current_suggestion = None;
137 }
138 } else {
139 self.current_suggestion = None;
140 }
141 }
142
143 fn navigate_history(&mut self, state: &mut UIState, up: bool) {
150 if self.input_history.is_empty() {
151 return;
152 }
153
154 let new_index = if up {
155 match self.history_index {
156 None => Some(self.input_history.len() - 1),
157 Some(0) => Some(0),
158 Some(i) => Some(i - 1),
159 }
160 } else {
161 match self.history_index {
162 None => None,
163 Some(i) if i + 1 >= self.input_history.len() => None,
164 Some(i) => Some(i + 1),
165 }
166 };
167
168 self.history_index = new_index;
169
170 if let Some(index) = new_index {
171 state.input_buffer = self.input_history[index].clone();
172 state.safe_cursor_end();
173 } else {
174 state.input_buffer.clear();
175 state.safe_cursor_home();
176 }
177 }
178
179 async fn execute_command(
193 &self,
194 input: &str,
195 action_tx: &mpsc::UnboundedSender<UIAction>,
196 ) -> Result<()> {
197 let parts: Vec<&str> = input.split_whitespace().collect();
198 if parts.is_empty() {
199 return Ok(());
200 }
201
202 let command = parts[0];
203 match command {
204 "send" => {
205 if parts.len() >= 3 {
206 let recipient = parts[1].to_string();
207 let message = parts[2..].join(" ");
208 let _ = action_tx.send(UIAction::SendMessage(recipient, message));
209 } else {
210 debug!("Usage: send <recipient> <message>");
211 }
212 }
213 _ => {
214 let _ = action_tx.send(UIAction::ExecuteCommand(input.to_string()));
215 }
216 }
217
218 Ok(())
219 }
220}