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};