diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-01 23:05:23 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-01 23:05:51 +0300 |
| commit | 5bd21b7ed468b5ff4d1a5b2ed7e211f401eb138a (patch) | |
| tree | 672fa4625cca0ecb3e9376fc7f8e33d7b0e9deef /src/ghostty | |
| parent | 32abb3776668ee768200019c170956f67fe33a86 (diff) | |
chore: update libghostty vendor
refs #276
Diffstat (limited to 'src/ghostty')
| -rw-r--r-- | src/ghostty/bindings.rs | 37 | ||||
| -rw-r--r-- | src/ghostty/mod.rs | 173 |
2 files changed, 154 insertions, 56 deletions
diff --git a/src/ghostty/bindings.rs b/src/ghostty/bindings.rs index 4998e2b6..4676c327 100644 --- a/src/ghostty/bindings.rs +++ b/src/ghostty/bindings.rs @@ -104,6 +104,34 @@ const _: () = { ["Offset of field: GhosttyString::ptr"][::std::mem::offset_of!(GhosttyString, ptr) - 0usize]; ["Offset of field: GhosttyString::len"][::std::mem::offset_of!(GhosttyString, len) - 8usize]; }; +#[doc = " A caller-provided byte buffer.\n\n APIs that write to this type use `len` for the number of bytes written on\n GHOSTTY_SUCCESS and the required byte capacity on GHOSTTY_OUT_OF_SPACE."] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct GhosttyBuffer { + #[doc = " Destination buffer for bytes. May be NULL when cap is 0 to query required size."] + pub ptr: *mut u8, + #[doc = " Capacity of ptr in bytes."] + pub cap: usize, + #[doc = " Bytes written on success, or required byte capacity on GHOSTTY_OUT_OF_SPACE."] + pub len: usize, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of GhosttyBuffer"][::std::mem::size_of::<GhosttyBuffer>() - 24usize]; + ["Alignment of GhosttyBuffer"][::std::mem::align_of::<GhosttyBuffer>() - 8usize]; + ["Offset of field: GhosttyBuffer::ptr"][::std::mem::offset_of!(GhosttyBuffer, ptr) - 0usize]; + ["Offset of field: GhosttyBuffer::cap"][::std::mem::offset_of!(GhosttyBuffer, cap) - 8usize]; + ["Offset of field: GhosttyBuffer::len"][::std::mem::offset_of!(GhosttyBuffer, len) - 16usize]; +}; +impl Default for GhosttyBuffer { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::<Self>::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} impl Default for GhosttyString { fn default() -> Self { let mut s = ::std::mem::MaybeUninit::<Self>::uninit(); @@ -1605,6 +1633,15 @@ pub const GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_BG_ #[doc = " The resolved foreground color of the cell (GhosttyColorRgb).\n Resolves palette indices through the palette. Bold color handling\n is not applied; the caller should handle bold styling separately.\n Returns GHOSTTY_INVALID_VALUE if the cell has no explicit foreground\n color, in which case the caller should use whatever default foreground\n color it wants (e.g. the terminal foreground)."] pub const GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_FG_COLOR: GhosttyRenderStateRowCellsData = 6; +#[doc = " Whether the cell is contained within the current selection (bool)."] +pub const GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_SELECTED: + GhosttyRenderStateRowCellsData = 7; +#[doc = " Whether the cell has any explicit styling (bool)."] +pub const GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_HAS_STYLING: + GhosttyRenderStateRowCellsData = 8; +#[doc = " Encode the current cell's full grapheme cluster as UTF-8 into a GhosttyBuffer."] +pub const GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_UTF8: + GhosttyRenderStateRowCellsData = 9; #[doc = " Queryable data kinds for ghostty_render_state_row_cells_get().\n\n @ingroup render"] pub type GhosttyRenderStateRowCellsData = ::std::os::raw::c_uint; unsafe extern "C" { diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs index e2b592d2..57114de4 100644 --- a/src/ghostty/mod.rs +++ b/src/ghostty/mod.rs @@ -410,8 +410,15 @@ unsafe extern "C" fn write_pty_trampoline( if userdata.is_null() { return; } + if data.is_null() && len != 0 { + return; + } let state = unsafe { &mut *(userdata.cast::<WritePtyCallbackState>()) }; - let bytes = unsafe { slice::from_raw_parts(data, len) }; + let bytes = if len == 0 { + &[] + } else { + unsafe { slice::from_raw_parts(data, len) } + }; (state.callback)(bytes); } @@ -565,6 +572,8 @@ impl Terminal { cell_width_px: u32, cell_height_px: u32, ) -> Result<(), Error> { + let cell_width_px = cell_width_px.max(1); + let cell_height_px = cell_height_px.max(1); // SAFETY: self.raw is valid and sizes are plain values. unsafe { ffi::ghostty_terminal_resize(self.raw, cols, rows, cell_width_px, cell_height_px) @@ -726,6 +735,19 @@ impl Terminal { grid_ref_graphemes(&grid_ref) } + fn viewport_graphemes_and_style(&self, x: u16, y: u32) -> Result<(Vec<u32>, CellStyle), Error> { + let grid_ref = self.grid_ref(ghostty_viewport_point(x, y))?; + let graphemes = grid_ref_graphemes(&grid_ref)?; + let mut style = ffi::GhosttyStyle { + size: mem::size_of::<ffi::GhosttyStyle>(), + ..Default::default() + }; + unsafe { + ffi::ghostty_grid_ref_style(&grid_ref, &mut style).into_result()?; + } + Ok((graphemes, style.into())) + } + pub fn viewport_hyperlink_uri(&self, x: u16, y: u32) -> Result<Option<String>, Error> { let grid_ref = self.grid_ref(ghostty_viewport_point(x, y))?; grid_ref_hyperlink_uri(&grid_ref) @@ -1182,29 +1204,20 @@ impl Terminal { return Ok(Vec::new()); } - let cols = self.cols()?.max(1) as u32; - let rows = self.rows()?.max(1) as u32; - let cell_width = (self.width_px()? / cols).max(1); - let cell_height = (self.height_px()? / rows).max(1); - let mut render_state = RenderState::new()?; - render_state.update(self)?; - let mut row_iterator = RowIterator::new()?; - let mut row_cells = RowCells::new()?; - let mut row_iter = render_state.populate_row_iterator(&mut row_iterator)?; - let mut graphemes = Vec::new(); + let viewport_cols = self.cols()?.max(1); + let viewport_rows = self.rows()?.max(1); + let cell_width = (self.width_px()? / u32::from(viewport_cols)).max(1); + let cell_height = (self.height_px()? / u32::from(viewport_rows)).max(1); let mut runs = Vec::new(); - let mut y = 0u16; - while row_iter.next() { - let mut cells = row_iter.populate_cells(&mut row_cells)?; + for y in 0..viewport_rows { let mut current: Option<KittyVirtualRun> = None; - let mut x = 0u16; - while cells.next() { - let cell = kitty_virtual_cell(x, y, &cells, &mut graphemes)?; + for x in 0..viewport_cols { + let (graphemes, style) = self.viewport_graphemes_and_style(x, u32::from(y))?; + let cell = kitty_virtual_cell(x, y, &graphemes, style); match cell { Some(cell) => { if let Some(run) = current.as_mut() { if run.append(cell) { - x = x.saturating_add(1); continue; } runs.push(*run); @@ -1217,12 +1230,10 @@ impl Terminal { } } } - x = x.saturating_add(1); } if let Some(run) = current { runs.push(run); } - y = y.saturating_add(1); } let mut placements = Vec::new(); @@ -1455,14 +1466,12 @@ fn find_virtual_placement_spec( fn kitty_virtual_cell( x: u16, y: u16, - cells: &RowCellIter<'_>, - graphemes: &mut Vec<u32>, -) -> Result<Option<KittyVirtualCell>, Error> { - cells.graphemes_into(graphemes)?; + graphemes: &[u32], + style: CellStyle, +) -> Option<KittyVirtualCell> { if graphemes.first().copied() != Some(KITTY_UNICODE_PLACEHOLDER) { - return Ok(None); + return None; } - let style = cells.style()?; let image_id_low = style .fg_color .map(kitty_placeholder_color_to_id) @@ -1482,7 +1491,7 @@ fn kitty_virtual_cell( .and_then(|codepoint| kitty_placeholder_diacritic_index(*codepoint)) .filter(|high| *high <= u32::from(u8::MAX)); - Ok(Some(KittyVirtualCell { + Some(KittyVirtualCell { x, y, image_id_low, @@ -1490,7 +1499,7 @@ fn kitty_virtual_cell( placement_id, row, col, - })) + }) } fn kitty_placeholder_color_to_id(color: CellColor) -> u32 { @@ -2365,42 +2374,95 @@ impl<'a> RowCellIter<'a> { } } - pub fn grapheme_len(&self) -> Result<u32, Error> { - let mut len = 0u32; - // SAFETY: len output matches requested cell data type. + fn raw_cell_text_into(&self, text: &mut String) -> Result<(), Error> { + let raw = self.raw_cell()?; + let mut has_text = false; unsafe { - ffi::ghostty_render_state_row_cells_get( - self.cells.raw, - ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_LEN, - (&mut len as *mut u32).cast(), + ffi::ghostty_cell_get( + raw, + ffi::GhosttyCellData_GHOSTTY_CELL_DATA_HAS_TEXT, + (&mut has_text as *mut bool).cast(), ) .into_result()?; } - Ok(len) + if !has_text { + return Ok(()); + } + + let mut codepoint = 0u32; + unsafe { + ffi::ghostty_cell_get( + raw, + ffi::GhosttyCellData_GHOSTTY_CELL_DATA_CODEPOINT, + (&mut codepoint as *mut u32).cast(), + ) + .into_result()?; + } + if let Some(ch) = char::from_u32(codepoint) { + text.push(ch); + } + Ok(()) } - pub fn graphemes(&self) -> Result<Vec<u32>, Error> { - let mut out = Vec::new(); - self.graphemes_into(&mut out)?; - Ok(out) + pub fn grapheme_text(&self) -> Result<String, Error> { + let mut bytes = Vec::new(); + let mut text = String::new(); + self.grapheme_text_into(&mut bytes, &mut text)?; + Ok(text) } - pub fn graphemes_into(&self, out: &mut Vec<u32>) -> Result<(), Error> { - let len = self.grapheme_len()? as usize; - out.clear(); - out.resize(len, 0); - if len == 0 { - return Ok(()); + pub fn grapheme_text_into(&self, bytes: &mut Vec<u8>, text: &mut String) -> Result<(), Error> { + text.clear(); + bytes.clear(); + + let mut buffer = ffi::GhosttyBuffer { + ptr: ptr::null_mut(), + cap: 0, + len: 0, + }; + let result = unsafe { + ffi::ghostty_render_state_row_cells_get( + self.cells.raw, + ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_UTF8, + (&mut buffer as *mut ffi::GhosttyBuffer).cast(), + ) + }; + match result { + ffi::GhosttyResult_GHOSTTY_SUCCESS if buffer.len == 0 => { + return self.raw_cell_text_into(text); + } + ffi::GhosttyResult_GHOSTTY_SUCCESS => { + return Err(Error(ffi::GhosttyResult_GHOSTTY_INVALID_VALUE)); + } + ffi::GhosttyResult_GHOSTTY_OUT_OF_SPACE => {} + other => return Err(Error(other)), + } + + if buffer.len == 0 { + return self.raw_cell_text_into(text); } - // SAFETY: out buffer is allocated for the grapheme count returned by the API. + bytes.resize(buffer.len, 0); + let mut buffer = ffi::GhosttyBuffer { + ptr: bytes.as_mut_ptr(), + cap: bytes.len(), + len: 0, + }; unsafe { ffi::ghostty_render_state_row_cells_get( self.cells.raw, - ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_BUF, - out.as_mut_ptr().cast::<c_void>(), + ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_UTF8, + (&mut buffer as *mut ffi::GhosttyBuffer).cast(), ) .into_result()?; } + if buffer.len > bytes.len() { + return Err(Error(ffi::GhosttyResult_GHOSTTY_OUT_OF_SPACE)); + } + bytes.truncate(buffer.len); + match std::str::from_utf8(bytes) { + Ok(value) => text.push_str(value), + Err(_) => text.push_str(&String::from_utf8_lossy(bytes)), + } Ok(()) } } @@ -2513,7 +2575,8 @@ mod tests { let mut terminal = Terminal::new(10, 5, 0).unwrap(); terminal.enable_kitty_graphics().unwrap(); terminal.resize(10, 5, 8, 16).unwrap(); - terminal.write(b"\x1b_Gq=2,a=T,C=1,U=1,f=32,s=1,v=1,i=1193046,c=2,r=1,m=0;/wAA/w==\x1b\\"); + terminal.write(b"\x1b_Gq=2,a=t,t=d,f=32,s=1,v=1,i=1193046,m=0;/wAA/w==\x1b\\"); + terminal.write(b"\x1b_Gq=2,a=p,U=1,i=1193046,c=2,r=1\x1b\\"); terminal.write("\x1b[2;3H\x1b[38;2;18;52;86m\u{10eeee}\u{0305}\u{0305}\u{10eeee}\u{0305}\u{030d}\x1b[0m".as_bytes()); let placements = terminal.kitty_image_placements().unwrap(); @@ -2721,13 +2784,11 @@ mod tests { let mut cells = row_iter.populate_cells(&mut row_cells).unwrap(); let mut line = String::new(); while cells.next() { - let graphemes = cells.graphemes().unwrap(); - if let Some(codepoint) = graphemes.first().copied() { - if let Some(ch) = char::from_u32(codepoint) { - line.push(ch); - } - } else { + let text = cells.grapheme_text().unwrap(); + if text.is_empty() { line.push(' '); + } else { + line.push_str(&text); } } let trimmed = line.trim_end().to_string(); |
