aboutsummaryrefslogtreecommitdiff
path: root/src/ghostty
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-06 22:05:00 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-06 22:56:01 +0300
commit6ccf156a3dd23f419c03892ddc1136e72957f66a (patch)
tree1d5465e61d03344a63683ad3b9bbdec9a4476a5c /src/ghostty
parent62b751b2b551d866e2aebfdecf89f7a003d64ebc (diff)
refactor: use libghostty-native selection text reading
Diffstat (limited to 'src/ghostty')
-rw-r--r--src/ghostty/mod.rs90
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);