1use std::{marker::PhantomData, ptr::NonNull};
4
5use crate::{
6 alloc::Allocator,
7 error::{Error, Result, from_result},
8 ffi,
9 style::{PaletteIndex, RgbColor, Underline},
10};
11
12#[derive(Debug)]
41pub struct Parser<'alloc> {
42 ptr: NonNull<ffi::GhosttySgrParser>,
43 _phan: PhantomData<&'alloc ffi::GhosttyAllocator>,
44}
45
46impl<'alloc> Parser<'alloc> {
47 pub fn new() -> Result<Self> {
49 unsafe { Self::new_inner(std::ptr::null()) }
51 }
52
53 pub fn new_with_alloc<'ctx: 'alloc, Ctx>(alloc: &'alloc Allocator<'ctx, Ctx>) -> Result<Self> {
58 unsafe { Self::new_inner(alloc.to_raw()) }
60 }
61
62 unsafe fn new_inner(alloc: *const ffi::GhosttyAllocator) -> Result<Self> {
63 let mut raw: ffi::GhosttySgrParser_ptr = std::ptr::null_mut();
64 let result = unsafe { ffi::ghostty_sgr_new(alloc, &raw mut raw) };
65 from_result(result)?;
66 let ptr = NonNull::new(raw).ok_or(Error::OutOfMemory)?;
67 Ok(Self {
68 ptr,
69 _phan: PhantomData,
70 })
71 }
72
73 pub fn set_params(&mut self, params: &[u16], separators: Option<&[u8]>) -> Result<()> {
92 let sep_ptr = match separators {
93 Some(seps) => {
94 assert!(
95 seps.len() == params.len(),
96 "separators length must equal params length"
97 );
98 seps.as_ptr().cast::<std::os::raw::c_char>()
99 }
100 None => std::ptr::null(),
101 };
102 let result = unsafe {
103 ffi::ghostty_sgr_set_params(self.ptr.as_ptr(), params.as_ptr(), sep_ptr, params.len())
104 };
105 from_result(result)
106 }
107
108 #[expect(
117 clippy::should_implement_trait,
118 reason = "lending `next` cannot implement trait"
119 )]
120 pub fn next(&mut self) -> Result<Option<Attribute<'_>>> {
121 let mut raw_attr = ffi::GhosttySgrAttribute::default();
122 let has_next = unsafe { ffi::ghostty_sgr_next(self.ptr.as_ptr(), &raw mut raw_attr) };
123 if has_next {
124 Ok(Some(Attribute::from_raw(raw_attr)?))
127 } else {
128 Ok(None)
129 }
130 }
131
132 pub fn reset(&mut self) {
138 unsafe { ffi::ghostty_sgr_reset(self.ptr.as_ptr()) }
139 }
140}
141
142impl Drop for Parser<'_> {
143 fn drop(&mut self) {
144 unsafe { ffi::ghostty_sgr_free(self.ptr.as_ptr()) }
145 }
146}
147
148#[derive(Clone, Copy, Debug, PartialEq, Eq)]
150#[non_exhaustive]
151#[expect(missing_docs, reason = "missing upstream docs")]
152pub enum Attribute<'p> {
153 Unset,
154 Unknown(Unknown<'p>),
155 Bold,
156 ResetBold,
157 Italic,
158 ResetItalic,
159 Faint,
160 Underline(Underline),
161 UnderlineColor(RgbColor),
162 UnderlineColor256(PaletteIndex),
163 ResetUnderlineColor,
164 Overline,
165 ResetOverline,
166 Blink,
167 ResetBlink,
168 Inverse,
169 ResetInverse,
170 Invisible,
171 ResetInvisible,
172 Strikethrough,
173 ResetStrikethrough,
174 DirectColorFg(RgbColor),
175 DirectColorBg(RgbColor),
176 Bg8(PaletteIndex),
177 Fg8(PaletteIndex),
178 ResetFg,
179 ResetBg,
180 BrightBg8(PaletteIndex),
181 BrightFg8(PaletteIndex),
182 Bg256(PaletteIndex),
183 Fg256(PaletteIndex),
184}
185
186impl Attribute<'_> {
187 fn from_raw(value: ffi::GhosttySgrAttribute) -> Result<Self> {
189 Ok(match value.tag {
190 0 => Self::Unset,
191 1 => Self::Unknown(unsafe { value.value.unknown }.into()),
192 2 => Self::Bold,
193 3 => Self::ResetBold,
194 4 => Self::Italic,
195 5 => Self::ResetItalic,
196 6 => Self::Faint,
197 7 => Self::Underline(
198 Underline::try_from(unsafe { value.value.underline })
199 .map_err(|_| Error::InvalidValue)?,
200 ),
201 8 => Self::UnderlineColor(unsafe { value.value.underline_color }.into()),
202 9 => Self::UnderlineColor256(PaletteIndex(unsafe { value.value.underline_color_256 })),
203 10 => Self::ResetUnderlineColor,
204 11 => Self::Overline,
205 12 => Self::ResetOverline,
206 13 => Self::Blink,
207 14 => Self::ResetBlink,
208 15 => Self::Inverse,
209 16 => Self::ResetInverse,
210 17 => Self::Invisible,
211 18 => Self::ResetInvisible,
212 19 => Self::Strikethrough,
213 20 => Self::ResetStrikethrough,
214 21 => Self::DirectColorFg(unsafe { value.value.direct_color_fg }.into()),
215 22 => Self::DirectColorBg(unsafe { value.value.direct_color_bg }.into()),
216 23 => Self::Bg8(PaletteIndex(unsafe { value.value.bg_8 })),
217 24 => Self::Fg8(PaletteIndex(unsafe { value.value.fg_8 })),
218 25 => Self::ResetFg,
219 26 => Self::ResetBg,
220 27 => Self::BrightBg8(PaletteIndex(unsafe { value.value.bright_bg_8 })),
221 28 => Self::BrightFg8(PaletteIndex(unsafe { value.value.bright_fg_8 })),
222 29 => Self::Bg256(PaletteIndex(unsafe { value.value.bg_256 })),
223 30 => Self::Fg256(PaletteIndex(unsafe { value.value.fg_256 })),
224 _ => return Err(Error::InvalidValue),
225 })
226 }
227}
228
229#[derive(Clone, Copy, Debug, PartialEq, Eq)]
231pub struct Unknown<'p> {
232 pub full: &'p [u16],
234 pub partial: &'p [u16],
236}
237
238impl From<ffi::GhosttySgrUnknown> for Unknown<'_> {
239 fn from(value: ffi::GhosttySgrUnknown) -> Self {
240 let full = unsafe { std::slice::from_raw_parts(value.full_ptr, value.full_len) };
246 let partial = unsafe { std::slice::from_raw_parts(value.partial_ptr, value.partial_len) };
247 Self { full, partial }
248 }
249}