use std::{marker::PhantomData, ptr::NonNull};
use crate::{
alloc::Allocator,
error::{Error, Result, from_result},
ffi,
style::{PaletteIndex, RgbColor, Underline},
};
#[derive(Debug)]
pub struct Parser<'alloc> {
ptr: NonNull<ffi::GhosttySgrParser>,
_phan: PhantomData<&'alloc ffi::GhosttyAllocator>,
}
impl<'alloc> Parser<'alloc> {
pub fn new() -> Result<Self> {
unsafe { Self::new_inner(std::ptr::null()) }
}
pub fn new_with_alloc<'ctx: 'alloc, Ctx>(alloc: &'alloc Allocator<'ctx, Ctx>) -> Result<Self> {
unsafe { Self::new_inner(alloc.to_raw()) }
}
unsafe fn new_inner(alloc: *const ffi::GhosttyAllocator) -> Result<Self> {
let mut raw: ffi::GhosttySgrParser_ptr = std::ptr::null_mut();
let result = unsafe { ffi::ghostty_sgr_new(alloc, &raw mut raw) };
from_result(result)?;
let ptr = NonNull::new(raw).ok_or(Error::OutOfMemory)?;
Ok(Self {
ptr,
_phan: PhantomData,
})
}
pub fn set_params(&mut self, params: &[u16], separators: Option<&[u8]>) -> Result<()> {
let sep_ptr = match separators {
Some(seps) => {
assert!(
seps.len() == params.len(),
"separators length must equal params length"
);
seps.as_ptr().cast::<std::os::raw::c_char>()
}
None => std::ptr::null(),
};
let result = unsafe {
ffi::ghostty_sgr_set_params(self.ptr.as_ptr(), params.as_ptr(), sep_ptr, params.len())
};
from_result(result)
}
#[expect(
clippy::should_implement_trait,
reason = "lending `next` cannot implement trait"
)]
pub fn next(&mut self) -> Result<Option<Attribute<'_>>> {
let mut raw_attr = ffi::GhosttySgrAttribute::default();
let has_next = unsafe { ffi::ghostty_sgr_next(self.ptr.as_ptr(), &raw mut raw_attr) };
if has_next {
Ok(Some(Attribute::from_raw(raw_attr)?))
} else {
Ok(None)
}
}
pub fn reset(&mut self) {
unsafe { ffi::ghostty_sgr_reset(self.ptr.as_ptr()) }
}
}
impl Drop for Parser<'_> {
fn drop(&mut self) {
unsafe { ffi::ghostty_sgr_free(self.ptr.as_ptr()) }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[expect(missing_docs, reason = "missing upstream docs")]
pub enum Attribute<'p> {
Unset,
Unknown(Unknown<'p>),
Bold,
ResetBold,
Italic,
ResetItalic,
Faint,
Underline(Underline),
UnderlineColor(RgbColor),
UnderlineColor256(PaletteIndex),
ResetUnderlineColor,
Overline,
ResetOverline,
Blink,
ResetBlink,
Inverse,
ResetInverse,
Invisible,
ResetInvisible,
Strikethrough,
ResetStrikethrough,
DirectColorFg(RgbColor),
DirectColorBg(RgbColor),
Bg8(PaletteIndex),
Fg8(PaletteIndex),
ResetFg,
ResetBg,
BrightBg8(PaletteIndex),
BrightFg8(PaletteIndex),
Bg256(PaletteIndex),
Fg256(PaletteIndex),
}
impl Attribute<'_> {
fn from_raw(value: ffi::GhosttySgrAttribute) -> Result<Self> {
Ok(match value.tag {
0 => Self::Unset,
1 => Self::Unknown(unsafe { value.value.unknown }.into()),
2 => Self::Bold,
3 => Self::ResetBold,
4 => Self::Italic,
5 => Self::ResetItalic,
6 => Self::Faint,
7 => Self::Underline(
Underline::try_from(unsafe { value.value.underline })
.map_err(|_| Error::InvalidValue)?,
),
8 => Self::UnderlineColor(unsafe { value.value.underline_color }.into()),
9 => Self::UnderlineColor256(PaletteIndex(unsafe { value.value.underline_color_256 })),
10 => Self::ResetUnderlineColor,
11 => Self::Overline,
12 => Self::ResetOverline,
13 => Self::Blink,
14 => Self::ResetBlink,
15 => Self::Inverse,
16 => Self::ResetInverse,
17 => Self::Invisible,
18 => Self::ResetInvisible,
19 => Self::Strikethrough,
20 => Self::ResetStrikethrough,
21 => Self::DirectColorFg(unsafe { value.value.direct_color_fg }.into()),
22 => Self::DirectColorBg(unsafe { value.value.direct_color_bg }.into()),
23 => Self::Bg8(PaletteIndex(unsafe { value.value.bg_8 })),
24 => Self::Fg8(PaletteIndex(unsafe { value.value.fg_8 })),
25 => Self::ResetFg,
26 => Self::ResetBg,
27 => Self::BrightBg8(PaletteIndex(unsafe { value.value.bright_bg_8 })),
28 => Self::BrightFg8(PaletteIndex(unsafe { value.value.bright_fg_8 })),
29 => Self::Bg256(PaletteIndex(unsafe { value.value.bg_256 })),
30 => Self::Fg256(PaletteIndex(unsafe { value.value.fg_256 })),
_ => return Err(Error::InvalidValue),
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Unknown<'p> {
pub full: &'p [u16],
pub partial: &'p [u16],
}
impl From<ffi::GhosttySgrUnknown> for Unknown<'_> {
fn from(value: ffi::GhosttySgrUnknown) -> Self {
let full = unsafe { std::slice::from_raw_parts(value.full_ptr, value.full_len) };
let partial = unsafe { std::slice::from_raw_parts(value.partial_ptr, value.partial_len) };
Self { full, partial }
}
}