Skip to main content

libghostty_vt/
fmt.rs

1//! Format terminal content as plain text, VT sequences, or HTML.
2//!
3//! A formatter captures a reference to a terminal and formatting options.
4//! It can be used repeatedly to produce output that reflects the current
5//! terminal state at the time of each format call.
6use std::{marker::PhantomData, ptr::NonNull};
7
8use crate::{
9    alloc::{Allocator, Bytes, Object},
10    error::{Error, Result, from_result},
11    ffi,
12    terminal::Terminal,
13};
14
15/// Formatter that formats terminal content.
16#[derive(Debug)]
17pub struct Formatter<'t, 'alloc: 'cb, 'cb: 't> {
18    inner: Object<'alloc, ffi::GhosttyFormatter>,
19    _terminal: PhantomData<&'t Terminal<'alloc, 'cb>>,
20}
21
22/// Options for creating a terminal formatter.
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub struct FormatterOptions {
25    /// Output format to emit.
26    pub format: Format,
27    /// Whether to trim trailing whitespace on non-blank lines.
28    pub trim: bool,
29    /// Whether to unwrap soft-wrapped lines.
30    pub unwrap: bool,
31}
32
33impl<'t, 'alloc: 'cb, 'cb: 't> Formatter<'t, 'alloc, 'cb> {
34    /// Create a formatter for a terminal's active screen.
35    pub fn new(terminal: &'t Terminal<'alloc, 'cb>, opts: FormatterOptions) -> Result<Self> {
36        // SAFETY: A NULL allocator is always valid
37        unsafe { Self::new_inner(std::ptr::null(), terminal, opts) }
38    }
39
40    /// Create a formatter for a terminal's active screen.
41    ///
42    /// See the [crate-level documentation](crate#memory-management-and-lifetimes)
43    /// regarding custom memory management and lifetimes.
44    pub fn new_with_alloc<'ctx: 'alloc, Ctx>(
45        alloc: &'alloc Allocator<'ctx, Ctx>,
46        terminal: &'t Terminal<'alloc, 'cb>,
47        opts: FormatterOptions,
48    ) -> Result<Self> {
49        // SAFETY: Borrow checking should forbid invalid allocators
50        unsafe { Self::new_inner(alloc.to_raw(), terminal, opts) }
51    }
52
53    unsafe fn new_inner(
54        alloc: *const ffi::GhosttyAllocator,
55        terminal: &'t Terminal<'alloc, 'cb>,
56        opts: FormatterOptions,
57    ) -> Result<Self> {
58        let mut raw: ffi::GhosttyFormatter_ptr = std::ptr::null_mut();
59        let result = unsafe {
60            ffi::ghostty_formatter_terminal_new(
61                alloc,
62                &raw mut raw,
63                terminal.inner.as_raw(),
64                opts.into(),
65            )
66        };
67        from_result(result)?;
68
69        Ok(Self {
70            inner: Object::new(raw)?,
71            _terminal: PhantomData,
72        })
73    }
74
75    /// Run the formatter and return an allocated buffer with the output.
76    ///
77    /// Each call formats the current terminal state. The buffer is allocated
78    /// using the provided allocator (or the default allocator if `None`).
79    pub fn format_alloc<'a, 'ctx: 'a, Ctx>(
80        &mut self,
81        alloc: Option<&'a Allocator<'ctx, Ctx>>,
82    ) -> Result<Bytes<'a>> {
83        let alloc = if let Some(alloc) = alloc {
84            alloc.to_raw()
85        } else {
86            std::ptr::null()
87        };
88
89        let mut bytes = std::ptr::null_mut();
90        let mut len = 0usize;
91        let result = unsafe {
92            ffi::ghostty_formatter_format_alloc(
93                self.inner.as_raw(),
94                alloc,
95                std::ptr::from_mut(&mut bytes),
96                std::ptr::from_mut(&mut len),
97            )
98        };
99        from_result(result)?;
100
101        let ptr = NonNull::new(bytes).ok_or(Error::OutOfMemory)?;
102        Ok(unsafe { Bytes::from_raw_parts(ptr, len, alloc) })
103    }
104
105    /// Run the formatter and produce output into the caller-provided buffer.
106    ///
107    /// Each call formats the current terminal state. If the buffer is too small,
108    /// returns `Err(Error::OutOfSpace { required })` where `required` is the
109    /// required size. The caller can then retry with a larger buffer.
110    pub fn format_buf(&mut self, buf: &mut [u8]) -> Result<usize> {
111        let mut len = 0usize;
112        let result = unsafe {
113            ffi::ghostty_formatter_format_buf(
114                self.inner.as_raw(),
115                std::ptr::from_mut(buf).cast(),
116                buf.len(),
117                std::ptr::from_mut(&mut len),
118            )
119        };
120        from_result(result)?;
121        Ok(len)
122    }
123
124    /// Query the required buffer size for the formatted output.
125    ///
126    /// The result can be used to create a sufficiently large buffer
127    /// for [`Formatter::format_buf`].
128    pub fn format_len(&mut self) -> Result<usize> {
129        let mut len = 0usize;
130        let result = unsafe {
131            ffi::ghostty_formatter_format_buf(
132                self.inner.as_raw(),
133                std::ptr::null_mut(),
134                0,
135                std::ptr::from_mut(&mut len),
136            )
137        };
138        // This should always fail with OutOfSpace.
139        match from_result(result) {
140            Err(Error::OutOfSpace { .. }) => Ok(len),
141            Err(e) => Err(e),
142            Ok(()) => Err(Error::InvalidValue),
143        }
144    }
145}
146
147impl Drop for Formatter<'_, '_, '_> {
148    fn drop(&mut self) {
149        unsafe { ffi::ghostty_formatter_free(self.inner.as_raw()) }
150    }
151}
152
153impl From<FormatterOptions> for ffi::GhosttyFormatterTerminalOptions {
154    fn from(value: FormatterOptions) -> Self {
155        Self {
156            size: std::mem::size_of::<ffi::GhosttyFormatterTerminalOptions>(),
157            emit: value.format.into(),
158            trim: value.trim,
159            extra: ffi::GhosttyFormatterTerminalExtra::default(),
160            unwrap: value.unwrap,
161        }
162    }
163}
164
165/// Output format.
166#[repr(u32)]
167#[derive(Debug, Clone, Copy, PartialEq, Eq, int_enum::IntEnum)]
168pub enum Format {
169    /// Plain text (no escape sequences).
170    Plain = ffi::GhosttyFormatterFormat_GHOSTTY_FORMATTER_FORMAT_PLAIN,
171    /// VT sequences preserving colors, styles, URLs, etc.
172    Vt = ffi::GhosttyFormatterFormat_GHOSTTY_FORMATTER_FORMAT_VT,
173    /// HTML with inline styles.
174    Html = ffi::GhosttyFormatterFormat_GHOSTTY_FORMATTER_FORMAT_HTML,
175}