Skip to main content

term_wm_pty_engine/
pane.rs

1use std::io;
2use std::sync::{Arc, Mutex};
3use std::thread::JoinHandle;
4
5use portable_pty::{Child, ExitStatus, PtySize};
6
7use crate::{PtyResult, PtyStatus};
8
9pub trait Pane {
10    fn resize(&mut self, size: PtySize) -> PtyResult<()>;
11    fn has_exited(&mut self) -> bool;
12    fn alternate_screen(&mut self) -> bool;
13    fn scrollback(&mut self) -> usize;
14    fn set_scrollback(&mut self, rows: usize);
15    fn write_bytes(&mut self, input: &[u8]) -> io::Result<()>;
16    fn max_scrollback(&mut self) -> usize;
17    fn scrollback_len(&self) -> usize;
18    fn take_exit_status(&mut self) -> Option<ExitStatus>;
19    fn exit_status(&self) -> Option<ExitStatus>;
20    fn bytes_received(&self) -> usize;
21    fn last_bytes_text(&self) -> String;
22    fn kill_child(&mut self) -> PtyResult<()>;
23    /// Set a status callback invoked on PTY data or exit.
24    fn set_status_callback(&mut self, _cb: Option<Box<dyn Fn(PtyStatus) + Send + Sync>>) {}
25    fn take_pending_title(&mut self) -> Option<String> {
26        None
27    }
28    /// Extract the child process and reader thread handle so they can be
29    /// moved into the `Reaper` for async teardown.
30    /// Returns `None` by default (for mock panes). The real `Pty` impl
31    /// returns `(child, reader_handle)`.
32    fn take_parts(&mut self) -> Option<(Box<dyn Child + Send + Sync>, JoinHandle<()>)> {
33        None
34    }
35    /// Access the shared parser for zero-copy rendering.
36    fn shared_parser(&mut self) -> Arc<Mutex<term_wm_vt100::Parser>> {
37        Arc::new(Mutex::new(term_wm_vt100::Parser::new(24, 80, 0)))
38    }
39    /// Reset the dirty flag. Returns true if dirty was set.
40    fn take_dirty(&self) -> bool {
41        false
42    }
43    /// Clear the dirty flag and notify the reader thread via Condvar.
44    /// This is the primary mechanism for I/O burst budget backpressure.
45    fn clear_dirty_and_notify(&self) {}
46    /// Sync dirty state and handle DSR/foreground polling.
47    /// Call before locking the parser for cell access.
48    fn sync_screen(&mut self) {}
49
50    /// Unified routing decision: returns true if keyboard/mouse inputs
51    /// should be forwarded to the PTY child rather than intercepted
52    /// by the window manager's native scrollbar.
53    /// Thread-safe: no mutable borrow needed (atomic reads).
54    fn requires_app_routing(&self) -> bool {
55        false
56    }
57
58    /// Returns true when DECCKM (DECSET 1 / Application Cursor Keys) is active.
59    /// When true, unmodified arrow keys must use SS3 (`\eOA`) instead of CSI (`\e[A`).
60    fn is_application_cursor_keys_active(&self) -> bool {
61        false
62    }
63    /// Attempt to reap the child and populate exit_status without firing callbacks.
64    /// Default delegates to `has_exited()`. The real `Pty` impl retries with backoff.
65    fn try_reap(&mut self) -> bool {
66        self.has_exited()
67    }
68}
69
70impl Pane for crate::Pty {
71    fn resize(&mut self, size: PtySize) -> PtyResult<()> {
72        self.resize(size)
73    }
74
75    fn has_exited(&mut self) -> bool {
76        self.has_exited()
77    }
78
79    fn alternate_screen(&mut self) -> bool {
80        self.alternate_screen()
81    }
82
83    fn scrollback(&mut self) -> usize {
84        self.scrollback()
85    }
86
87    fn set_scrollback(&mut self, rows: usize) {
88        self.set_scrollback(rows);
89    }
90
91    fn write_bytes(&mut self, input: &[u8]) -> io::Result<()> {
92        self.write_bytes(input)
93    }
94
95    fn sync_screen(&mut self) {
96        crate::Pty::screen(self);
97    }
98
99    fn max_scrollback(&mut self) -> usize {
100        self.max_scrollback()
101    }
102
103    fn scrollback_len(&self) -> usize {
104        self.scrollback_len()
105    }
106
107    fn take_exit_status(&mut self) -> Option<ExitStatus> {
108        self.take_exit_status()
109    }
110
111    fn exit_status(&self) -> Option<ExitStatus> {
112        self.exit_status()
113    }
114
115    fn bytes_received(&self) -> usize {
116        self.bytes_received()
117    }
118
119    fn last_bytes_text(&self) -> String {
120        self.last_bytes_text()
121    }
122
123    fn kill_child(&mut self) -> PtyResult<()> {
124        self.kill_child()
125    }
126
127    fn take_pending_title(&mut self) -> Option<String> {
128        crate::Pty::take_pending_title(self)
129    }
130
131    fn take_parts(&mut self) -> Option<(Box<dyn Child + Send + Sync>, JoinHandle<()>)> {
132        let parts = self.into_parts();
133        match (parts.child, parts.reader_handle) {
134            (Some(child), Some(handle)) => Some((child, handle)),
135            _ => None,
136        }
137    }
138
139    fn set_status_callback(&mut self, cb: Option<Box<dyn Fn(PtyStatus) + Send + Sync>>) {
140        crate::Pty::set_status_callback(self, cb)
141    }
142
143    fn requires_app_routing(&self) -> bool {
144        self.tracker.requires_app_routing()
145    }
146
147    fn is_application_cursor_keys_active(&self) -> bool {
148        self.tracker.is_application_cursor_keys_active()
149    }
150
151    fn try_reap(&mut self) -> bool {
152        crate::Pty::try_reap(self)
153    }
154
155    fn shared_parser(&mut self) -> Arc<Mutex<term_wm_vt100::Parser>> {
156        self.shared_parser.clone()
157    }
158
159    fn take_dirty(&self) -> bool {
160        self.dirty.swap(false, std::sync::atomic::Ordering::AcqRel)
161    }
162
163    fn clear_dirty_and_notify(&self) {
164        let (lock, cvar) = &*self.dirty_cond;
165        let _guard = lock.lock().unwrap_or_else(|e| e.into_inner());
166        self.dirty
167            .store(false, std::sync::atomic::Ordering::Release);
168        cvar.notify_one();
169    }
170}