diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-07 21:41:13 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-07 21:48:50 +0300 |
| commit | b0aa7b2cddc57a55a1c0285a1ea82b59ac9783bd (patch) | |
| tree | 866e4bd3322e69c4916457cde420c7e4b74792ef /src/ghostty | |
| parent | dcfe11a7588fcd42471159453810abc9b7720a57 (diff) | |
fix: preserve osc8 hyperlinks in client rendering
fixes #73
Diffstat (limited to 'src/ghostty')
| -rw-r--r-- | src/ghostty/mod.rs | 108 |
1 files changed, 80 insertions, 28 deletions
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<Vec<u32>, 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<Option<String>, 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<ffi::GhosttyGridRef, Error> { let mut grid_ref = ffi::GhosttyGridRef { size: mem::size_of::<ffi::GhosttyGridRef>(), ..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<Vec<u32>, 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<Option<String>, 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<bool, Error> { + 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<CellStyle, Error> { let mut style = ffi::GhosttyStyle { size: mem::size_of::<ffi::GhosttyStyle>(), @@ -1293,6 +1333,18 @@ mod tests { } #[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(); terminal.write("1A⚡".as_bytes()); |
