editline/
lib.rs

1//! Platform-agnostic line editor with history and full editing capabilities.
2//!
3//! This library provides a flexible line editing system with complete separation between
4//! I/O operations and editing logic through the [`Terminal`] trait. This design enables
5//! usage across various platforms and I/O systems without modification to the core logic.
6//!
7//! # Features
8//!
9//! - **Full line editing**: Insert, delete, cursor movement
10//! - **Word-aware navigation**: Ctrl+Left/Right, Alt+Backspace, Ctrl+Delete
11//! - **Command history**: 50-entry circular buffer with up/down navigation
12//! - **Smart history**: Automatically skips duplicates and empty lines
13//! - **Cross-platform**: Unix (termios/ANSI) and Windows (Console API) implementations included
14//! - **Zero global state**: All state is explicitly managed
15//! - **Type-safe**: Strong typing with Result-based error handling
16//!
17//! # Quick Start
18//!
19//! ```no_run
20//! use editline::{LineEditor, terminals::StdioTerminal};
21//!
22//! let mut editor = LineEditor::new(1024, 50);  // buffer size, history size
23//! let mut terminal = StdioTerminal::new();
24//!
25//! loop {
26//!     print!("> ");
27//!     std::io::Write::flush(&mut std::io::stdout()).unwrap();
28//!
29//!     match editor.read_line(&mut terminal) {
30//!         Ok(line) => {
31//!             if line == "exit" {
32//!                 break;
33//!             }
34//!             println!("You typed: {}", line);
35//!         }
36//!         Err(e) => {
37//!             eprintln!("Error: {}", e);
38//!             break;
39//!         }
40//!     }
41//! }
42//! ```
43//!
44//! # Architecture
45//!
46//! The library is organized around three main components:
47//!
48//! - [`LineEditor`]: Main API that orchestrates editing operations
49//! - [`LineBuffer`]: Manages text buffer and cursor position
50//! - [`History`]: Circular buffer for command history
51//!
52//! All I/O is abstracted through the [`Terminal`] trait, which platform-specific
53//! implementations must provide.
54//!
55//! # Custom Terminal Implementation
56//!
57//! To use editline with custom I/O (UART, network, etc.), implement the [`Terminal`] trait:
58//!
59//! ```
60//! use editline::{Terminal, KeyEvent, Result};
61//!
62//! struct MyTerminal {
63//!     // Your platform-specific fields
64//! }
65//!
66//! impl Terminal for MyTerminal {
67//!     fn read_byte(&mut self) -> Result<u8> {
68//!         // Read from your input source
69//! #       Ok(b'x')
70//!     }
71//!
72//!     fn write(&mut self, data: &[u8]) -> Result<()> {
73//!         // Write to your output
74//! #       Ok(())
75//!     }
76//!
77//!     fn flush(&mut self) -> Result<()> {
78//!         // Flush output
79//! #       Ok(())
80//!     }
81//!
82//!     fn enter_raw_mode(&mut self) -> Result<()> {
83//!         // Configure for character-by-character input
84//! #       Ok(())
85//!     }
86//!
87//!     fn exit_raw_mode(&mut self) -> Result<()> {
88//!         // Restore normal mode
89//! #       Ok(())
90//!     }
91//!
92//!     fn cursor_left(&mut self) -> Result<()> {
93//!         // Move cursor left one position
94//! #       Ok(())
95//!     }
96//!
97//!     fn cursor_right(&mut self) -> Result<()> {
98//!         // Move cursor right one position
99//! #       Ok(())
100//!     }
101//!
102//!     fn clear_eol(&mut self) -> Result<()> {
103//!         // Clear from cursor to end of line
104//! #       Ok(())
105//!     }
106//!
107//!     fn parse_key_event(&mut self) -> Result<KeyEvent> {
108//!         // Parse input bytes into key events
109//! #       Ok(KeyEvent::Enter)
110//!     }
111//! }
112//! ```
113
114#![cfg_attr(not(feature = "std"), no_std)]
115
116extern crate alloc;
117
118use alloc::string::{String, ToString};
119use alloc::vec::Vec;
120use core::fmt;
121
122// Import prelude types that are normally available via std::prelude
123#[cfg(not(feature = "std"))]
124use core::prelude::v1::*;
125#[cfg(not(feature = "std"))]
126use core::result::Result::{Ok, Err};
127
128/// Error type for editline operations
129#[derive(Debug)]
130pub enum Error {
131    /// I/O error occurred
132    Io(&'static str),
133    /// Invalid UTF-8 data
134    InvalidUtf8,
135    /// End of file
136    Eof,
137    /// Operation interrupted
138    Interrupted,
139}
140
141impl fmt::Display for Error {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        match self {
144            Error::Io(msg) => {
145                f.write_str("I/O error: ")?;
146                f.write_str(msg)
147            }
148            Error::InvalidUtf8 => f.write_str("Invalid UTF-8"),
149            Error::Eof => f.write_str("End of file"),
150            Error::Interrupted => f.write_str("Interrupted"),
151        }
152    }
153}
154
155#[cfg(feature = "std")]
156impl From<std::io::Error> for Error {
157    fn from(e: std::io::Error) -> Self {
158        use std::io::ErrorKind;
159        match e.kind() {
160            ErrorKind::UnexpectedEof => Error::Eof,
161            ErrorKind::Interrupted => Error::Interrupted,
162            _ => Error::Io("I/O error"),
163        }
164    }
165}
166
167#[cfg(feature = "std")]
168impl From<Error> for std::io::Error {
169    fn from(e: Error) -> Self {
170        use std::io::{Error as IoError, ErrorKind};
171        match e {
172            Error::Io(msg) => IoError::new(ErrorKind::Other, msg),
173            Error::InvalidUtf8 => IoError::new(ErrorKind::InvalidData, "Invalid UTF-8"),
174            Error::Eof => IoError::new(ErrorKind::UnexpectedEof, "End of file"),
175            Error::Interrupted => IoError::new(ErrorKind::Interrupted, "Interrupted"),
176        }
177    }
178}
179
180impl From<core::str::Utf8Error> for Error {
181    fn from(_: core::str::Utf8Error) -> Self {
182        Error::InvalidUtf8
183    }
184}
185
186/// Result type for editline operations
187pub type Result<T> = core::result::Result<T, Error>;
188
189/// Key events that can be processed by the line editor
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub enum KeyEvent {
192    /// Normal printable character
193    Normal(char),
194    /// Left arrow
195    Left,
196    /// Right arrow
197    Right,
198    /// Up arrow (history previous)
199    Up,
200    /// Down arrow (history next)
201    Down,
202    /// Home key
203    Home,
204    /// End key
205    End,
206    /// Backspace
207    Backspace,
208    /// Delete
209    Delete,
210    /// Enter/Return
211    Enter,
212    /// Ctrl+Left (word left)
213    CtrlLeft,
214    /// Ctrl+Right (word right)
215    CtrlRight,
216    /// Ctrl+Delete (delete word right)
217    CtrlDelete,
218    /// Alt+Backspace (delete word left)
219    AltBackspace,
220}
221
222/// Terminal abstraction that enables platform-agnostic line editing.
223///
224/// Implement this trait to use editline with any I/O system: standard terminals,
225/// UART connections, network sockets, or custom devices.
226///
227/// # Platform Implementations
228///
229/// This library provides built-in implementations:
230/// - [`terminals::StdioTerminal`] for Unix (termios + ANSI)
231/// - [`terminals::StdioTerminal`] for Windows (Console API)
232///
233/// # Example
234///
235/// ```
236/// use editline::{Terminal, KeyEvent, Result};
237///
238/// struct MockTerminal {
239///     input: Vec<u8>,
240///     output: Vec<u8>,
241/// }
242///
243/// impl Terminal for MockTerminal {
244///     fn read_byte(&mut self) -> Result<u8> {
245///         self.input.pop().ok_or(editline::Error::Eof)
246///     }
247///
248///     fn write(&mut self, data: &[u8]) -> Result<()> {
249///         self.output.extend_from_slice(data);
250///         Ok(())
251///     }
252///
253///     // ... implement other methods
254/// #   fn flush(&mut self) -> Result<()> { Ok(()) }
255/// #   fn enter_raw_mode(&mut self) -> Result<()> { Ok(()) }
256/// #   fn exit_raw_mode(&mut self) -> Result<()> { Ok(()) }
257/// #   fn cursor_left(&mut self) -> Result<()> { Ok(()) }
258/// #   fn cursor_right(&mut self) -> Result<()> { Ok(()) }
259/// #   fn clear_eol(&mut self) -> Result<()> { Ok(()) }
260/// #   fn parse_key_event(&mut self) -> Result<KeyEvent> { Ok(KeyEvent::Enter) }
261/// }
262/// ```
263pub trait Terminal {
264    /// Reads a single byte from the input source.
265    ///
266    /// This is called repeatedly to fetch user input. Should block until a byte is available.
267    fn read_byte(&mut self) -> Result<u8>;
268
269    /// Writes raw bytes to the output.
270    ///
271    /// Used to display typed characters and redraw the line during editing.
272    fn write(&mut self, data: &[u8]) -> Result<()>;
273
274    /// Flushes any buffered output.
275    ///
276    /// Called after each key event to ensure immediate visual feedback.
277    fn flush(&mut self) -> Result<()>;
278
279    /// Enters raw mode for character-by-character input.
280    ///
281    /// Should disable line buffering and echo. Called at the start of [`LineEditor::read_line`].
282    fn enter_raw_mode(&mut self) -> Result<()>;
283
284    /// Exits raw mode and restores normal terminal settings.
285    ///
286    /// Called at the end of [`LineEditor::read_line`] to restore the terminal state.
287    fn exit_raw_mode(&mut self) -> Result<()>;
288
289    /// Moves the cursor left by one position.
290    ///
291    /// Typically outputs an ANSI escape sequence like `\x1b[D` or calls a platform API.
292    fn cursor_left(&mut self) -> Result<()>;
293
294    /// Moves the cursor right by one position.
295    ///
296    /// Typically outputs an ANSI escape sequence like `\x1b[C` or calls a platform API.
297    fn cursor_right(&mut self) -> Result<()>;
298
299    /// Clears from the cursor position to the end of the line.
300    ///
301    /// Typically outputs an ANSI escape sequence like `\x1b[K` or calls a platform API.
302    fn clear_eol(&mut self) -> Result<()>;
303
304    /// Parses the next key event from input.
305    ///
306    /// Should handle multi-byte sequences (like ANSI escape codes) and return a single
307    /// [`KeyEvent`]. Called once per key press by [`LineEditor::read_line`].
308    fn parse_key_event(&mut self) -> Result<KeyEvent>;
309}
310
311/// Text buffer with cursor tracking for line editing operations.
312///
313/// Manages the actual text being edited and the cursor position within it.
314/// Supports UTF-8 text and provides methods for character/word manipulation.
315///
316/// This struct is typically not used directly - instead use [`LineEditor`] which
317/// provides the high-level editing interface.
318pub struct LineBuffer {
319    buffer: Vec<u8>,
320    cursor_pos: usize,
321}
322
323impl LineBuffer {
324    /// Creates a new line buffer with the specified capacity.
325    ///
326    /// # Arguments
327    ///
328    /// * `capacity` - Initial capacity for the internal buffer in bytes
329    ///
330    /// # Examples
331    ///
332    /// ```
333    /// use editline::LineBuffer;
334    ///
335    /// let buffer = LineBuffer::new(1024);
336    /// assert!(buffer.is_empty());
337    /// ```
338    pub fn new(capacity: usize) -> Self {
339        Self {
340            buffer: Vec::with_capacity(capacity),
341            cursor_pos: 0,
342        }
343    }
344
345    /// Clears the buffer and resets the cursor to the start.
346    pub fn clear(&mut self) {
347        self.buffer.clear();
348        self.cursor_pos = 0;
349    }
350
351    /// Returns the length of the buffer in bytes.
352    ///
353    /// Note: For UTF-8 text, this is the byte count, not the character count.
354    pub fn len(&self) -> usize {
355        self.buffer.len()
356    }
357
358    /// Returns `true` if the buffer is empty.
359    pub fn is_empty(&self) -> bool {
360        self.buffer.is_empty()
361    }
362
363    /// Returns the current cursor position in bytes from the start.
364    pub fn cursor_pos(&self) -> usize {
365        self.cursor_pos
366    }
367
368    /// Returns the buffer contents as a UTF-8 string slice.
369    ///
370    /// # Errors
371    ///
372    /// Returns `Err` if the buffer contains invalid UTF-8.
373    pub fn as_str(&self) -> Result<&str> {
374        core::str::from_utf8(&self.buffer).map_err(|_| Error::InvalidUtf8)
375    }
376
377    /// Returns the buffer contents as a byte slice.
378    pub fn as_bytes(&self) -> &[u8] {
379        &self.buffer
380    }
381
382    /// Inserts a character at the cursor position, moving the cursor forward.
383    ///
384    /// Supports UTF-8 characters. The cursor advances by the byte length of the character.
385    pub fn insert_char(&mut self, c: char) {
386        let mut buf = [0; 4];
387        let bytes = c.encode_utf8(&mut buf).as_bytes();
388
389        for &byte in bytes {
390            self.buffer.insert(self.cursor_pos, byte);
391            self.cursor_pos += 1;
392        }
393    }
394
395    /// Deletes the character before the cursor (backspace operation).
396    ///
397    /// Returns `true` if a character was deleted, `false` if the cursor is at the start.
398    pub fn delete_before_cursor(&mut self) -> bool {
399        if self.cursor_pos > 0 {
400            self.cursor_pos -= 1;
401            self.buffer.remove(self.cursor_pos);
402            true
403        } else {
404            false
405        }
406    }
407
408    /// Deletes the character at the cursor (delete key operation).
409    ///
410    /// Returns `true` if a character was deleted, `false` if the cursor is at the end.
411    pub fn delete_at_cursor(&mut self) -> bool {
412        if self.cursor_pos < self.buffer.len() {
413            self.buffer.remove(self.cursor_pos);
414            true
415        } else {
416            false
417        }
418    }
419
420    /// Moves the cursor one position to the left.
421    ///
422    /// Returns `true` if the cursor moved, `false` if already at the start.
423    pub fn move_cursor_left(&mut self) -> bool {
424        if self.cursor_pos > 0 {
425            self.cursor_pos -= 1;
426            true
427        } else {
428            false
429        }
430    }
431
432    /// Moves the cursor one position to the right.
433    ///
434    /// Returns `true` if the cursor moved, `false` if already at the end.
435    pub fn move_cursor_right(&mut self) -> bool {
436        if self.cursor_pos < self.buffer.len() {
437            self.cursor_pos += 1;
438            true
439        } else {
440            false
441        }
442    }
443
444    /// Moves the cursor to the start of the line.
445    ///
446    /// Returns the number of positions the cursor moved.
447    pub fn move_cursor_to_start(&mut self) -> usize {
448        let old_pos = self.cursor_pos;
449        self.cursor_pos = 0;
450        old_pos
451    }
452
453    /// Moves the cursor to the end of the line.
454    ///
455    /// Returns the number of positions the cursor moved.
456    pub fn move_cursor_to_end(&mut self) -> usize {
457        let old_pos = self.cursor_pos;
458        self.cursor_pos = self.buffer.len();
459        self.buffer.len() - old_pos
460    }
461
462    /// Find start of word to the left
463    fn find_word_start_left(&self) -> usize {
464        if self.cursor_pos == 0 {
465            return 0;
466        }
467
468        let mut pos = self.cursor_pos;
469
470        // If we're on a word char, skip to start of current word
471        if pos > 0 && is_word_char(self.buffer[pos - 1]) {
472            while pos > 0 && is_word_char(self.buffer[pos - 1]) {
473                pos -= 1;
474            }
475        } else {
476            // Skip non-word chars
477            while pos > 0 && !is_word_char(self.buffer[pos - 1]) {
478                pos -= 1;
479            }
480            // Then skip word chars
481            while pos > 0 && is_word_char(self.buffer[pos - 1]) {
482                pos -= 1;
483            }
484        }
485
486        pos
487    }
488
489    /// Find start of word to the right
490    fn find_word_start_right(&self) -> usize {
491        if self.cursor_pos >= self.buffer.len() {
492            return self.buffer.len();
493        }
494
495        let mut pos = self.cursor_pos;
496
497        // If on word char, skip to end of current word
498        if pos < self.buffer.len() && is_word_char(self.buffer[pos]) {
499            while pos < self.buffer.len() && is_word_char(self.buffer[pos]) {
500                pos += 1;
501            }
502        }
503
504        // Skip non-word chars
505        while pos < self.buffer.len() && !is_word_char(self.buffer[pos]) {
506            pos += 1;
507        }
508
509        pos
510    }
511
512    /// Moves the cursor to the start of the previous word.
513    ///
514    /// Words are defined as sequences of alphanumeric characters and underscores.
515    /// Returns the number of positions the cursor moved.
516    pub fn move_cursor_word_left(&mut self) -> usize {
517        let target = self.find_word_start_left();
518        let moved = self.cursor_pos - target;
519        self.cursor_pos = target;
520        moved
521    }
522
523    /// Moves the cursor to the start of the next word.
524    ///
525    /// Words are defined as sequences of alphanumeric characters and underscores.
526    /// Returns the number of positions the cursor moved.
527    pub fn move_cursor_word_right(&mut self) -> usize {
528        let target = self.find_word_start_right();
529        let moved = target - self.cursor_pos;
530        self.cursor_pos = target;
531        moved
532    }
533
534    /// Deletes the word to the left of the cursor (Alt+Backspace operation).
535    ///
536    /// Returns the number of bytes deleted.
537    pub fn delete_word_left(&mut self) -> usize {
538        let target = self.find_word_start_left();
539        let count = self.cursor_pos - target;
540
541        for _ in 0..count {
542            if self.cursor_pos > 0 {
543                self.cursor_pos -= 1;
544                self.buffer.remove(self.cursor_pos);
545            }
546        }
547
548        count
549    }
550
551    /// Deletes the word to the right of the cursor (Ctrl+Delete operation).
552    ///
553    /// Returns the number of bytes deleted.
554    pub fn delete_word_right(&mut self) -> usize {
555        let target = self.find_word_start_right();
556        let count = target - self.cursor_pos;
557
558        for _ in 0..count {
559            if self.cursor_pos < self.buffer.len() {
560                self.buffer.remove(self.cursor_pos);
561            }
562        }
563
564        count
565    }
566
567    /// Loads text into the buffer, replacing existing content.
568    ///
569    /// The cursor is positioned at the end of the loaded text.
570    /// Used internally for history navigation.
571    pub fn load(&mut self, text: &str) {
572        self.buffer.clear();
573        self.buffer.extend_from_slice(text.as_bytes());
574        self.cursor_pos = self.buffer.len();
575    }
576}
577
578/// Check if a byte is a word character
579fn is_word_char(c: u8) -> bool {
580    c.is_ascii_alphanumeric() || c == b'_'
581}
582
583/// Command history manager with circular buffer storage.
584///
585/// Maintains a fixed-size history of entered commands with automatic
586/// duplicate and empty-line filtering. Supports bidirectional navigation
587/// and preserves the current line when browsing history.
588///
589/// # Examples
590///
591/// ```
592/// use editline::History;
593///
594/// let mut hist = History::new(50);
595/// hist.add("first command");
596/// hist.add("second command");
597///
598/// // Navigate through history
599/// assert_eq!(hist.previous(""), Some("second command"));
600/// assert_eq!(hist.previous(""), Some("first command"));
601/// ```
602pub struct History {
603    entries: Vec<String>,
604    capacity: usize,
605    current_entry: usize,
606    viewing_entry: Option<usize>,
607    saved_line: Option<String>,
608}
609
610impl History {
611    /// Creates a new history buffer with the specified capacity.
612    ///
613    /// When the capacity is reached, the oldest entries are overwritten.
614    ///
615    /// # Arguments
616    ///
617    /// * `capacity` - Maximum number of history entries to store
618    pub fn new(capacity: usize) -> Self {
619        Self {
620            entries: Vec::with_capacity(capacity),
621            capacity,
622            current_entry: 0,
623            viewing_entry: None,
624            saved_line: None,
625        }
626    }
627
628    /// Adds a line to the history.
629    ///
630    /// Empty lines (including whitespace-only) and consecutive duplicates are automatically skipped.
631    /// When the buffer is full, the oldest entry is overwritten.
632    ///
633    /// # Arguments
634    ///
635    /// * `line` - The command line to add to history
636    pub fn add(&mut self, line: &str) {
637        let trimmed = line.trim();
638
639        // Skip empty or whitespace-only lines
640        if trimmed.is_empty() {
641            return;
642        }
643
644        // Skip if same as most recent (after trimming)
645        if let Some(last) = self.entries.last() {
646            if last == trimmed {
647                return;
648            }
649        }
650
651        if self.entries.len() < self.capacity {
652            self.entries.push(trimmed.to_string());
653            self.current_entry = self.entries.len() - 1;
654        } else {
655            // Circular buffer - overwrite oldest
656            self.current_entry = (self.current_entry + 1) % self.capacity;
657            self.entries[self.current_entry] = trimmed.to_string();
658        }
659
660        self.viewing_entry = None;
661        self.saved_line = None;
662    }
663
664    /// Navigates to the previous (older) history entry.
665    ///
666    /// On the first call, saves `current_line` so it can be restored when
667    /// navigating forward past the most recent entry.
668    ///
669    /// # Arguments
670    ///
671    /// * `current_line` - The current line content to save (only used on first call)
672    ///
673    /// # Returns
674    ///
675    /// `Some(&str)` with the previous history entry, or `None` if at the oldest entry.
676    pub fn previous(&mut self, current_line: &str) -> Option<&str> {
677        if self.entries.is_empty() {
678            return None;
679        }
680
681        match self.viewing_entry {
682            None => {
683                // First time - save current line and start at most recent
684                self.saved_line = Some(current_line.to_string());
685                self.viewing_entry = Some(self.current_entry);
686                Some(&self.entries[self.current_entry])
687            }
688            Some(idx) => {
689                // Go further back
690                if self.entries.len() < self.capacity {
691                    // Haven't filled buffer yet
692                    if idx > 0 {
693                        let prev = idx - 1;
694                        self.viewing_entry = Some(prev);
695                        Some(&self.entries[prev])
696                    } else {
697                        None
698                    }
699                } else {
700                    // Buffer is full
701                    let prev = (idx + self.capacity - 1) % self.capacity;
702                    if prev == self.current_entry {
703                        None
704                    } else {
705                        self.viewing_entry = Some(prev);
706                        Some(&self.entries[prev])
707                    }
708                }
709            }
710        }
711    }
712
713    /// Navigates to the next (newer) history entry.
714    ///
715    /// When reaching the end of history, returns the saved current line
716    /// that was passed to the first [`previous`](Self::previous) call.
717    ///
718    /// # Returns
719    ///
720    /// `Some(&str)` with the next history entry or saved line, or `None` if
721    /// not currently viewing history.
722    pub fn next_entry(&mut self) -> Option<&str> {
723        match self.viewing_entry {
724            None => None,
725            Some(idx) => {
726                if self.entries.len() < self.capacity {
727                    // Haven't filled buffer yet
728                    if idx < self.entries.len() - 1 {
729                        let next = idx + 1;
730                        self.viewing_entry = Some(next);
731                        Some(&self.entries[next])
732                    } else {
733                        // Reached the end, return saved line
734                        self.viewing_entry = None;
735                        self.saved_line.as_deref()
736                    }
737                } else {
738                    // Buffer is full
739                    let next = (idx + 1) % self.capacity;
740                    if next == (self.current_entry + 1) % self.capacity {
741                        // Reached the end, return saved line
742                        self.viewing_entry = None;
743                        self.saved_line.as_deref()
744                    } else {
745                        self.viewing_entry = Some(next);
746                        Some(&self.entries[next])
747                    }
748                }
749            }
750        }
751    }
752
753    /// Resets the history view to the current line.
754    ///
755    /// Called when the user starts typing to exit history browsing mode.
756    pub fn reset_view(&mut self) {
757        self.viewing_entry = None;
758    }
759}
760
761/// Main line editor interface with full editing and history support.
762///
763/// Provides a high-level API for reading edited lines from any [`Terminal`]
764/// implementation. Handles all keyboard input, cursor movement, text editing,
765/// and history navigation.
766///
767/// # Examples
768///
769/// ```no_run
770/// use editline::{LineEditor, terminals::StdioTerminal};
771///
772/// let mut editor = LineEditor::new(1024, 50);
773/// let mut terminal = StdioTerminal::new();
774///
775/// match editor.read_line(&mut terminal) {
776///     Ok(line) => println!("Got: {}", line),
777///     Err(e) => eprintln!("Error: {}", e),
778/// }
779/// ```
780///
781/// # Key Bindings
782///
783/// - **Arrow keys**: Move cursor left/right, navigate history up/down
784/// - **Home/End**: Jump to start/end of line
785/// - **Backspace/Delete**: Delete characters
786/// - **Ctrl+Left/Right**: Move by word
787/// - **Alt+Backspace**: Delete word left
788/// - **Ctrl+Delete**: Delete word right
789/// - **Enter**: Submit line
790pub struct LineEditor {
791    line: LineBuffer,
792    history: History,
793}
794
795impl LineEditor {
796    /// Creates a new line editor with the specified capacities.
797    ///
798    /// # Arguments
799    ///
800    /// * `buffer_capacity` - Initial capacity for the line buffer in bytes
801    /// * `history_capacity` - Maximum number of history entries to store
802    ///
803    /// # Examples
804    ///
805    /// ```
806    /// use editline::LineEditor;
807    ///
808    /// // 1024 byte buffer, 50 history entries
809    /// let editor = LineEditor::new(1024, 50);
810    /// ```
811    pub fn new(buffer_capacity: usize, history_capacity: usize) -> Self {
812        Self {
813            line: LineBuffer::new(buffer_capacity),
814            history: History::new(history_capacity),
815        }
816    }
817
818    /// Reads a line from the terminal with full editing support.
819    ///
820    /// Enters raw mode, processes key events until Enter is pressed, then returns
821    /// the edited line with leading and trailing whitespace removed. The trimmed
822    /// line is automatically added to history if non-empty.
823    ///
824    /// # Arguments
825    ///
826    /// * `terminal` - Any type implementing the [`Terminal`] trait
827    ///
828    /// # Returns
829    ///
830    /// `Ok(String)` with the trimmed entered line, or `Err` if an I/O error occurs.
831    ///
832    /// # Examples
833    ///
834    /// ```no_run
835    /// use editline::{LineEditor, terminals::StdioTerminal};
836    ///
837    /// let mut editor = LineEditor::new(1024, 50);
838    /// let mut terminal = StdioTerminal::new();
839    ///
840    /// print!("> ");
841    /// std::io::Write::flush(&mut std::io::stdout()).unwrap();
842    ///
843    /// let line = editor.read_line(&mut terminal)?;
844    /// println!("You entered: {}", line);
845    /// # Ok::<(), editline::Error>(())
846    /// ```
847    pub fn read_line<T: Terminal>(&mut self, terminal: &mut T) -> Result<String> {
848        self.line.clear();
849        terminal.enter_raw_mode()?;
850
851        // Use a closure to ensure we always exit raw mode, even on error
852        let result = (|| {
853            loop {
854                let event = terminal.parse_key_event()?;
855
856                if event == KeyEvent::Enter {
857                    break;
858                }
859
860                self.handle_key_event(terminal, event)?;
861            }
862
863            terminal.write(b"\n")?;
864            terminal.flush()?;
865
866            let result = self.line.as_str()?
867                .trim()
868                .to_string();
869
870            // Add to history (History::add will check if empty and skip duplicates)
871            self.history.add(&result);
872            self.history.reset_view();
873
874            Ok(result)
875        })();
876
877        // Always exit raw mode, even if an error occurred
878        terminal.exit_raw_mode()?;
879
880        result
881    }
882
883    fn handle_key_event<T: Terminal>(&mut self, terminal: &mut T, event: KeyEvent) -> Result<()> {
884        match event {
885            KeyEvent::Normal(c) => {
886                self.history.reset_view();
887                self.line.insert_char(c);
888                terminal.write(c.to_string().as_bytes())?;
889                self.redraw_from_cursor(terminal)?;
890            }
891            KeyEvent::Left => {
892                if self.line.move_cursor_left() {
893                    terminal.cursor_left()?;
894                }
895            }
896            KeyEvent::Right => {
897                if self.line.move_cursor_right() {
898                    terminal.cursor_right()?;
899                }
900            }
901            KeyEvent::Up => {
902                let current = self.line.as_str().unwrap_or("").to_string();
903                if let Some(text) = self.history.previous(&current) {
904                    let text = text.to_string();
905                    self.load_history_into_line(terminal, &text)?;
906                }
907            }
908            KeyEvent::Down => {
909                if let Some(text) = self.history.next_entry() {
910                    let text = text.to_string();
911                    self.load_history_into_line(terminal, &text)?;
912                }
913                // If None, we're not viewing history, so do nothing
914            }
915            KeyEvent::Home => {
916                let count = self.line.move_cursor_to_start();
917                for _ in 0..count {
918                    terminal.cursor_left()?;
919                }
920            }
921            KeyEvent::End => {
922                let count = self.line.move_cursor_to_end();
923                for _ in 0..count {
924                    terminal.cursor_right()?;
925                }
926            }
927            KeyEvent::Backspace => {
928                self.history.reset_view();
929                if self.line.delete_before_cursor() {
930                    terminal.cursor_left()?;
931                    self.redraw_from_cursor(terminal)?;
932                }
933            }
934            KeyEvent::Delete => {
935                self.history.reset_view();
936                if self.line.delete_at_cursor() {
937                    self.redraw_from_cursor(terminal)?;
938                }
939            }
940            KeyEvent::CtrlLeft => {
941                let count = self.line.move_cursor_word_left();
942                for _ in 0..count {
943                    terminal.cursor_left()?;
944                }
945            }
946            KeyEvent::CtrlRight => {
947                let count = self.line.move_cursor_word_right();
948                for _ in 0..count {
949                    terminal.cursor_right()?;
950                }
951            }
952            KeyEvent::AltBackspace => {
953                self.history.reset_view();
954                let count = self.line.delete_word_left();
955                for _ in 0..count {
956                    terminal.cursor_left()?;
957                }
958                self.redraw_from_cursor(terminal)?;
959            }
960            KeyEvent::CtrlDelete => {
961                self.history.reset_view();
962                self.line.delete_word_right();
963                self.redraw_from_cursor(terminal)?;
964            }
965            KeyEvent::Enter => {}
966        }
967
968        terminal.flush()?;
969        Ok(())
970    }
971
972    fn redraw_from_cursor<T: Terminal>(&self, terminal: &mut T) -> Result<()> {
973        terminal.clear_eol()?;
974
975        let cursor_pos = self.line.cursor_pos();
976        let remaining = &self.line.as_bytes()[cursor_pos..];
977        terminal.write(remaining)?;
978
979        // Move cursor back
980        for _ in 0..remaining.len() {
981            terminal.cursor_left()?;
982        }
983
984        Ok(())
985    }
986
987    fn clear_line_display<T: Terminal>(&self, terminal: &mut T) -> Result<()> {
988        for _ in 0..self.line.cursor_pos() {
989            terminal.cursor_left()?;
990        }
991        terminal.clear_eol()?;
992        Ok(())
993    }
994
995    fn load_history_into_line<T: Terminal>(&mut self, terminal: &mut T, text: &str) -> Result<()> {
996        self.clear_line_display(terminal)?;
997        self.line.load(text);
998        terminal.write(text.as_bytes())?;
999        Ok(())
1000    }
1001}
1002
1003// Re-export terminal implementations (only with std feature)
1004#[cfg(feature = "std")]
1005pub mod terminals;
1006
1007#[cfg(test)]
1008mod tests {
1009    use super::*;
1010
1011    // LineBuffer tests
1012    #[test]
1013    fn test_line_buffer_insert() {
1014        let mut buf = LineBuffer::new(100);
1015        buf.insert_char('h');
1016        buf.insert_char('i');
1017        assert_eq!(buf.as_str().unwrap(), "hi");
1018        assert_eq!(buf.cursor_pos(), 2);
1019        assert_eq!(buf.len(), 2);
1020    }
1021
1022    #[test]
1023    fn test_line_buffer_backspace() {
1024        let mut buf = LineBuffer::new(100);
1025        buf.insert_char('h');
1026        buf.insert_char('i');
1027        assert!(buf.delete_before_cursor());
1028        assert_eq!(buf.as_str().unwrap(), "h");
1029        assert_eq!(buf.cursor_pos(), 1);
1030    }
1031
1032    #[test]
1033    fn test_line_buffer_delete() {
1034        let mut buf = LineBuffer::new(100);
1035        buf.insert_char('h');
1036        buf.insert_char('i');
1037        buf.move_cursor_left();
1038        assert!(buf.delete_at_cursor());
1039        assert_eq!(buf.as_str().unwrap(), "h");
1040        assert_eq!(buf.cursor_pos(), 1);
1041    }
1042
1043    #[test]
1044    fn test_line_buffer_cursor_movement() {
1045        let mut buf = LineBuffer::new(100);
1046        buf.insert_char('h');
1047        buf.insert_char('e');
1048        buf.insert_char('y');
1049        assert_eq!(buf.cursor_pos(), 3);
1050
1051        assert!(buf.move_cursor_left());
1052        assert_eq!(buf.cursor_pos(), 2);
1053
1054        assert!(buf.move_cursor_right());
1055        assert_eq!(buf.cursor_pos(), 3);
1056
1057        assert!(!buf.move_cursor_right()); // at end
1058    }
1059
1060    #[test]
1061    fn test_line_buffer_home_end() {
1062        let mut buf = LineBuffer::new(100);
1063        buf.insert_char('h');
1064        buf.insert_char('e');
1065        buf.insert_char('y');
1066
1067        buf.move_cursor_to_start();
1068        assert_eq!(buf.cursor_pos(), 0);
1069
1070        buf.move_cursor_to_end();
1071        assert_eq!(buf.cursor_pos(), 3);
1072    }
1073
1074    #[test]
1075    fn test_line_buffer_word_navigation() {
1076        let mut buf = LineBuffer::new(100);
1077        for c in "hello world test".chars() {
1078            buf.insert_char(c);
1079        }
1080
1081        // At end: "hello world test|"
1082        buf.move_cursor_word_left();
1083        assert_eq!(buf.cursor_pos(), 12); // "hello world |test"
1084
1085        buf.move_cursor_word_left();
1086        assert_eq!(buf.cursor_pos(), 6); // "hello |world test"
1087
1088        buf.move_cursor_word_right();
1089        assert_eq!(buf.cursor_pos(), 12); // "hello world |test"
1090    }
1091
1092    #[test]
1093    fn test_line_buffer_delete_word() {
1094        let mut buf = LineBuffer::new(100);
1095        for c in "hello world".chars() {
1096            buf.insert_char(c);
1097        }
1098
1099        buf.delete_word_left();
1100        assert_eq!(buf.as_str().unwrap(), "hello ");
1101
1102        buf.delete_word_left();
1103        assert_eq!(buf.as_str().unwrap(), "");
1104    }
1105
1106    #[test]
1107    fn test_line_buffer_delete_word_right() {
1108        let mut buf = LineBuffer::new(100);
1109        for c in "hello world".chars() {
1110            buf.insert_char(c);
1111        }
1112        buf.move_cursor_to_start();
1113
1114        buf.delete_word_right();
1115        assert_eq!(buf.as_str().unwrap(), "world");
1116    }
1117
1118    #[test]
1119    fn test_line_buffer_insert_middle() {
1120        let mut buf = LineBuffer::new(100);
1121        buf.insert_char('h');
1122        buf.insert_char('e');
1123        buf.move_cursor_left();
1124        buf.insert_char('x');
1125        assert_eq!(buf.as_str().unwrap(), "hxe");
1126        assert_eq!(buf.cursor_pos(), 2);
1127    }
1128
1129    // History tests
1130    #[test]
1131    fn test_history_add() {
1132        let mut hist = History::new(10);
1133        hist.add("first");
1134        hist.add("second");
1135
1136        assert_eq!(hist.previous(""), Some("second"));
1137        assert_eq!(hist.previous(""), Some("first"));
1138        assert_eq!(hist.previous(""), None); // no more
1139    }
1140
1141    #[test]
1142    fn test_history_skip_empty() {
1143        let mut hist = History::new(10);
1144        hist.add("first");
1145        hist.add("");
1146        hist.add("second");
1147
1148        assert_eq!(hist.previous(""), Some("second"));
1149        assert_eq!(hist.previous(""), Some("first"));
1150        assert_eq!(hist.previous(""), None);
1151    }
1152
1153    #[test]
1154    fn test_history_skip_duplicates() {
1155        let mut hist = History::new(10);
1156        hist.add("test");
1157        hist.add("test"); // should be skipped
1158        hist.add("other");
1159
1160        assert_eq!(hist.previous(""), Some("other"));
1161        assert_eq!(hist.previous(""), Some("test"));
1162        assert_eq!(hist.previous(""), None);
1163    }
1164
1165    #[test]
1166    fn test_history_navigation() {
1167        let mut hist = History::new(10);
1168        hist.add("first");
1169        hist.add("second");
1170        hist.add("third");
1171
1172        // Go back through history
1173        assert_eq!(hist.previous(""), Some("third"));
1174        assert_eq!(hist.previous(""), Some("second"));
1175
1176        // Go forward
1177        assert_eq!(hist.next_entry(), Some("third"));
1178        assert_eq!(hist.next_entry(), Some("")); // returns saved line (empty string)
1179    }
1180
1181    #[test]
1182    fn test_history_saves_current_line() {
1183        let mut hist = History::new(10);
1184        hist.add("first");
1185        hist.add("second");
1186
1187        // Start typing something
1188        assert_eq!(hist.previous("hello"), Some("second"));
1189        assert_eq!(hist.previous("hello"), Some("first"));
1190
1191        // Navigate back forward
1192        assert_eq!(hist.next_entry(), Some("second"));
1193        assert_eq!(hist.next_entry(), Some("hello")); // restored!
1194    }
1195
1196    #[test]
1197    fn test_history_down_without_up() {
1198        let mut hist = History::new(10);
1199        hist.add("first");
1200
1201        // Down without going up first should do nothing
1202        assert_eq!(hist.next_entry(), None);
1203    }
1204
1205    #[test]
1206    fn test_history_circular_buffer() {
1207        let mut hist = History::new(3);
1208        hist.add("first");
1209        hist.add("second");
1210        hist.add("third");
1211        hist.add("fourth"); // overwrites "first"
1212
1213        assert_eq!(hist.previous(""), Some("fourth"));
1214        assert_eq!(hist.previous(""), Some("third"));
1215        assert_eq!(hist.previous(""), Some("second"));
1216        assert_eq!(hist.previous(""), None); // "first" was overwritten
1217    }
1218
1219    #[test]
1220    fn test_history_reset_view() {
1221        let mut hist = History::new(10);
1222        hist.add("first");
1223        hist.add("second");
1224
1225        assert_eq!(hist.previous(""), Some("second"));
1226        hist.reset_view();
1227
1228        // After reset, previous() should start from most recent again
1229        assert_eq!(hist.previous(""), Some("second"));
1230    }
1231
1232    #[test]
1233    fn test_line_buffer_utf8() {
1234        let mut buf = LineBuffer::new(100);
1235        buf.insert_char('ä');
1236        buf.insert_char('ö');
1237        buf.insert_char('ü');
1238        assert_eq!(buf.as_str().unwrap(), "äöü");
1239        assert_eq!(buf.len(), 6); // UTF-8 bytes
1240    }
1241
1242    #[test]
1243    fn test_line_buffer_load() {
1244        let mut buf = LineBuffer::new(100);
1245        buf.insert_char('x');
1246        buf.load("hello world");
1247        assert_eq!(buf.as_str().unwrap(), "hello world");
1248        assert_eq!(buf.cursor_pos(), 11);
1249    }
1250}