use std::sync::Arc; use crate::render_signal::RenderSignal; use bytes::Bytes; use ratatui::{layout::Rect, Frame}; use tokio::sync::{mpsc, Notify}; use crate::events::AppEvent; use crate::layout::PaneId; /// Live runtime for a server-owned terminal. /// /// The PTY implementation still delegates to the legacy pane runtime while the /// migration proceeds, but production code now depends on this terminal-layer /// type instead of the pane module's implementation detail. pub struct TerminalRuntime(crate::pane::PaneRuntime); impl TerminalRuntime { pub fn shutdown(self) { self.0.shutdown(); } #[cfg(unix)] pub fn duplicate_handoff_fd(&self) -> std::io::Result { self.0.duplicate_handoff_fd() } #[cfg(unix)] pub fn preserve_for_handoff(self) { self.0.preserve_for_handoff() } #[cfg(unix)] pub fn assume_handoff_ownership(&mut self) { self.0.assume_handoff_ownership(); } #[cfg(unix)] pub fn set_handoff_reader_paused(&self, paused: bool) { self.0.set_handoff_reader_paused(paused); } #[cfg(unix)] pub fn pause_handoff_reader(&self, timeout: std::time::Duration) -> std::io::Result<()> { self.0.pause_handoff_reader(timeout) } #[cfg(unix)] pub fn handoff_runtime_state( &self, pane_id: u32, ) -> crate::handoff_runtime::HandoffRuntimeState { self.0.handoff_runtime_state(pane_id) } #[cfg(unix)] pub fn handoff_history_ansi(&self) -> Option { self.0.handoff_history_ansi() } #[cfg(unix)] pub fn from_handoff_fd( import: crate::handoff_runtime::ImportedHandoffRuntime, scrollback_limit_bytes: usize, host_terminal_theme: crate::terminal_theme::TerminalTheme, host_terminal_appearance: Option, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, ) -> std::io::Result { crate::pane::PaneRuntime::from_handoff_fd( import, scrollback_limit_bytes, host_terminal_theme, host_terminal_appearance, events, render_notify, render_dirty, ) .map(Self) } // Wrapper mirrors pane runtime construction arguments. #[allow(clippy::too_many_arguments)] pub fn spawn( pane_id: PaneId, rows: u16, cols: u16, cwd: std::path::PathBuf, scrollback_limit_bytes: usize, host_terminal_theme: crate::terminal_theme::TerminalTheme, host_terminal_appearance: Option, shell_config: crate::pane::PaneShellConfig<'_>, launch_env: &crate::pane::PaneLaunchEnv, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, ) -> std::io::Result { crate::pane::PaneRuntime::spawn( pane_id, rows, cols, cwd, scrollback_limit_bytes, host_terminal_theme, host_terminal_appearance, shell_config, launch_env, events, render_notify, render_dirty, ) .map(Self) } // Wrapper mirrors pane runtime construction arguments. #[allow(clippy::too_many_arguments)] pub fn spawn_with_initial_history( pane_id: PaneId, rows: u16, cols: u16, cwd: std::path::PathBuf, scrollback_limit_bytes: usize, host_terminal_theme: crate::terminal_theme::TerminalTheme, host_terminal_appearance: Option, shell_config: crate::pane::PaneShellConfig<'_>, launch_env: &crate::pane::PaneLaunchEnv, initial_history_ansi: Option<&str>, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, ) -> std::io::Result { crate::pane::PaneRuntime::spawn_with_initial_history( pane_id, rows, cols, cwd, scrollback_limit_bytes, host_terminal_theme, host_terminal_appearance, shell_config, launch_env, initial_history_ansi, events, render_notify, render_dirty, ) .map(Self) } // Wrapper mirrors pane runtime construction arguments. #[allow(clippy::too_many_arguments)] pub fn spawn_shell_command( pane_id: PaneId, rows: u16, cols: u16, cwd: std::path::PathBuf, command: &str, launch_env: &crate::pane::PaneLaunchEnv, agent_detection: crate::pane::AgentDetection, scrollback_limit_bytes: usize, host_terminal_theme: crate::terminal_theme::TerminalTheme, host_terminal_appearance: Option, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, ) -> std::io::Result { crate::pane::PaneRuntime::spawn_shell_command( pane_id, rows, cols, cwd, command, launch_env, agent_detection, scrollback_limit_bytes, host_terminal_theme, host_terminal_appearance, events, render_notify, render_dirty, ) .map(Self) } // Wrapper mirrors pane runtime construction arguments, including detection policy. #[allow(clippy::too_many_arguments)] pub fn spawn_argv_command( pane_id: PaneId, rows: u16, cols: u16, cwd: std::path::PathBuf, argv: &[String], launch_env: &crate::pane::PaneLaunchEnv, agent_detection: crate::pane::AgentDetection, scrollback_limit_bytes: usize, host_terminal_theme: crate::terminal_theme::TerminalTheme, host_terminal_appearance: Option, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, ) -> std::io::Result { crate::pane::PaneRuntime::spawn_argv_command( pane_id, rows, cols, cwd, argv, launch_env, agent_detection, scrollback_limit_bytes, host_terminal_theme, host_terminal_appearance, events, render_notify, render_dirty, ) .map(Self) } pub fn apply_host_terminal_theme(&self, theme: crate::terminal_theme::TerminalTheme) { self.0.apply_host_terminal_theme(theme); } pub fn apply_host_terminal_appearance( &self, appearance: Option, ) { self.0.apply_host_terminal_appearance(appearance); } pub fn begin_graceful_release(&self, agent: crate::detect::Agent) { self.0.begin_graceful_release(agent); } pub fn reset_agent_detection(&self) { self.0.reset_agent_detection(); } #[cfg(test)] pub(crate) fn agent_detection_reset_notify_for_test( &self, ) -> std::sync::Arc { self.0.agent_detection_reset_notify_for_test() } pub fn set_full_lifecycle_authority_active(&self, active: bool) { self.0.set_full_lifecycle_authority_active(active); } pub fn resize(&self, rows: u16, cols: u16, cell_width_px: u32, cell_height_px: u32) { self.0.resize(rows, cols, cell_width_px, cell_height_px); } #[cfg(unix)] pub fn nudge_child_redraw_after_handoff(&self) { self.0.nudge_child_redraw_after_handoff(); } pub fn scroll_up(&self, lines: usize) { self.0.scroll_up(lines); } pub fn scroll_down(&self, lines: usize) { self.0.scroll_down(lines); } pub fn scroll_reset(&self) { self.0.scroll_reset(); } pub fn set_scroll_offset_from_bottom(&self, lines: usize) { self.0.set_scroll_offset_from_bottom(lines); } pub fn scroll_metrics(&self) -> Option { self.0.scroll_metrics() } pub(crate) fn search_text_window( &self, query: &str, case_sensitive: bool, direction: crate::pane::TerminalSearchDirection, cursor: crate::pane::TerminalTextPoint, previous: Option<( crate::pane::TerminalTextPoint, crate::pane::TerminalTextPoint, )>, limit: usize, ) -> crate::pane::TerminalSearchWindow { self.0 .search_text_window(query, case_sensitive, direction, cursor, previous, limit) } pub(crate) fn word_motion_target( &self, row: u32, col: u16, motion: crate::pane::TerminalWordMotion, ) -> Option { self.0.word_motion_target(row, col, motion) } pub(crate) fn terminal_dimensions(&self) -> Option<(u16, u16)> { self.0.terminal_dimensions() } pub(crate) fn paragraph_motion_target( &self, row: u32, direction: i8, ) -> Option { self.0.paragraph_motion_target(row, direction) } pub fn bracketed_paste_enabled(&self) -> bool { self.0.bracketed_paste_enabled() } pub fn mouse_reporting_enabled(&self) -> bool { self.0.mouse_reporting_enabled() } pub fn sgr_pixel_mouse_enabled(&self) -> bool { self.0.sgr_pixel_mouse_enabled() } pub fn plain_page_keys_use_host_scrollback(&self) -> Option { self.0.plain_page_keys_use_host_scrollback() } /// Reads only whether the alternate screen is active. pub fn alternate_screen_active(&self) -> bool { self.0.alternate_screen_active() } pub fn cursor_state( &self, area: Rect, show_cursor: bool, ) -> Option { self.0.cursor_state(area, show_cursor) } pub fn synchronized_output_active(&self) -> bool { self.0.synchronized_output_active() } pub fn visible_text(&self) -> String { self.0.visible_text() } pub fn visible_ansi(&self) -> String { self.0.visible_ansi() } pub fn detection_text(&self) -> String { self.0.detection_text() } pub fn terminal_title(&self) -> Option { self.0.terminal_title() } pub fn agent_osc_title(&self) -> String { self.0.agent_osc_title() } pub fn agent_osc_progress(&self) -> String { self.0.agent_osc_progress() } pub(crate) fn recent_text_snapshot(&self, lines: usize) -> crate::pane::TerminalReadSnapshot { self.0.recent_text_snapshot(lines) } pub(crate) fn recent_ansi_snapshot(&self, lines: usize) -> crate::pane::TerminalReadSnapshot { self.0.recent_ansi_snapshot(lines) } #[cfg(test)] pub fn recent_unwrapped_text(&self, lines: usize) -> String { self.0.recent_unwrapped_text_snapshot(lines).text } pub(crate) fn recent_unwrapped_text_snapshot( &self, lines: usize, ) -> crate::pane::TerminalReadSnapshot { self.0.recent_unwrapped_text_snapshot(lines) } pub(crate) fn recent_unwrapped_ansi_snapshot( &self, lines: usize, ) -> crate::pane::TerminalReadSnapshot { self.0.recent_unwrapped_ansi_snapshot(lines) } pub fn snapshot_history(&self) -> Option { self.0.snapshot_history() } pub fn extract_selection(&self, selection: &crate::selection::Selection) -> Option { self.0.extract_selection(selection) } pub fn render(&self, frame: &mut Frame, area: Rect, show_cursor: bool) { self.0.render(frame, area, show_cursor); } pub(crate) fn collect_dirty_patch( &self, area_width: u16, area_height: u16, ) -> crate::pane::TerminalDirtyPatchOutcome { self.0.collect_dirty_patch(area_width, area_height) } pub fn visible_hyperlinks(&self, area: Rect) -> Vec<((u16, u16), String, String)> { self.0.visible_hyperlinks(area) } pub fn kitty_image_placements_with_data_filter( &self, needs_data: F, ) -> Vec where F: FnMut(crate::ghostty::KittyImageDescriptor) -> bool, { self.0.kitty_image_placements_with_data_filter(needs_data) } pub fn keyboard_protocol(&self) -> crate::input::KeyboardProtocol { self.0.keyboard_protocol() } pub fn modify_other_keys_level(&self) -> u8 { self.0.modify_other_keys_level() } pub fn encode_terminal_key(&self, key: crate::input::TerminalKey) -> Vec { self.0.encode_terminal_key(key) } pub fn try_send_bytes(&self, bytes: Bytes) -> Result<(), mpsc::error::TrySendError> { self.0.try_send_bytes(bytes) } pub fn queue_user_input_submission( &self, text: Bytes, enter: Bytes, delay: std::time::Duration, ) -> std::io::Result>> { self.0.queue_user_input_submission(text, enter, delay) } pub fn try_send_paste(&self, text: String) -> Result<(), mpsc::error::TrySendError> { self.0.try_send_paste(text) } pub fn try_send_focus_event(&self, event: crate::ghostty::FocusEvent) -> bool { self.0.try_send_focus_event(event) } pub fn wheel_routing(&self) -> Option { self.0.wheel_routing() } pub(crate) fn screen_text_snapshot( &self, ) -> Option<( crate::ghostty::ActiveScreen, crate::terminal::ScreenSnapshot, )> { let (screen, cols, rows) = self.0.screen_text_snapshot()?; Some((screen, crate::terminal::ScreenSnapshot { cols, rows })) } pub(crate) fn screen_text_snapshot_with_seq( &self, ) -> Option<( crate::ghostty::ActiveScreen, crate::terminal::ScreenSnapshot, u64, )> { for _ in 0..3 { let before = self.content_seq(); if !before.is_multiple_of(2) { continue; } let (screen, snapshot) = self.screen_text_snapshot()?; let after = self.content_seq(); if before == after { return Some((screen, snapshot, after)); } } None } pub fn encode_mouse_button( &self, kind: crossterm::event::MouseEventKind, position: crate::input::mouse::Position, modifiers: crossterm::event::KeyModifiers, ) -> Option> { self.0.encode_mouse_button(kind, position, modifiers) } pub(crate) fn encode_mouse_motion( &self, kind: crossterm::event::MouseEventKind, position: crate::input::mouse::Position, modifiers: crossterm::event::KeyModifiers, ) -> Option> { self.0.encode_mouse_motion(kind, position, modifiers) } pub(crate) fn encode_mouse_wheel( &self, kind: crossterm::event::MouseEventKind, position: crate::input::mouse::Position, modifiers: crossterm::event::KeyModifiers, ) -> Option> { self.0.encode_mouse_wheel(kind, position, modifiers) } pub(crate) fn pixel_size(&self) -> Option<(u32, u32)> { self.0.pixel_size() } pub fn encode_alternate_scroll( &self, kind: crossterm::event::MouseEventKind, ) -> Option> { self.0.encode_alternate_scroll(kind) } pub fn cwd(&self) -> Option { self.0.cwd() } pub fn follow_cwd(&self) -> Option { self.0.follow_cwd() } pub fn foreground_cwd(&self) -> Option { self.0.foreground_cwd() } pub fn child_pid(&self) -> Option { self.0.child_pid() } pub(crate) fn current_size(&self) -> (u16, u16) { self.0.current_size() } pub(crate) fn content_seq(&self) -> u64 { self.0.content_seq() } } #[cfg(test)] impl TerminalRuntime { pub(crate) fn test_with_channel(cols: u16, rows: u16) -> (Self, mpsc::Receiver) { let (runtime, rx) = crate::pane::PaneRuntime::test_with_channel(cols, rows); (Self(runtime), rx) } pub(crate) fn test_with_channel_capacity( cols: u16, rows: u16, capacity: usize, ) -> (Self, mpsc::Receiver) { let (runtime, rx) = crate::pane::PaneRuntime::test_with_channel_capacity(cols, rows, capacity); (Self(runtime), rx) } pub(crate) fn test_with_screen_bytes(cols: u16, rows: u16, bytes: &[u8]) -> Self { Self(crate::pane::PaneRuntime::test_with_screen_bytes( cols, rows, bytes, )) } pub(crate) fn test_process_pty_bytes(&self, bytes: &[u8]) { self.0.test_process_pty_bytes(bytes); } pub(crate) fn test_with_scrollback_bytes( cols: u16, rows: u16, scrollback_limit_bytes: usize, bytes: &[u8], ) -> Self { Self(crate::pane::PaneRuntime::test_with_scrollback_bytes( cols, rows, scrollback_limit_bytes, bytes, )) } pub(crate) fn test_with_channel_and_scrollback_bytes( cols: u16, rows: u16, scrollback_limit_bytes: usize, bytes: &[u8], channel_capacity: usize, ) -> (Self, mpsc::Receiver) { let (runtime, rx) = crate::pane::PaneRuntime::test_with_channel_and_scrollback_bytes( cols, rows, scrollback_limit_bytes, bytes, channel_capacity, ); (Self(runtime), rx) } }