1use anyhow::Result;
2use colored::Colorize;
3use crossterm::{
4 event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers},
5 terminal::{disable_raw_mode, enable_raw_mode},
6};
7use std::io::{self, Write};
8
9pub struct MultiLineInput {
11 lines: Vec<String>,
12 current_line: String,
13 cursor_pos: usize,
14}
15
16impl MultiLineInput {
17 pub fn new() -> Self {
18 Self {
19 lines: Vec::new(),
20 current_line: String::new(),
21 cursor_pos: 0,
22 }
23 }
24
25 pub fn read_input(&mut self, prompt: &str) -> Result<String> {
32 print!("{} ", prompt);
33 io::stdout().flush()?;
34
35 if let Err(e) = enable_raw_mode() {
37 eprintln!("Warning: Failed to enable raw mode: {}. Falling back to simple input.", e);
38 return self.fallback_input();
39 }
40
41 let result = self.read_input_raw();
42
43 let _ = disable_raw_mode();
45
46 result
47 }
48
49 fn read_input_raw(&mut self) -> Result<String> {
50 loop {
51 let event = event::read()?;
52 if let Event::Key(key_event) = event {
53 if key_event.kind == KeyEventKind::Press {
55 match self.handle_key_event(key_event)? {
56 InputAction::Continue => continue,
57 InputAction::Submit => {
58 if !self.current_line.is_empty() {
60 self.lines.push(self.current_line.clone());
61 }
62
63 let result = self.lines.join("\n");
65
66 self.lines.clear();
68 self.current_line.clear();
69 self.cursor_pos = 0;
70
71 println!(); return Ok(result);
73 }
74 InputAction::Cancel => {
75 self.lines.clear();
77 self.current_line.clear();
78 self.cursor_pos = 0;
79
80 println!(); return Ok(String::new());
82 }
83 InputAction::NewLine => {
84 self.lines.push(self.current_line.clone());
86 self.current_line.clear();
87 self.cursor_pos = 0;
88
89 print!("\r\n{} ", "...".dimmed());
91 io::stdout().flush()?;
92 }
93 }
94 }
95 }
96 }
97 }
98
99 fn handle_key_event(&mut self, key_event: KeyEvent) -> Result<InputAction> {
100 if std::env::var("LC_DEBUG_INPUT").is_ok() {
102 eprintln!("[DEBUG] Key event: {:?}", key_event);
103 }
104 match key_event.code {
105 KeyCode::Enter => {
106 if key_event.modifiers.contains(KeyModifiers::SHIFT) {
107 Ok(InputAction::NewLine)
109 } else {
110 Ok(InputAction::Submit)
112 }
113 }
114 KeyCode::Char('j') if key_event.modifiers.contains(KeyModifiers::CONTROL) => {
115 Ok(InputAction::NewLine)
117 }
118 KeyCode::Char('\n') => {
119 Ok(InputAction::NewLine)
121 }
122 KeyCode::Char('c') if key_event.modifiers.contains(KeyModifiers::CONTROL) => {
123 Ok(InputAction::Cancel)
125 }
126 KeyCode::Char(c) => {
127 self.current_line.insert(self.cursor_pos, c);
129 self.cursor_pos += 1;
130
131 print!("{}", c);
133 io::stdout().flush()?;
134
135 Ok(InputAction::Continue)
136 }
137 KeyCode::Backspace => {
138 if self.cursor_pos > 0 {
139 self.current_line.remove(self.cursor_pos - 1);
141 self.cursor_pos -= 1;
142
143 print!("\x08 \x08");
145 io::stdout().flush()?;
146 } else if !self.lines.is_empty() {
147 let prev_line = self.lines.pop().unwrap();
150
151 print!("\r{} \r", " ".repeat(10));
153
154 self.cursor_pos = prev_line.len();
156 self.current_line = prev_line;
157
158 if self.lines.is_empty() {
160 print!("You: {}", self.current_line);
161 } else {
162 print!("... {}", self.current_line);
163 }
164 io::stdout().flush()?;
165 }
166
167 Ok(InputAction::Continue)
168 }
169 KeyCode::Left => {
170 if self.cursor_pos > 0 {
171 self.cursor_pos -= 1;
172 print!("\x08"); io::stdout().flush()?;
174 }
175 Ok(InputAction::Continue)
176 }
177 KeyCode::Right => {
178 if self.cursor_pos < self.current_line.len() {
179 self.cursor_pos += 1;
180 print!("\x1b[C"); io::stdout().flush()?;
182 }
183 Ok(InputAction::Continue)
184 }
185 _ => Ok(InputAction::Continue),
186 }
187 }
188
189 fn fallback_input(&mut self) -> Result<String> {
191 eprintln!("Multi-line input (Shift+Enter) will not be available.");
192 let mut input = String::new();
193 io::stdin().read_line(&mut input)?;
194 Ok(input.trim().to_string())
195 }
196}
197
198#[derive(Debug)]
199enum InputAction {
200 Continue,
201 Submit,
202 Cancel,
203 NewLine,
204}
205
206impl Default for MultiLineInput {
207 fn default() -> Self {
208 Self::new()
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_multiline_input_creation() {
218 let input = MultiLineInput::new();
219 assert!(input.lines.is_empty());
220 assert!(input.current_line.is_empty());
221 assert_eq!(input.cursor_pos, 0);
222 }
223}