Skip to main content

gpui_cn/terminal/
stream.rs

1//! A byte source an application drives itself: an ssh session, a
2//! websocket to a remote shell, a pty the application owns, or a recorded
3//! stream.
4//!
5//! [`StreamSource::new`] returns the source and its [`StreamPeer`]. The
6//! source goes to [`Engine`](super::Engine) like any [`ByteSource`]; the
7//! application keeps the peer, writes the program's output to it, reads
8//! the terminal's input and grid sizes from it, and reports the exit.
9//! It works on every target, including those with no local pty.
10//!
11//! ```no_run
12//! use gpui_cn::terminal::{
13//!     Engine, EngineOptions, ExitStatus, StreamEvent, StreamSource, TerminalConfig,
14//!     TerminalState,
15//! };
16//! use gpui_kit::{AppContext as _, Context, Window};
17//!
18//! fn remote(window: &mut Window, cx: &mut Context<TerminalState>) -> TerminalState {
19//!     let (source, peer) = StreamSource::new();
20//!     cx.spawn(async move |_, _| {
21//!         peer.output(b"connected\r\n$ ");
22//!         while let Ok(event) = peer.events().recv().await {
23//!             match event {
24//!                 // Send the keys to the remote program.
25//!                 StreamEvent::Input(bytes) => peer.output(&bytes),
26//!                 // Tell the remote side the new size.
27//!                 StreamEvent::Resize(_) => {}
28//!                 StreamEvent::Closed => break,
29//!                 _ => {}
30//!             }
31//!         }
32//!         peer.exit(ExitStatus::new(Some(0), None::<String>));
33//!     })
34//!     .detach();
35//!     let engine = Engine::new(source, EngineOptions::default());
36//!     TerminalState::new(engine, TerminalConfig::default(), window, cx)
37//! }
38//! ```
39
40use std::io;
41use std::sync::{Arc, Mutex};
42
43use gpui_kit::base::async_util;
44
45use crate::terminal::engine::{ByteHandle, ByteSink, ByteSource, ExitStatus};
46use crate::terminal::frame::Viewport;
47
48/// What the terminal sends to the application's side of a
49/// [`StreamSource`].
50#[derive(Clone, Debug, PartialEq, Eq)]
51#[non_exhaustive]
52pub enum StreamEvent {
53    /// Bytes for the program: keys, pastes and replies to its queries,
54    /// already encoded for the terminal modes the program set.
55    Input(Vec<u8>),
56    /// The grid changed. The first event is the grid the terminal
57    /// started with.
58    Resize(Viewport),
59    /// The terminal closed. Stop the program.
60    Closed,
61}
62
63/// A [`ByteSource`] fed by its [`StreamPeer`].
64#[derive(Debug)]
65pub struct StreamSource {
66    shared: Arc<Shared>,
67}
68
69/// The application's side of a [`StreamSource`]. Cheap to clone; every
70/// clone feeds the same terminal.
71#[derive(Clone, Debug)]
72pub struct StreamPeer {
73    shared: Arc<Shared>,
74    events: async_util::Receiver<StreamEvent>,
75}
76
77#[derive(Debug)]
78struct Shared {
79    state: Mutex<State>,
80    events: async_util::Sender<StreamEvent>,
81}
82
83/// Output written before the engine started waits here.
84#[derive(Debug, Default)]
85struct State {
86    sink: Option<ByteSink>,
87    pending: Vec<u8>,
88    exit: Option<ExitStatus>,
89}
90
91impl Shared {
92    fn lock(&self) -> std::sync::MutexGuard<'_, State> {
93        self.state
94            .lock()
95            .unwrap_or_else(std::sync::PoisonError::into_inner)
96    }
97}
98
99impl StreamSource {
100    /// A source and the peer that feeds it.
101    #[must_use]
102    pub fn new() -> (Self, StreamPeer) {
103        let (events, receiver) = async_util::unbounded();
104        let shared = Arc::new(Shared {
105            state: Mutex::new(State::default()),
106            events,
107        });
108        (
109            Self {
110                shared: shared.clone(),
111            },
112            StreamPeer {
113                shared,
114                events: receiver,
115            },
116        )
117    }
118}
119
120impl StreamPeer {
121    /// Delivers the program's output to the terminal. Output written
122    /// before the terminal starts is kept until it does.
123    pub fn output(&self, bytes: &[u8]) {
124        let mut state = self.shared.lock();
125        match &state.sink {
126            Some(sink) => {
127                sink.write(bytes);
128            }
129            None => state.pending.extend_from_slice(bytes),
130        }
131    }
132
133    /// Reports that the program ended. The terminal shows the status and
134    /// emits [`TerminalEvent::Exited`](super::TerminalEvent::Exited).
135    pub fn exit(&self, status: ExitStatus) {
136        let mut state = self.shared.lock();
137        state.exit = Some(status);
138        if let Some(sink) = &state.sink {
139            sink.finished();
140        }
141    }
142
143    /// The terminal's input, grid sizes and close, in order. Await
144    /// `recv()` on it, or poll `try_recv()`.
145    pub fn events(&self) -> &async_util::Receiver<StreamEvent> {
146        &self.events
147    }
148}
149
150/// The engine's side of a started [`StreamSource`].
151struct Handle {
152    shared: Arc<Shared>,
153}
154
155impl ByteSource for StreamSource {
156    fn start(
157        self: Box<Self>,
158        sink: ByteSink,
159        viewport: Viewport,
160    ) -> io::Result<Box<dyn ByteHandle>> {
161        {
162            let mut state = self.shared.lock();
163            let pending = std::mem::take(&mut state.pending);
164            if !pending.is_empty() {
165                sink.write(&pending);
166            }
167            if state.exit.is_some() {
168                sink.finished();
169            }
170            state.sink = Some(sink);
171        }
172        let _ = self.shared.events.try_send(StreamEvent::Resize(viewport));
173        Ok(Box::new(Handle {
174            shared: self.shared,
175        }))
176    }
177}
178
179impl ByteHandle for Handle {
180    fn write(&self, bytes: &[u8]) -> io::Result<usize> {
181        let _ = self
182            .shared
183            .events
184            .try_send(StreamEvent::Input(bytes.to_vec()));
185        Ok(bytes.len())
186    }
187
188    fn resize(&self, viewport: Viewport) -> io::Result<()> {
189        let _ = self.shared.events.try_send(StreamEvent::Resize(viewport));
190        Ok(())
191    }
192
193    fn try_wait(&self) -> io::Result<Option<ExitStatus>> {
194        Ok(self.shared.lock().exit.clone())
195    }
196
197    fn close(&self) {
198        let _ = self.shared.events.try_send(StreamEvent::Closed);
199        self.shared.lock().sink = None;
200    }
201}