lc/
input.rs

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
9/// Multi-line input handler that supports Shift+Enter for new lines
10pub 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    /// Read multi-line input from the terminal
26    /// - Enter: Submit the input
27    /// - Shift+Enter: Add a new line
28    /// - Ctrl+C: Cancel input (returns empty string)
29    /// - Backspace: Delete character
30    /// - Arrow keys: Navigate (basic support)
31    pub fn read_input(&mut self, prompt: &str) -> Result<String> {
32        print!("{} ", prompt);
33        io::stdout().flush()?;
34
35        // Ensure we can enable raw mode
36        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        // Always disable raw mode, even if there was an error
44        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                // Only process key press events, ignore key release
54                if key_event.kind == KeyEventKind::Press {
55                match self.handle_key_event(key_event)? {
56                    InputAction::Continue => continue,
57                    InputAction::Submit => {
58                        // Add current line to lines if it's not empty
59                        if !self.current_line.is_empty() {
60                            self.lines.push(self.current_line.clone());
61                        }
62                        
63                        // Join all lines and return
64                        let result = self.lines.join("\n");
65                        
66                        // Clear state for next use
67                        self.lines.clear();
68                        self.current_line.clear();
69                        self.cursor_pos = 0;
70                        
71                        println!(); // Move to next line after input
72                        return Ok(result);
73                    }
74                    InputAction::Cancel => {
75                        // Clear state and return empty string
76                        self.lines.clear();
77                        self.current_line.clear();
78                        self.cursor_pos = 0;
79                        
80                        println!(); // Move to next line
81                        return Ok(String::new());
82                    }
83                    InputAction::NewLine => {
84                        // Add current line to lines and start a new line
85                        self.lines.push(self.current_line.clone());
86                        self.current_line.clear();
87                        self.cursor_pos = 0;
88                        
89                        // Print newline and show continuation prompt at beginning of line
90                        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        // Debug: print key event details
101        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                    // Shift+Enter: New line
108                    Ok(InputAction::NewLine)
109                } else {
110                    // Enter: Submit
111                    Ok(InputAction::Submit)
112                }
113            }
114            KeyCode::Char('j') if key_event.modifiers.contains(KeyModifiers::CONTROL) => {
115                // Ctrl+J: Alternative for new line (common in some terminals)
116                Ok(InputAction::NewLine)
117            }
118            KeyCode::Char('\n') => {
119                // Direct newline character (some terminals send this for Shift+Enter)
120                Ok(InputAction::NewLine)
121            }
122            KeyCode::Char('c') if key_event.modifiers.contains(KeyModifiers::CONTROL) => {
123                // Ctrl+C: Cancel
124                Ok(InputAction::Cancel)
125            }
126            KeyCode::Char(c) => {
127                // Insert character at cursor position
128                self.current_line.insert(self.cursor_pos, c);
129                self.cursor_pos += 1;
130                
131                // Print the character
132                print!("{}", c);
133                io::stdout().flush()?;
134                
135                Ok(InputAction::Continue)
136            }
137            KeyCode::Backspace => {
138                if self.cursor_pos > 0 {
139                    // Remove character before cursor
140                    self.current_line.remove(self.cursor_pos - 1);
141                    self.cursor_pos -= 1;
142                    
143                    // Move cursor back, print space to clear character, move back again
144                    print!("\x08 \x08");
145                    io::stdout().flush()?;
146                } else if !self.lines.is_empty() {
147                    // If at beginning of current line and there are previous lines,
148                    // move to end of previous line
149                    let prev_line = self.lines.pop().unwrap();
150                    
151                    // Clear current line display
152                    print!("\r{}   \r", " ".repeat(10));
153                    
154                    // Restore previous line
155                    self.cursor_pos = prev_line.len();
156                    self.current_line = prev_line;
157                    
158                    // Redraw prompt and current line
159                    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"); // Move cursor left
173                    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"); // Move cursor right
181                    io::stdout().flush()?;
182                }
183                Ok(InputAction::Continue)
184            }
185            _ => Ok(InputAction::Continue),
186        }
187    }
188
189    /// Fallback to simple input when raw mode fails
190    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}