1use crate::AuraError;
29use async_trait::async_trait;
30use serde::{Deserialize, Serialize};
31use std::fmt;
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
39pub enum TerminalEvent {
40 Key(KeyEvent),
42 Mouse(MouseEvent),
44 Resize { width: u16, height: u16 },
46 FocusGained,
48 FocusLost,
50 Paste(String),
52 Tick,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
58pub struct KeyEvent {
59 pub code: KeyCode,
61 pub modifiers: Modifiers,
63 pub kind: KeyEventKind,
65}
66
67impl KeyEvent {
68 pub fn press(code: KeyCode) -> Self {
70 Self {
71 code,
72 modifiers: Modifiers::NONE,
73 kind: KeyEventKind::Press,
74 }
75 }
76
77 pub fn press_with(code: KeyCode, modifiers: Modifiers) -> Self {
79 Self {
80 code,
81 modifiers,
82 kind: KeyEventKind::Press,
83 }
84 }
85
86 pub fn char(c: char) -> Self {
88 Self::press(KeyCode::Char(c))
89 }
90
91 pub fn ctrl(c: char) -> Self {
93 Self::press_with(KeyCode::Char(c), Modifiers::CTRL)
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
99pub enum KeyCode {
100 Backspace,
102 Enter,
104 Left,
106 Right,
108 Up,
110 Down,
112 Home,
114 End,
116 PageUp,
118 PageDown,
120 Tab,
122 BackTab,
124 Delete,
126 Insert,
128 F(u8),
130 Char(char),
132 Null,
134 Esc,
136 CapsLock,
138 ScrollLock,
140 NumLock,
142 PrintScreen,
144 Pause,
146 Menu,
148 KeypadBegin,
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
154pub enum KeyEventKind {
155 #[default]
157 Press,
158 Release,
160 Repeat,
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
166pub struct Modifiers(u8);
167
168impl Modifiers {
169 pub const NONE: Self = Self(0);
171 pub const SHIFT: Self = Self(1 << 0);
173 pub const CTRL: Self = Self(1 << 1);
175 pub const ALT: Self = Self(1 << 2);
177 pub const SUPER: Self = Self(1 << 3);
179 pub const HYPER: Self = Self(1 << 4);
181 pub const META: Self = Self(1 << 5);
183
184 pub fn shift(self) -> bool {
186 self.0 & Self::SHIFT.0 != 0
187 }
188
189 pub fn ctrl(self) -> bool {
191 self.0 & Self::CTRL.0 != 0
192 }
193
194 pub fn alt(self) -> bool {
196 self.0 & Self::ALT.0 != 0
197 }
198
199 pub fn super_key(self) -> bool {
201 self.0 & Self::SUPER.0 != 0
202 }
203
204 pub fn union(self, other: Self) -> Self {
206 Self(self.0 | other.0)
207 }
208}
209
210impl std::ops::BitOr for Modifiers {
211 type Output = Self;
212 fn bitor(self, rhs: Self) -> Self {
213 Self(self.0 | rhs.0)
214 }
215}
216
217#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
219pub struct MouseEvent {
220 pub kind: MouseEventKind,
222 pub column: u16,
224 pub row: u16,
226 pub modifiers: Modifiers,
228}
229
230#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
232pub enum MouseEventKind {
233 Down(MouseButton),
235 Up(MouseButton),
237 Drag(MouseButton),
239 Moved,
241 ScrollUp,
243 ScrollDown,
245 ScrollLeft,
247 ScrollRight,
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
253pub enum MouseButton {
254 Left,
256 Right,
258 Middle,
260}
261
262#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
268pub struct TerminalFrame {
269 pub width: u16,
271 pub height: u16,
273 pub cells: Vec<Vec<Cell>>,
275 pub cursor: Option<CursorPosition>,
277 pub sequence: u64,
279}
280
281impl TerminalFrame {
282 pub fn new(width: u16, height: u16) -> Self {
284 let cells = (0..height)
285 .map(|_| (0..width).map(|_| Cell::default()).collect())
286 .collect();
287 Self {
288 width,
289 height,
290 cells,
291 cursor: None,
292 sequence: 0,
293 }
294 }
295
296 pub fn get(&self, col: u16, row: u16) -> Option<&Cell> {
298 self.cells.get(row as usize)?.get(col as usize)
299 }
300
301 pub fn set(&mut self, col: u16, row: u16, cell: Cell) {
303 if let Some(row_cells) = self.cells.get_mut(row as usize) {
304 if let Some(existing) = row_cells.get_mut(col as usize) {
305 *existing = cell;
306 }
307 }
308 }
309
310 pub fn row_text(&self, row: u16) -> String {
312 self.cells
313 .get(row as usize)
314 .map(|cells| cells.iter().map(|c| c.char).collect())
315 .unwrap_or_default()
316 }
317
318 pub fn text(&self) -> String {
320 self.cells
321 .iter()
322 .map(|row| row.iter().map(|c| c.char).collect::<String>())
323 .collect::<Vec<_>>()
324 .join("\n")
325 }
326
327 pub fn contains(&self, text: &str) -> bool {
329 for row in &self.cells {
331 let row_text: String = row.iter().map(|c| c.char).collect();
332 if row_text.contains(text) {
333 return true;
334 }
335 }
336 self.text().contains(text)
338 }
339
340 pub fn find(&self, text: &str) -> Option<(u16, u16)> {
342 for (row_idx, row) in self.cells.iter().enumerate() {
343 let row_text: String = row.iter().map(|c| c.char).collect();
344 if let Some(col_idx) = row_text.find(text) {
345 return Some((col_idx as u16, row_idx as u16));
346 }
347 }
348 None
349 }
350}
351
352impl fmt::Display for TerminalFrame {
353 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354 for row in &self.cells {
355 for cell in row {
356 write!(f, "{}", cell.char)?;
357 }
358 writeln!(f)?;
359 }
360 Ok(())
361 }
362}
363
364#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
366pub struct Cell {
367 pub char: char,
369 pub fg: Color,
371 pub bg: Color,
373 pub style: Style,
375}
376
377impl Default for Cell {
378 fn default() -> Self {
379 Self {
380 char: ' ',
381 fg: Color::Reset,
382 bg: Color::Reset,
383 style: Style::empty(),
384 }
385 }
386}
387
388impl Cell {
389 pub fn new(c: char) -> Self {
391 Self {
392 char: c,
393 ..Default::default()
394 }
395 }
396
397 #[must_use]
399 pub fn with_fg(c: char, fg: Color) -> Self {
400 Self {
401 char: c,
402 fg,
403 ..Default::default()
404 }
405 }
406}
407
408#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
410pub enum Color {
411 #[default]
413 Reset,
414 Black,
416 DarkRed,
418 DarkGreen,
420 DarkYellow,
422 DarkBlue,
424 DarkMagenta,
426 DarkCyan,
428 Gray,
430 DarkGray,
432 Red,
434 Green,
436 Yellow,
438 Blue,
440 Magenta,
442 Cyan,
444 White,
446 Indexed(u8),
448 Rgb { r: u8, g: u8, b: u8 },
450}
451
452#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
454pub struct Style(u16);
455
456impl Style {
457 pub const fn empty() -> Self {
459 Self(0)
460 }
461
462 pub const BOLD: Self = Self(1 << 0);
464 pub const DIM: Self = Self(1 << 1);
466 pub const ITALIC: Self = Self(1 << 2);
468 pub const UNDERLINED: Self = Self(1 << 3);
470 pub const SLOW_BLINK: Self = Self(1 << 4);
472 pub const RAPID_BLINK: Self = Self(1 << 5);
474 pub const REVERSED: Self = Self(1 << 6);
476 pub const HIDDEN: Self = Self(1 << 7);
478 pub const CROSSED_OUT: Self = Self(1 << 8);
480
481 pub fn is_bold(self) -> bool {
483 self.0 & Self::BOLD.0 != 0
484 }
485
486 pub fn is_underlined(self) -> bool {
488 self.0 & Self::UNDERLINED.0 != 0
489 }
490
491 pub fn union(self, other: Self) -> Self {
493 Self(self.0 | other.0)
494 }
495}
496
497impl std::ops::BitOr for Style {
498 type Output = Self;
499 fn bitor(self, rhs: Self) -> Self {
500 Self(self.0 | rhs.0)
501 }
502}
503
504#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
506pub struct CursorPosition {
507 pub col: u16,
509 pub row: u16,
511 pub shape: CursorShape,
513 pub blinking: bool,
515}
516
517#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
519pub enum CursorShape {
520 #[default]
522 Block,
523 Underline,
525 Bar,
527}
528
529#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
535pub enum TerminalError {
536 EndOfInput,
538 IoError(String),
540 NotAvailable,
542 InvalidOperation(String),
544 Timeout,
546}
547
548impl fmt::Display for TerminalError {
549 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550 match self {
551 Self::EndOfInput => write!(f, "end of input"),
552 Self::IoError(msg) => write!(f, "terminal I/O error: {msg}"),
553 Self::NotAvailable => write!(f, "terminal not available"),
554 Self::InvalidOperation(msg) => write!(f, "invalid operation: {msg}"),
555 Self::Timeout => write!(f, "timeout waiting for terminal event"),
556 }
557 }
558}
559
560impl std::error::Error for TerminalError {}
561
562impl From<TerminalError> for AuraError {
563 fn from(err: TerminalError) -> Self {
564 AuraError::Terminal(err.to_string())
565 }
566}
567
568#[async_trait]
580pub trait TerminalInputEffects: Send + Sync {
581 async fn next_event(&self) -> Result<TerminalEvent, TerminalError>;
586
587 async fn poll_event(&self, timeout_ms: u64) -> Result<Option<TerminalEvent>, TerminalError>;
593
594 async fn has_input(&self) -> bool;
596}
597
598#[async_trait]
606pub trait TerminalOutputEffects: Send + Sync {
607 async fn render(&self, frame: TerminalFrame) -> Result<(), TerminalError>;
612
613 async fn size(&self) -> Result<(u16, u16), TerminalError>;
615
616 async fn clear(&self) -> Result<(), TerminalError>;
618
619 async fn set_cursor(&self, col: u16, row: u16) -> Result<(), TerminalError>;
621
622 async fn set_cursor_visible(&self, visible: bool) -> Result<(), TerminalError>;
624
625 async fn set_cursor_shape(&self, shape: CursorShape) -> Result<(), TerminalError>;
627
628 async fn enter_alternate_screen(&self) -> Result<(), TerminalError>;
630
631 async fn leave_alternate_screen(&self) -> Result<(), TerminalError>;
633
634 async fn enable_raw_mode(&self) -> Result<(), TerminalError>;
636
637 async fn disable_raw_mode(&self) -> Result<(), TerminalError>;
639}
640
641pub trait TerminalEffects: TerminalInputEffects + TerminalOutputEffects {}
643
644impl<T: TerminalInputEffects + TerminalOutputEffects> TerminalEffects for T {}
646
647#[async_trait]
652impl<T: TerminalInputEffects + ?Sized> TerminalInputEffects for std::sync::Arc<T> {
653 async fn next_event(&self) -> Result<TerminalEvent, TerminalError> {
654 (**self).next_event().await
655 }
656
657 async fn poll_event(&self, timeout_ms: u64) -> Result<Option<TerminalEvent>, TerminalError> {
658 (**self).poll_event(timeout_ms).await
659 }
660
661 async fn has_input(&self) -> bool {
662 (**self).has_input().await
663 }
664}
665
666#[async_trait]
667impl<T: TerminalOutputEffects + ?Sized> TerminalOutputEffects for std::sync::Arc<T> {
668 async fn render(&self, frame: TerminalFrame) -> Result<(), TerminalError> {
669 (**self).render(frame).await
670 }
671
672 async fn size(&self) -> Result<(u16, u16), TerminalError> {
673 (**self).size().await
674 }
675
676 async fn clear(&self) -> Result<(), TerminalError> {
677 (**self).clear().await
678 }
679
680 async fn set_cursor(&self, col: u16, row: u16) -> Result<(), TerminalError> {
681 (**self).set_cursor(col, row).await
682 }
683
684 async fn set_cursor_visible(&self, visible: bool) -> Result<(), TerminalError> {
685 (**self).set_cursor_visible(visible).await
686 }
687
688 async fn set_cursor_shape(&self, shape: CursorShape) -> Result<(), TerminalError> {
689 (**self).set_cursor_shape(shape).await
690 }
691
692 async fn enter_alternate_screen(&self) -> Result<(), TerminalError> {
693 (**self).enter_alternate_screen().await
694 }
695
696 async fn leave_alternate_screen(&self) -> Result<(), TerminalError> {
697 (**self).leave_alternate_screen().await
698 }
699
700 async fn enable_raw_mode(&self) -> Result<(), TerminalError> {
701 (**self).enable_raw_mode().await
702 }
703
704 async fn disable_raw_mode(&self) -> Result<(), TerminalError> {
705 (**self).disable_raw_mode().await
706 }
707}
708
709pub mod events {
715 use super::*;
716
717 pub fn char(c: char) -> TerminalEvent {
719 TerminalEvent::Key(KeyEvent::char(c))
720 }
721
722 pub fn key(code: KeyCode) -> TerminalEvent {
724 TerminalEvent::Key(KeyEvent::press(code))
725 }
726
727 pub fn enter() -> TerminalEvent {
729 key(KeyCode::Enter)
730 }
731
732 pub fn escape() -> TerminalEvent {
734 key(KeyCode::Esc)
735 }
736
737 pub fn tab() -> TerminalEvent {
739 key(KeyCode::Tab)
740 }
741
742 pub fn backspace() -> TerminalEvent {
744 key(KeyCode::Backspace)
745 }
746
747 pub fn arrow_up() -> TerminalEvent {
749 key(KeyCode::Up)
750 }
751
752 pub fn arrow_down() -> TerminalEvent {
754 key(KeyCode::Down)
755 }
756
757 pub fn arrow_left() -> TerminalEvent {
759 key(KeyCode::Left)
760 }
761
762 pub fn arrow_right() -> TerminalEvent {
764 key(KeyCode::Right)
765 }
766
767 pub fn ctrl(c: char) -> TerminalEvent {
769 TerminalEvent::Key(KeyEvent::ctrl(c))
770 }
771
772 pub fn function(n: u8) -> TerminalEvent {
774 key(KeyCode::F(n))
775 }
776
777 pub fn resize(width: u16, height: u16) -> TerminalEvent {
779 TerminalEvent::Resize { width, height }
780 }
781
782 pub fn tick() -> TerminalEvent {
784 TerminalEvent::Tick
785 }
786
787 pub fn type_str(s: &str) -> Vec<TerminalEvent> {
789 s.chars().map(char).collect()
790 }
791}
792
793#[cfg(test)]
794mod tests {
795 use super::*;
796
797 #[test]
798 fn test_key_event_creation() {
799 let event = KeyEvent::char('a');
800 assert_eq!(event.code, KeyCode::Char('a'));
801 assert_eq!(event.modifiers, Modifiers::NONE);
802 assert_eq!(event.kind, KeyEventKind::Press);
803 }
804
805 #[test]
806 fn test_ctrl_key() {
807 let event = KeyEvent::ctrl('c');
808 assert_eq!(event.code, KeyCode::Char('c'));
809 assert!(event.modifiers.ctrl());
810 }
811
812 #[test]
813 fn test_frame_contains() {
814 let mut frame = TerminalFrame::new(20, 5);
815 for (i, c) in "Hello".chars().enumerate() {
817 frame.set(i as u16, 1, Cell::new(c));
818 }
819
820 assert!(frame.contains("Hello"));
821 assert!(frame.contains("ell"));
822 assert!(!frame.contains("World"));
823 }
824
825 #[test]
826 fn test_frame_find() {
827 let mut frame = TerminalFrame::new(20, 5);
828 for (i, c) in "Test".chars().enumerate() {
830 frame.set(5 + i as u16, 2, Cell::new(c));
831 }
832
833 assert_eq!(frame.find("Test"), Some((5, 2)));
834 assert_eq!(frame.find("Missing"), None);
835 }
836
837 #[test]
838 fn test_event_helpers() {
839 use events::*;
840
841 let events = [char('h'), char('i'), enter(), escape()];
842
843 assert_eq!(events.len(), 4);
844
845 let typed = type_str("hello");
846 assert_eq!(typed.len(), 5);
847 }
848
849 #[test]
850 fn test_modifiers() {
851 let ctrl_shift = Modifiers::CTRL | Modifiers::SHIFT;
852 assert!(ctrl_shift.ctrl());
853 assert!(ctrl_shift.shift());
854 assert!(!ctrl_shift.alt());
855 }
856}