diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-06 22:05:00 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-06 22:56:01 +0300 |
| commit | 6ccf156a3dd23f419c03892ddc1136e72957f66a (patch) | |
| tree | 1d5465e61d03344a63683ad3b9bbdec9a4476a5c /src/ghostty | |
| parent | 62b751b2b551d866e2aebfdecf89f7a003d64ebc (diff) | |
refactor: use libghostty-native selection text reading
Diffstat (limited to 'src/ghostty')
| -rw-r--r-- | src/ghostty/mod.rs | 90 |
1 files changed, 90 insertions, 0 deletions
diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs index a4846db4..e9ca1f86 100644 --- a/src/ghostty/mod.rs +++ b/src/ghostty/mod.rs @@ -223,6 +223,23 @@ struct WritePtyCallbackState { callback: Box<dyn FnMut(&[u8]) + Send>, } +#[repr(C)] +struct GhosttyTerminalSelection { + start: ffi::GhosttyPoint, + end: ffi::GhosttyPoint, + rectangle: bool, +} + +unsafe extern "C" { + fn ghostty_terminal_read_text( + terminal: ffi::GhosttyTerminal_ptr, + selection: GhosttyTerminalSelection, + allocator: *const ffi::GhosttyAllocator, + out_ptr: *mut *mut u8, + out_len: *mut usize, + ) -> ffi::GhosttyResult; +} + unsafe extern "C" fn write_pty_trampoline( _terminal: ffi::GhosttyTerminal_ptr, userdata: *mut c_void, @@ -438,6 +455,46 @@ impl Terminal { Ok(buffer) } + pub fn read_text_viewport( + &self, + start: (u16, u32), + end: (u16, u32), + rectangle: bool, + ) -> Result<String, Error> { + let selection = GhosttyTerminalSelection { + start: ghostty_viewport_point(start.0, start.1), + end: ghostty_viewport_point(end.0, end.1), + rectangle, + }; + let mut out_ptr = ptr::null_mut(); + let mut out_len = 0usize; + unsafe { + ghostty_terminal_read_text( + self.raw, + selection, + ptr::null(), + &mut out_ptr, + &mut out_len, + ) + .into_result()?; + } + + let text = if out_len == 0 { + String::new() + } else { + let bytes = unsafe { slice::from_raw_parts(out_ptr.cast_const(), out_len) }; + String::from_utf8_lossy(bytes).into_owned() + }; + + if !out_ptr.is_null() { + unsafe { + ffi::ghostty_free(ptr::null(), out_ptr, out_len); + } + } + + Ok(text) + } + pub fn scroll_viewport_bottom(&mut self) { let viewport = ffi::GhosttyTerminalScrollViewport { tag: ffi::GhosttyTerminalScrollViewportTag_GHOSTTY_SCROLL_VIEWPORT_BOTTOM, @@ -504,6 +561,15 @@ impl Drop for Terminal { } } +fn ghostty_viewport_point(x: u16, y: u32) -> ffi::GhosttyPoint { + ffi::GhosttyPoint { + tag: ffi::GhosttyPointTag_GHOSTTY_POINT_TAG_VIEWPORT, + value: ffi::GhosttyPointValue { + coordinate: ffi::GhosttyPointCoordinate { x, y }, + }, + } +} + pub struct RenderState { raw: ffi::GhosttyRenderState_ptr, } @@ -1104,6 +1170,30 @@ mod tests { } #[test] + fn terminal_read_text_viewport_unwraps_soft_wrapped_selection() { + let mut terminal = Terminal::new(5, 3, 0).unwrap(); + terminal.write("1ABCD2EFGH3IJKL".as_bytes()); + + let text = terminal.read_text_viewport((0, 1), (2, 2), false).unwrap(); + assert_eq!(text, "2EFGH3IJ"); + } + + #[test] + fn terminal_read_text_viewport_handles_wide_chars() { + let mut terminal = Terminal::new(5, 3, 0).unwrap(); + terminal.write("1A⚡".as_bytes()); + + let full = terminal.read_text_viewport((0, 0), (3, 0), false).unwrap(); + assert_eq!(full, "1A⚡"); + + let through_wide_head = terminal.read_text_viewport((0, 0), (2, 0), false).unwrap(); + assert_eq!(through_wide_head, "1A⚡"); + + let wide_only = terminal.read_text_viewport((3, 0), (3, 0), false).unwrap(); + assert_eq!(wide_only, "⚡"); + } + + #[test] fn terminal_and_render_state_smoke_test() { let mut terminal = Terminal::new(8, 3, 100).unwrap(); assert_eq!(terminal.cols().unwrap(), 8); |
