gpui_terminal/
lib.rs

1//! # gpui-terminal
2//!
3//! A terminal emulator component for embedding in [GPUI](https://gpui.rs) applications.
4//!
5//! This library provides [`TerminalView`], a complete terminal emulator that can be
6//! embedded in any GPUI application. It uses [alacritty_terminal](https://docs.rs/alacritty_terminal)
7//! for VTE parsing and terminal state management, providing a production-ready terminal
8//! with full ANSI escape sequence support.
9//!
10//! ## Features
11//!
12//! - **Full Terminal Emulation**: VTE-compliant terminal powered by alacritty_terminal
13//! - **Color Support**: 16 ANSI colors, 256-color mode, and 24-bit true color (RGB)
14//! - **GPUI Integration**: Implements GPUI's `Render` trait for seamless embedding
15//! - **Flexible I/O**: Accepts arbitrary [`std::io::Read`]/[`std::io::Write`] streams
16//!   (works with any PTY implementation)
17//! - **Customizable Colors**: Builder pattern for configuring the full color palette
18//! - **Event Callbacks**: Hooks for resize, exit, bell, title changes, and clipboard operations
19//! - **Keyboard Input**: Full keyboard support including control sequences, function keys,
20//!   and application cursor mode
21//! - **Push-based I/O**: Efficient async architecture that only wakes when data arrives
22//!
23//! ## Quick Start
24//!
25//! The terminal view accepts I/O streams rather than spawning processes directly.
26//! This gives you full control over the PTY lifecycle. Here's a complete example
27//! using [portable-pty](https://docs.rs/portable-pty):
28//!
29//! ```ignore
30//! use gpui::{Application, Edges, px};
31//! use gpui_terminal::{ColorPalette, TerminalConfig, TerminalView};
32//! use portable_pty::{native_pty_system, CommandBuilder, PtySize};
33//! use std::sync::Arc;
34//!
35//! fn main() {
36//!     let app = Application::new();
37//!     app.run(|cx| {
38//!         // 1. Create PTY with initial dimensions
39//!         let pty_system = native_pty_system();
40//!         let pair = pty_system.openpty(PtySize {
41//!             rows: 24,
42//!             cols: 80,
43//!             pixel_width: 0,
44//!             pixel_height: 0,
45//!         }).expect("Failed to open PTY");
46//!
47//!         // 2. Spawn shell in the PTY
48//!         let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".into());
49//!         let mut cmd = CommandBuilder::new(&shell);
50//!         cmd.env("TERM", "xterm-256color");
51//!         cmd.env("COLORTERM", "truecolor");
52//!         let _child = pair.slave.spawn_command(cmd).expect("Failed to spawn shell");
53//!
54//!         // 3. Get I/O handles from the PTY master
55//!         let writer = pair.master.take_writer().expect("Failed to get writer");
56//!         let reader = pair.master.try_clone_reader().expect("Failed to get reader");
57//!         let pty_master = Arc::new(parking_lot::Mutex::new(pair.master));
58//!
59//!         // 4. Create terminal configuration
60//!         let config = TerminalConfig {
61//!             font_family: "monospace".into(),
62//!             font_size: px(14.0),
63//!             cols: 80,
64//!             rows: 24,
65//!             scrollback: 10000,
66//!             line_height_multiplier: 1.2,
67//!             padding: Edges::all(px(8.0)),
68//!             colors: ColorPalette::default(),
69//!         };
70//!
71//!         // 5. Create resize callback to sync PTY dimensions
72//!         let pty_for_resize = pty_master.clone();
73//!         let resize_callback = move |cols: usize, rows: usize| {
74//!             let _ = pty_for_resize.lock().resize(PtySize {
75//!                 cols: cols as u16,
76//!                 rows: rows as u16,
77//!                 pixel_width: 0,
78//!                 pixel_height: 0,
79//!             });
80//!         };
81//!
82//!         // 6. Open window with terminal
83//!         cx.spawn(async move |cx| {
84//!             cx.open_window(Default::default(), |window, cx| {
85//!                 let terminal = cx.new(|cx| {
86//!                     TerminalView::new(writer, reader, config, cx)
87//!                         .with_resize_callback(resize_callback)
88//!                         .with_exit_callback(|_, cx| cx.quit())
89//!                 });
90//!                 terminal.read(cx).focus_handle().focus(window);
91//!                 terminal
92//!             })
93//!         }).detach();
94//!     });
95//! }
96//! ```
97//!
98//! ## Architecture
99//!
100//! The library is organized around a few key components:
101//!
102//! ```text
103//! ┌─────────────────────────────────────────────────────────────────┐
104//! │                      Your GPUI Application                      │
105//! └─────────────────────────────────────────────────────────────────┘
106//!                                  │
107//!                                  ▼
108//! ┌─────────────────────────────────────────────────────────────────┐
109//! │  TerminalView (GPUI Entity, implements Render)                  │
110//! │  ├─ Keyboard/mouse event handling                               │
111//! │  ├─ Callback dispatch (resize, exit, bell, title, clipboard)    │
112//! │  └─ Canvas-based rendering                                      │
113//! └─────────────────────────────────────────────────────────────────┘
114//!         │                    │                      │
115//!         ▼                    ▼                      ▼
116//! ┌───────────────┐   ┌──────────────────┐   ┌────────────────────────┐
117//! │ TerminalState │   │ TerminalRenderer │   │ I/O Pipeline           │
118//! │ ├─ Term<>     │   │ ├─ Font metrics  │   │ ├─ Background thread   │
119//! │ │  (alacritty)│   │ ├─ ColorPalette  │   │ │   reads from PTY     │
120//! │ └─ VTE Parser │   │ └─ Text layout   │   │ ├─ Flume channel       │
121//! └───────────────┘   └──────────────────┘   │ └─ Async task notifies │
122//!                                            └────────────────────────┘
123//! ```
124//!
125//! ### Data Flow
126//!
127//! 1. **Output from PTY**: A background thread reads bytes from the PTY stdout,
128//!    sends them through a [flume](https://docs.rs/flume) channel to an async task,
129//!    which processes them through the VTE parser and notifies GPUI to repaint.
130//!
131//! 2. **Input to PTY**: Keyboard events are converted to terminal escape sequences
132//!    by the [`input`] module and written directly to the PTY stdin.
133//!
134//! 3. **Rendering**: On each paint, [`TerminalRenderer`] reads the terminal grid,
135//!    batches cells with identical styling, and draws backgrounds, text, and cursor.
136//!
137//! ## Configuration
138//!
139//! ### Terminal Dimensions and Font
140//!
141//! Configure the terminal through [`TerminalConfig`]:
142//!
143//! ```ignore
144//! use gpui::{Edges, px};
145//! use gpui_terminal::{ColorPalette, TerminalConfig};
146//!
147//! let config = TerminalConfig {
148//!     // Grid dimensions (characters)
149//!     cols: 120,
150//!     rows: 40,
151//!
152//!     // Font settings
153//!     font_family: "JetBrains Mono".into(),
154//!     font_size: px(13.0),
155//!
156//!     // Line height multiplier for tall glyphs (nerd fonts)
157//!     line_height_multiplier: 1.2,
158//!
159//!     // Scrollback history (lines)
160//!     scrollback: 10000,
161//!
162//!     // Padding around terminal content
163//!     padding: Edges {
164//!         top: px(4.0),
165//!         right: px(8.0),
166//!         bottom: px(4.0),
167//!         left: px(8.0),
168//!     },
169//!
170//!     // Color scheme
171//!     colors: ColorPalette::default(),
172//! };
173//! ```
174//!
175//! ### Custom Color Palette
176//!
177//! Use [`ColorPalette::builder()`] to customize the terminal colors:
178//!
179//! ```
180//! use gpui_terminal::ColorPalette;
181//!
182//! // Create a custom color scheme (Gruvbox-like)
183//! let colors = ColorPalette::builder()
184//!     // Background and foreground
185//!     .background(0x28, 0x28, 0x28)
186//!     .foreground(0xeb, 0xdb, 0xb2)
187//!     .cursor(0xeb, 0xdb, 0xb2)
188//!
189//!     // Normal colors (0-7)
190//!     .black(0x28, 0x28, 0x28)
191//!     .red(0xcc, 0x24, 0x1d)
192//!     .green(0x98, 0x97, 0x1a)
193//!     .yellow(0xd7, 0x99, 0x21)
194//!     .blue(0x45, 0x85, 0x88)
195//!     .magenta(0xb1, 0x62, 0x86)
196//!     .cyan(0x68, 0x9d, 0x6a)
197//!     .white(0xa8, 0x99, 0x84)
198//!
199//!     // Bright colors (8-15)
200//!     .bright_black(0x92, 0x83, 0x74)
201//!     .bright_red(0xfb, 0x49, 0x34)
202//!     .bright_green(0xb8, 0xbb, 0x26)
203//!     .bright_yellow(0xfa, 0xbd, 0x2f)
204//!     .bright_blue(0x83, 0xa5, 0x98)
205//!     .bright_magenta(0xd3, 0x86, 0x9b)
206//!     .bright_cyan(0x8e, 0xc0, 0x7c)
207//!     .bright_white(0xeb, 0xdb, 0xb2)
208//!     .build();
209//! ```
210//!
211//! ## Event Handling
212//!
213//! The terminal provides several callback hooks for integration:
214//!
215//! ### Resize Callback
216//!
217//! **Essential** for proper terminal operation. Called when the terminal grid
218//! dimensions change (e.g., when the window is resized):
219//!
220//! ```ignore
221//! terminal.with_resize_callback(move |cols, rows| {
222//!     // Notify your PTY about the new size
223//!     pty.lock().resize(PtySize {
224//!         cols: cols as u16,
225//!         rows: rows as u16,
226//!         pixel_width: 0,
227//!         pixel_height: 0,
228//!     }).ok();
229//! })
230//! ```
231//!
232//! ### Exit Callback
233//!
234//! Called when the terminal process exits:
235//!
236//! ```ignore
237//! terminal.with_exit_callback(|window, cx| {
238//!     // Close the window or show an exit message
239//!     cx.quit();
240//! })
241//! ```
242//!
243//! ### Key Handler
244//!
245//! Intercept key events before the terminal processes them. Return `true` to
246//! consume the event:
247//!
248//! ```ignore
249//! terminal.with_key_handler(|event| {
250//!     // Handle Ctrl++ to increase font size
251//!     if event.keystroke.modifiers.control && event.keystroke.key == "+" {
252//!         // Your font size logic here
253//!         return true; // Consume the event
254//!     }
255//!     false // Let terminal handle it
256//! })
257//! ```
258//!
259//! ### Other Callbacks
260//!
261//! - **Bell**: `with_bell_callback` - Terminal bell (BEL character)
262//! - **Title**: `with_title_callback` - Window title changes (OSC 0/2)
263//! - **Clipboard**: `with_clipboard_store_callback` - Clipboard write requests (OSC 52)
264//!
265//! ## Dynamic Configuration
266//!
267//! Update terminal settings at runtime with [`TerminalView::update_config`]:
268//!
269//! ```ignore
270//! terminal.update(cx, |terminal, cx| {
271//!     let mut config = terminal.config().clone();
272//!     config.font_size += px(2.0);  // Increase font size
273//!     terminal.update_config(config, cx);
274//! });
275//! ```
276//!
277//! ## Feature Matrix
278//!
279//! | Feature | Status |
280//! |---------|--------|
281//! | Text rendering | ✅ Full support |
282//! | Bold/italic/underline | ✅ Full support |
283//! | 16 ANSI colors | ✅ Full support |
284//! | 256-color mode | ✅ Full support |
285//! | True color (24-bit) | ✅ Full support |
286//! | Keyboard input | ✅ Full support |
287//! | Application cursor mode | ✅ Full support |
288//! | Function keys (F1-F12) | ✅ Full support |
289//! | Mouse click reporting | 🔄 Partial (framework ready) |
290//! | Mouse selection | 🔄 Planned |
291//! | Scrollback | 🔄 Planned |
292//! | Clipboard (OSC 52) | ✅ Callback support |
293//! | Title changes (OSC 0/2) | ✅ Callback support |
294//! | Bell (BEL) | ✅ Callback support |
295//!
296//! ## Platform Support
297//!
298//! - **Linux**: X11 and Wayland via GPUI's platform support
299//! - **Clipboard**: Uses [arboard](https://docs.rs/arboard) with Wayland data-control
300//!
301//! ## Modules
302//!
303//! | Module | Description |
304//! |--------|-------------|
305//! | [`view`] | Main terminal view component ([`TerminalView`], [`TerminalConfig`]) |
306//! | [`terminal`] | Terminal state wrapper ([`TerminalState`]) |
307//! | [`colors`] | Color palette ([`ColorPalette`], [`ColorPaletteBuilder`]) |
308//! | [`render`] | Text and background rendering ([`TerminalRenderer`]) |
309//! | [`event`] | Event bridge ([`TerminalEvent`], [`GpuiEventProxy`]) |
310//! | [`input`] | Keyboard to escape sequence conversion |
311//! | [`mouse`] | Mouse event handling and reporting |
312//! | [`clipboard`] | System clipboard integration ([`Clipboard`]) |
313//!
314//! ## Troubleshooting
315//!
316//! ### Terminal shows garbled text
317//!
318//! Ensure you set the `TERM` environment variable when spawning the shell:
319//! ```ignore
320//! cmd.env("TERM", "xterm-256color");
321//! cmd.env("COLORTERM", "truecolor");
322//! ```
323//!
324//! ### Colors don't display correctly
325//!
326//! Some applications check `COLORTERM=truecolor` for 24-bit color support.
327//! Set both `TERM` and `COLORTERM` for best compatibility.
328//!
329//! ### Arrow keys don't work in vim/less
330//!
331//! The terminal automatically handles application cursor mode. Ensure your
332//! resize callback is correctly notifying the PTY, as some applications
333//! query terminal dimensions on startup.
334//!
335//! ### Font doesn't render correctly
336//!
337//! Use a monospace font. Nerd fonts work well with `line_height_multiplier: 1.2`
338//! to accommodate tall glyphs.
339
340pub mod clipboard;
341pub mod colors;
342pub mod event;
343pub mod input;
344pub mod mouse;
345pub mod render;
346pub mod terminal;
347pub mod view;
348
349// Re-export main types for convenience
350pub use clipboard::Clipboard;
351pub use colors::{ColorPalette, ColorPaletteBuilder};
352pub use event::{GpuiEventProxy, TerminalEvent};
353pub use render::TerminalRenderer;
354pub use terminal::TerminalState;
355pub use view::{
356    BellCallback, ClipboardStoreCallback, ExitCallback, KeyHandler, ResizeCallback, TerminalConfig,
357    TerminalView, TitleCallback,
358};