supercode-frontend-tui 0.4.5

Attachable terminal frontend primitives for Supercode SDK runtimes.
Documentation
// Derived from OpenAI Codex: codex-rs/tui/src/tui/event_stream.rs
// Pinned source: 8604689ec5e3437eb79802d8d72249b7722fbf5b
// Copyright 2025 OpenAI
// Licensed under the Apache License, Version 2.0.
// Modified by the Supercode contributors; see docs/legal/codex-frontend-extraction.toml.

//! Runtime-neutral mapping of Crossterm input, focus, and resize events.

use crossterm::event::Event;
use crossterm::event::KeyEvent;

/// Terminal input the frontend may translate into SDK actions.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TerminalEvent {
    Key(KeyEvent),
    Paste(String),
    Resize { width: u16, height: u16 },
    FocusChanged(bool),
}

/// Map one Crossterm event without introducing app/session behavior.
pub fn map_crossterm_event(event: Event) -> Option<TerminalEvent> {
    match event {
        Event::Key(key) => Some(TerminalEvent::Key(key)),
        Event::Paste(text) => Some(TerminalEvent::Paste(text)),
        Event::Resize(width, height) => Some(TerminalEvent::Resize { width, height }),
        Event::FocusGained => Some(TerminalEvent::FocusChanged(true)),
        Event::FocusLost => Some(TerminalEvent::FocusChanged(false)),
        Event::Mouse(_) => None,
    }
}

/// Focus and viewport state used by render scheduling and notifications.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TerminalState {
    pub width: u16,
    pub height: u16,
    pub focused: bool,
}

impl TerminalState {
    pub const fn new(width: u16, height: u16) -> Self {
        Self {
            width,
            height,
            focused: true,
        }
    }

    /// Apply focus/resize state and report whether a redraw is required.
    pub fn apply(&mut self, event: &TerminalEvent) -> bool {
        match event {
            TerminalEvent::Resize { width, height } => {
                let changed = self.width != *width || self.height != *height;
                self.width = *width;
                self.height = *height;
                changed
            }
            TerminalEvent::FocusChanged(focused) => {
                let changed = self.focused != *focused;
                self.focused = *focused;
                changed
            }
            TerminalEvent::Key(_) | TerminalEvent::Paste(_) => false,
        }
    }
}

#[cfg(test)]
mod tests {
    use crossterm::event::KeyCode;
    use crossterm::event::KeyEvent;
    use crossterm::event::KeyModifiers;

    use super::*;

    #[test]
    fn maps_focus_resize_paste_and_key_without_app_events() {
        assert_eq!(
            map_crossterm_event(Event::FocusLost),
            Some(TerminalEvent::FocusChanged(false))
        );
        assert_eq!(
            map_crossterm_event(Event::FocusGained),
            Some(TerminalEvent::FocusChanged(true))
        );
        assert_eq!(
            map_crossterm_event(Event::Resize(80, 24)),
            Some(TerminalEvent::Resize {
                width: 80,
                height: 24
            })
        );
        assert_eq!(
            map_crossterm_event(Event::Paste("hello".into())),
            Some(TerminalEvent::Paste("hello".into()))
        );
        let key = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);
        assert_eq!(
            map_crossterm_event(Event::Key(key)),
            Some(TerminalEvent::Key(key))
        );
    }

    #[test]
    fn focus_and_resize_change_state_and_require_redraw() {
        let mut state = TerminalState::new(80, 24);
        assert!(state.apply(&TerminalEvent::FocusChanged(false)));
        assert!(!state.focused);
        assert!(!state.apply(&TerminalEvent::FocusChanged(false)));
        assert!(state.apply(&TerminalEvent::Resize {
            width: 160,
            height: 48,
        }));
        assert_eq!((state.width, state.height), (160, 48));
        assert!(!state.apply(&TerminalEvent::Resize {
            width: 160,
            height: 48,
        }));
    }
}