From b0aa7b2cddc57a55a1c0285a1ea82b59ac9783bd Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Thu, 7 May 2026 21:41:13 +0300 Subject: fix: preserve osc8 hyperlinks in client rendering fixes #73 --- src/ghostty/mod.rs | 108 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 28 deletions(-) (limited to 'src/ghostty') diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs index 5648e606..60ca785d 100644 --- a/src/ghostty/mod.rs +++ b/src/ghostty/mod.rs @@ -401,12 +401,16 @@ impl Terminal { } pub fn screen_graphemes(&self, x: u16, y: u32) -> Result, Error> { - let point = ffi::GhosttyPoint { - tag: ffi::GhosttyPointTag_GHOSTTY_POINT_TAG_SCREEN, - value: ffi::GhosttyPointValue { - coordinate: ffi::GhosttyPointCoordinate { x, y }, - }, - }; + let grid_ref = self.grid_ref(ghostty_screen_point(x, y))?; + grid_ref_graphemes(&grid_ref) + } + + pub fn viewport_hyperlink_uri(&self, x: u16, y: u32) -> Result, Error> { + let grid_ref = self.grid_ref(ghostty_viewport_point(x, y))?; + grid_ref_hyperlink_uri(&grid_ref) + } + + fn grid_ref(&self, point: ffi::GhosttyPoint) -> Result { let mut grid_ref = ffi::GhosttyGridRef { size: mem::size_of::(), ..Default::default() @@ -414,28 +418,7 @@ impl Terminal { unsafe { ffi::ghostty_terminal_grid_ref(self.raw, point, &mut grid_ref).into_result()?; } - let mut required = 0usize; - let result = unsafe { - ffi::ghostty_grid_ref_graphemes(&grid_ref, ptr::null_mut(), 0, &mut required) - }; - if result != ffi::GhosttyResult_GHOSTTY_OUT_OF_SPACE { - result.into_result()?; - } - let mut buffer = vec![0u32; required]; - if required == 0 { - return Ok(buffer); - } - unsafe { - ffi::ghostty_grid_ref_graphemes( - &grid_ref, - buffer.as_mut_ptr(), - buffer.len(), - &mut required, - ) - .into_result()?; - } - buffer.truncate(required); - Ok(buffer) + Ok(grid_ref) } pub fn read_text_viewport( @@ -620,6 +603,49 @@ fn ghostty_screen_point(x: u16, y: u32) -> ffi::GhosttyPoint { } } +fn grid_ref_graphemes(grid_ref: &ffi::GhosttyGridRef) -> Result, Error> { + let mut required = 0usize; + let result = + unsafe { ffi::ghostty_grid_ref_graphemes(grid_ref, ptr::null_mut(), 0, &mut required) }; + if result != ffi::GhosttyResult_GHOSTTY_OUT_OF_SPACE { + result.into_result()?; + } + let mut buffer = vec![0u32; required]; + if required == 0 { + return Ok(buffer); + } + unsafe { + ffi::ghostty_grid_ref_graphemes(grid_ref, buffer.as_mut_ptr(), buffer.len(), &mut required) + .into_result()?; + } + buffer.truncate(required); + Ok(buffer) +} + +fn grid_ref_hyperlink_uri(grid_ref: &ffi::GhosttyGridRef) -> Result, Error> { + let mut required = 0usize; + let result = + unsafe { ffi::ghostty_grid_ref_hyperlink_uri(grid_ref, ptr::null_mut(), 0, &mut required) }; + if result != ffi::GhosttyResult_GHOSTTY_OUT_OF_SPACE { + result.into_result()?; + } + if required == 0 { + return Ok(None); + } + let mut buffer = vec![0u8; required]; + unsafe { + ffi::ghostty_grid_ref_hyperlink_uri( + grid_ref, + buffer.as_mut_ptr(), + buffer.len(), + &mut required, + ) + .into_result()?; + } + buffer.truncate(required); + Ok(Some(String::from_utf8_lossy(&buffer).into_owned())) +} + pub struct RenderState { raw: ffi::GhosttyRenderState_ptr, } @@ -1083,6 +1109,20 @@ impl<'a> RowCellIter<'a> { Ok(CellWide::from_raw(wide)) } + pub fn has_hyperlink(&self) -> Result { + let raw = self.raw_cell()?; + let mut has_hyperlink = false; + unsafe { + ffi::ghostty_cell_get( + raw, + ffi::GhosttyCellData_GHOSTTY_CELL_DATA_HAS_HYPERLINK, + (&mut has_hyperlink as *mut bool).cast(), + ) + .into_result()?; + } + Ok(has_hyperlink) + } + pub fn style(&self) -> Result { let mut style = ffi::GhosttyStyle { size: mem::size_of::(), @@ -1292,6 +1332,18 @@ mod tests { assert_eq!(text, "2EFGH3IJ"); } + #[test] + fn terminal_extracts_viewport_hyperlink_uri() { + let mut terminal = Terminal::new(20, 3, 0).unwrap(); + terminal.write(b"\x1b]8;;https://example.com\x1b\\Link\x1b]8;;\x1b\\"); + + assert_eq!( + terminal.viewport_hyperlink_uri(0, 0).unwrap().as_deref(), + Some("https://example.com") + ); + assert_eq!(terminal.viewport_hyperlink_uri(4, 0).unwrap(), None); + } + #[test] fn terminal_read_text_viewport_handles_wide_chars() { let mut terminal = Terminal::new(5, 3, 0).unwrap(); -- cgit v1.2.3