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!(
38 "Warning: Failed to enable raw mode: {}. Falling back to simple input.",
39 e
40 );
41 return self.fallback_input();
42 }
43
44 let result = self.read_input_raw();
45
46 let _ = disable_raw_mode();
48
49 result
50 }
51
52 fn read_input_raw(&mut self) -> Result<String> {
53 loop {
54 let event = event::read()?;
55 if let Event::Key(key_event) = event {
56 if key_event.kind == KeyEventKind::Press {
58 match self.handle_key_event(key_event)? {
59 InputAction::Continue => continue,
60 InputAction::Submit => {
61 if !self.current_line.is_empty() {
63 self.lines.push(self.current_line.clone());
64 }
65
66 let result = self.lines.join("\n");
68
69 self.lines.clear();
71 self.current_line.clear();
72 self.cursor_pos = 0;
73
74 println!(); return Ok(result);
76 }
77 InputAction::Cancel => {
78 self.lines.clear();
80 self.current_line.clear();
81 self.cursor_pos = 0;
82
83 println!(); return Ok(String::new());
85 }
86 InputAction::NewLine => {
87 self.lines.push(self.current_line.clone());
89 self.current_line.clear();
90 self.cursor_pos = 0;
91
92 print!("\r\n{} ", "...".dimmed());
94 io::stdout().flush()?;
95 }
96 }
97 }
98 }
99 }
100 }
101
102 fn handle_key_event(&mut self, key_event: KeyEvent) -> Result<InputAction> {
103 if std::env::var("LC_DEBUG_INPUT").is_ok() {
105 eprintln!("[DEBUG] Key event: {:?}", key_event);
106 }
107 match key_event.code {
108 KeyCode::Enter => {
109 if key_event.modifiers.contains(KeyModifiers::SHIFT) {
110 Ok(InputAction::NewLine)
112 } else {
113 Ok(InputAction::Submit)
115 }
116 }
117 KeyCode::Char('j') if key_event.modifiers.contains(KeyModifiers::CONTROL) => {
118 Ok(InputAction::NewLine)
120 }
121 KeyCode::Char('\n') => {
122 Ok(InputAction::NewLine)
124 }
125 KeyCode::Char('c') if key_event.modifiers.contains(KeyModifiers::CONTROL) => {
126 Ok(InputAction::Cancel)
128 }
129 KeyCode::Char(c) => {
130 self.current_line.insert(self.cursor_pos, c);
132 self.cursor_pos += 1;
133
134 print!("{}", c);
136 io::stdout().flush()?;
137
138 Ok(InputAction::Continue)
139 }
140 KeyCode::Backspace => {
141 if self.cursor_pos > 0 {
142 self.current_line.remove(self.cursor_pos - 1);
144 self.cursor_pos -= 1;
145
146 print!("\x08 \x08");
148 io::stdout().flush()?;
149 } else if !self.lines.is_empty() {
150 let prev_line = self.lines.pop().unwrap();
153
154 print!("\r{} \r", " ".repeat(10));
156
157 self.cursor_pos = prev_line.len();
159 self.current_line = prev_line;
160
161 if self.lines.is_empty() {
163 print!("You: {}", self.current_line);
164 } else {
165 print!("... {}", self.current_line);
166 }
167 io::stdout().flush()?;
168 }
169
170 Ok(InputAction::Continue)
171 }
172 KeyCode::Left => {
173 if self.cursor_pos > 0 {
174 self.cursor_pos -= 1;
175 print!("\x08"); io::stdout().flush()?;
177 }
178 Ok(InputAction::Continue)
179 }
180 KeyCode::Right => {
181 if self.cursor_pos < self.current_line.len() {
182 self.cursor_pos += 1;
183 print!("\x1b[C"); io::stdout().flush()?;
185 }
186 Ok(InputAction::Continue)
187 }
188 _ => Ok(InputAction::Continue),
189 }
190 }
191
192 fn fallback_input(&mut self) -> Result<String> {
194 eprintln!("Multi-line input (Shift+Enter) will not be available.");
195 let mut input = String::new();
196 io::stdin().read_line(&mut input)?;
197 Ok(input.trim().to_string())
198 }
199}
200
201#[derive(Debug)]
202enum InputAction {
203 Continue,
204 Submit,
205 Cancel,
206 NewLine,
207}
208
209impl Default for MultiLineInput {
210 fn default() -> Self {
211 Self::new()
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_multiline_input_creation() {
221 let input = MultiLineInput::new();
222 assert!(input.lines.is_empty());
223 assert!(input.current_line.is_empty());
224 assert_eq!(input.cursor_pos, 0);
225 }
226}