Skip to main content

libghostty_vt/
osc.rs

1//! Handling OSC (Operating System Command) escape sequences.
2
3use std::marker::PhantomData;
4
5use crate::{
6    alloc::{Allocator, Object},
7    error::{Result, from_result},
8    ffi,
9};
10
11/// OSC (Operating System Command) sequence parser and command handling.
12///
13/// The parser operates in a streaming fashion, processing input byte-by-byte
14/// to handle OSC sequences that may arrive in fragments across multiple reads.
15/// This interface makes it easy to integrate into most environments and avoids
16/// over-allocating buffers.
17#[derive(Debug)]
18pub struct Parser<'alloc>(Object<'alloc, ffi::GhosttyOscParser>);
19
20impl<'alloc> Parser<'alloc> {
21    /// Create a new OSC parser.
22    pub fn new() -> Result<Self> {
23        // SAFETY: A NULL allocator is always valid
24        unsafe { Self::new_inner(std::ptr::null()) }
25    }
26
27    /// Create a new OSC parser with a custom allocator.
28    ///
29    /// See the [crate-level documentation](crate#memory-management-and-lifetimes)
30    /// regarding custom memory management and lifetimes.
31    pub fn new_with_alloc<'ctx: 'alloc, Ctx>(alloc: &'alloc Allocator<'ctx, Ctx>) -> Result<Self> {
32        // SAFETY: Borrow checking should forbid invalid allocators
33        unsafe { Self::new_inner(alloc.to_raw()) }
34    }
35
36    unsafe fn new_inner(alloc: *const ffi::GhosttyAllocator) -> Result<Self> {
37        let mut raw: ffi::GhosttyOscParser_ptr = std::ptr::null_mut();
38        let result = unsafe { ffi::ghostty_osc_new(alloc, &raw mut raw) };
39        from_result(result)?;
40        Ok(Self(Object::new(raw)?))
41    }
42
43    /// Reset an OSC parser instance to its initial state.
44    ///
45    /// Resets the parser state, clearing any partially parsed OSC sequences
46    /// and returning the parser to its initial state. This is useful for
47    /// reusing a parser instance or recovering from parse errors.
48    pub fn reset(&mut self) {
49        unsafe { ffi::ghostty_osc_reset(self.0.as_raw()) }
50    }
51
52    /// Parse the next byte in an OSC sequence.
53    ///
54    /// Processes a single byte as part of an OSC sequence. The parser maintains
55    /// internal state to track the progress through the sequence. Call this
56    /// function for each byte in the sequence data.
57    ///
58    /// When finished pumping the parser with bytes, call [`Parser::end`] to
59    /// get the final result.
60    pub fn next_byte(&mut self, byte: u8) {
61        unsafe { ffi::ghostty_osc_next(self.0.as_raw(), byte) }
62    }
63
64    /// Finalize OSC parsing and retrieve the parsed command.
65    ///
66    /// Call this function after feeding all bytes of an OSC sequence to the parser
67    /// using [`Parser::next_byte`] with the exception of the terminating character
68    /// (ESC or ST). This function finalizes the parsing process and returns the
69    /// parsed OSC command. Invalid commands will return a command with type
70    /// [`CommandType::Invalid`].
71    ///
72    /// The terminator parameter specifies the byte that terminated the OSC
73    /// sequence (typically 0x07 for BEL or 0x5C for ST after ESC).
74    /// This information is preserved in the parsed command so that responses
75    /// can use the same terminator format for better compatibility with the
76    /// calling program. For commands that do not require a response, this
77    /// parameter is ignored and the resulting command will not retain the
78    /// terminator information.
79    #[expect(clippy::missing_panics_doc, reason = "internal invariant")]
80    pub fn end<'p>(&'p mut self, terminator: u8) -> Command<'p, 'alloc> {
81        let raw = unsafe { ffi::ghostty_osc_end(self.0.as_raw(), terminator) };
82        Command {
83            inner: Object::new(raw).expect("command must not be null"),
84            _parser: PhantomData,
85        }
86    }
87}
88
89impl Drop for Parser<'_> {
90    fn drop(&mut self) {
91        unsafe { ffi::ghostty_osc_free(self.0.as_raw()) }
92    }
93}
94
95/// A parsed OSC (Operating System Command) command.
96///
97/// The command can be queried for its type and associated data.
98#[derive(Debug)]
99pub struct Command<'p, 'alloc> {
100    inner: Object<'alloc, ffi::GhosttyOscCommand>,
101    _parser: PhantomData<&'p Parser<'alloc>>,
102}
103
104impl Command<'_, '_> {
105    /// Get the type of an OSC command.
106    ///
107    /// This can be used to determine what kind of command was parsed and
108    /// what data might be available from it.
109    #[must_use]
110    pub fn command_type(&self) -> CommandType {
111        CommandType::try_from(unsafe { ffi::ghostty_osc_command_type(self.inner.as_raw()) })
112            .unwrap_or_default()
113    }
114}
115
116/// Type of an OSC command.
117#[expect(missing_docs, reason = "missing upstream docs")]
118#[repr(u32)]
119#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, int_enum::IntEnum)]
120pub enum CommandType {
121    #[default]
122    Invalid = ffi::GhosttyOscCommandType_GHOSTTY_OSC_COMMAND_INVALID,
123}