1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
//! Format terminal content as plain text, VT sequences, or HTML.
//!
//! A formatter captures a reference to a terminal and formatting options.
//! It can be used repeatedly to produce output that reflects the current
//! terminal state at the time of each format call.
use std::{marker::PhantomData, ptr::NonNull};
use crate::{
alloc::{Allocator, Bytes, Object},
error::{Error, Result, from_result},
ffi,
selection::Selection,
terminal::Terminal,
};
/// Formatter that formats terminal content.
#[derive(Debug)]
pub struct Formatter<'t, 'alloc: 'cb, 'cb: 't> {
inner: Object<'alloc, ffi::FormatterImpl>,
_terminal: PhantomData<&'t Terminal<'alloc, 'cb>>,
}
/// Options for [creating a terminal formatter](Formatter::new).
#[derive(Debug)]
pub struct FormatterOptions<'t, 's> {
inner: ffi::FormatterTerminalOptions,
_phan: PhantomData<&'s Selection<'t>>,
}
impl<'t, 's> FormatterOptions<'t, 's> {
/// Create a new set of options for [creating a terminal formatter](Formatter::new).
pub fn new() -> Self {
Self {
inner: ffi::FormatterTerminalOptions {
extra: ffi::FormatterTerminalExtra {
screen: ffi::FormatterScreenExtra {
..ffi::sized!(ffi::FormatterScreenExtra)
},
..ffi::sized!(ffi::FormatterTerminalExtra)
},
..ffi::sized!(ffi::FormatterTerminalOptions)
},
_phan: PhantomData,
}
}
/// Specify the output format to emit.
pub fn with_format(mut self, value: Format) -> Self {
self.inner.emit = value.into();
self
}
/// Specify whether to unwrap soft-wrapped lines.
pub fn with_unwrap(mut self, value: bool) -> Self {
self.inner.unwrap = value;
self
}
/// Specify whether to trim trailing whitespace on non-blank lines.
pub fn with_trim(mut self, value: bool) -> Self {
self.inner.trim = value;
self
}
/// Specify the selection to restrict output to a range.
///
/// If a selection is not given, the formatter defaults to formatting
/// the entire screen.
pub fn with_selection(mut self, value: &'s Selection<'t>) -> Self {
self.inner.selection = &value.inner;
self
}
// --- Extra settings --- //
/// Specify whether to emit the palette using OSC 4 sequences.
pub fn with_palette(mut self, value: bool) -> Self {
self.inner.extra.palette = value;
self
}
/// Specify terminal modes that differ from their defaults using CSI h/l.
pub fn with_modes(mut self, value: bool) -> Self {
self.inner.extra.modes = value;
self
}
/// Specify whether to emit scrolling region state using DECSTBM and DECSLRM sequences.
pub fn with_scrolling_region(mut self, value: bool) -> Self {
self.inner.extra.scrolling_region = value;
self
}
/// Specify tabstop positions by clearing all tabs and setting each one.
pub fn with_tabstops(mut self, value: bool) -> Self {
self.inner.extra.tabstops = value;
self
}
/// Specify the present working directory using OSC 7.
pub fn with_pwd(mut self, value: bool) -> Self {
self.inner.extra.pwd = value;
self
}
/// Specify keyboard modes such as ModifyOtherKeys.
pub fn with_keyboard(mut self, value: bool) -> Self {
self.inner.extra.keyboard = value;
self
}
// --- Screen settings --- //
/// Specify whether to emit cursor position using CUP (CSI H).
pub fn with_cursor(mut self, value: bool) -> Self {
self.inner.extra.screen.cursor = value;
self
}
/// Emit current SGR style state based on the cursor's active style_id.
pub fn with_style(mut self, value: bool) -> Self {
self.inner.extra.screen.style = value;
self
}
/// Emit current hyperlink state using OSC 8 sequences.
pub fn with_hyperlink(mut self, value: bool) -> Self {
self.inner.extra.screen.hyperlink = value;
self
}
/// Emit character protection mode using DECSCA.
pub fn with_protection(mut self, value: bool) -> Self {
self.inner.extra.screen.protection = value;
self
}
/// Emit Kitty keyboard protocol state using CSI > u and CSI = sequences.
pub fn with_kitty_keyboard(mut self, value: bool) -> Self {
self.inner.extra.screen.kitty_keyboard = value;
self
}
/// Emit character set designations and invocations.
pub fn with_charsets(mut self, value: bool) -> Self {
self.inner.extra.screen.charsets = value;
self
}
}
impl<'t, 'alloc: 'cb, 'cb: 't> Formatter<'t, 'alloc, 'cb> {
/// Create a formatter for a terminal's active screen.
pub fn new(
terminal: &'t Terminal<'alloc, 'cb>,
opts: FormatterOptions<'t, '_>,
) -> Result<Self> {
// SAFETY: A NULL allocator is always valid
unsafe { Self::new_inner(std::ptr::null(), terminal, opts) }
}
/// Create a formatter for a terminal's active screen.
///
/// See the [crate-level documentation](crate#memory-management-and-lifetimes)
/// regarding custom memory management and lifetimes.
pub fn new_with_alloc<'ctx: 'alloc>(
alloc: &'alloc Allocator<'ctx>,
terminal: &'t Terminal<'alloc, 'cb>,
opts: FormatterOptions,
) -> Result<Self> {
// SAFETY: Borrow checking should forbid invalid allocators
unsafe { Self::new_inner(alloc.to_raw(), terminal, opts) }
}
unsafe fn new_inner(
alloc: *const ffi::Allocator,
terminal: &'t Terminal<'alloc, 'cb>,
opts: FormatterOptions,
) -> Result<Self> {
let mut raw: ffi::Formatter = std::ptr::null_mut();
let result = unsafe {
ffi::ghostty_formatter_terminal_new(
alloc,
&raw mut raw,
terminal.inner.as_raw(),
opts.inner,
)
};
from_result(result)?;
Ok(Self {
inner: Object::new(raw)?,
_terminal: PhantomData,
})
}
/// Run the formatter and return an allocated buffer with the output.
///
/// Each call formats the current terminal state. The buffer is allocated
/// using the provided allocator (or the default allocator if `None`).
pub fn format_alloc<'a, 'ctx: 'a>(
&mut self,
alloc: Option<&'a Allocator<'ctx>>,
) -> Result<Bytes<'a>> {
let alloc = if let Some(alloc) = alloc {
alloc.to_raw()
} else {
std::ptr::null()
};
let mut bytes = std::ptr::null_mut();
let mut len = 0usize;
let result = unsafe {
ffi::ghostty_formatter_format_alloc(
self.inner.as_raw(),
alloc,
std::ptr::from_mut(&mut bytes),
std::ptr::from_mut(&mut len),
)
};
from_result(result)?;
let ptr = NonNull::new(bytes).ok_or(Error::OutOfMemory)?;
Ok(unsafe { Bytes::from_raw_parts(ptr, len, alloc) })
}
/// Run the formatter and produce output into the caller-provided buffer.
///
/// Each call formats the current terminal state. If the buffer is too small,
/// returns `Err(Error::OutOfSpace { required })` where `required` is the
/// required size. The caller can then retry with a larger buffer.
pub fn format_buf(&mut self, buf: &mut [u8]) -> Result<usize> {
let mut len = 0usize;
let result = unsafe {
ffi::ghostty_formatter_format_buf(
self.inner.as_raw(),
std::ptr::from_mut(buf).cast(),
buf.len(),
std::ptr::from_mut(&mut len),
)
};
from_result(result)?;
Ok(len)
}
/// Query the required buffer size for the formatted output.
///
/// The result can be used to create a sufficiently large buffer
/// for [`Formatter::format_buf`].
pub fn format_len(&mut self) -> Result<usize> {
let mut len = 0usize;
let result = unsafe {
ffi::ghostty_formatter_format_buf(
self.inner.as_raw(),
std::ptr::null_mut(),
0,
std::ptr::from_mut(&mut len),
)
};
// This should always fail with OutOfSpace.
match from_result(result) {
Err(Error::OutOfSpace { .. }) => Ok(len),
Err(e) => Err(e),
Ok(()) => Err(Error::InvalidValue),
}
}
}
impl Drop for Formatter<'_, '_, '_> {
fn drop(&mut self) {
unsafe { ffi::ghostty_formatter_free(self.inner.as_raw()) }
}
}
/// Output format.
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, int_enum::IntEnum)]
pub enum Format {
/// Plain text (no escape sequences).
Plain = ffi::FormatterFormat::PLAIN,
/// VT sequences preserving colors, styles, URLs, etc.
Vt = ffi::FormatterFormat::VT,
/// HTML with inline styles.
Html = ffi::FormatterFormat::HTML,
}