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}