aboutsummaryrefslogtreecommitdiff
path: root/src/ghostty
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-10 04:22:10 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-10 04:22:10 +0300
commitc8850a2e3e284033142db0f8117fa5998c68bd59 (patch)
tree487530d94265c096ceeb3ded7c60a94f14a6f7fd /src/ghostty
parent0de503ccd9a8fedeeb6cbcc1446ab2bc644fdbb6 (diff)
feat: add copy mode search and tmux word motions
refs #1230
Diffstat (limited to 'src/ghostty')
-rw-r--r--src/ghostty/mod.rs93
1 files changed, 93 insertions, 0 deletions
diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs
index 97b22f59..a65272c6 100644
--- a/src/ghostty/mod.rs
+++ b/src/ghostty/mod.rs
@@ -417,6 +417,19 @@ pub enum CellWide {
SpacerHead,
}
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) struct ScreenTextCell {
+ pub wide: CellWide,
+ pub graphemes: Vec<u32>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) struct ScreenTextRow {
+ pub cells: Vec<ScreenTextCell>,
+ pub soft_wrapped: bool,
+ pub wrap_continuation: bool,
+}
+
impl CellWide {
fn from_raw(value: ffi::GhosttyCellWide) -> Self {
match value {
@@ -768,6 +781,43 @@ impl Terminal {
Ok((wide, graphemes))
}
+ pub(crate) fn screen_text_rows(&self) -> Result<Vec<ScreenTextRow>, Error> {
+ self.screen_text_rows_range(0, usize::MAX)
+ }
+
+ pub(crate) fn screen_text_rows_range(
+ &self,
+ start_row: usize,
+ end_row_exclusive: usize,
+ ) -> Result<Vec<ScreenTextRow>, Error> {
+ let total_rows = self.total_rows()?;
+ let start_row = start_row.min(total_rows);
+ let end_row_exclusive = end_row_exclusive.min(total_rows).max(start_row);
+ let cols = self.cols()?;
+ let mut rows = Vec::with_capacity(end_row_exclusive.saturating_sub(start_row));
+ for y in start_row..end_row_exclusive {
+ let Some(y) = u32::try_from(y).ok() else {
+ break;
+ };
+ let mut grid_ref = self.grid_ref(ghostty_screen_point(0, y))?;
+ let (soft_wrapped, wrap_continuation) = grid_ref_wrap_state(&grid_ref)?;
+ let mut cells = Vec::with_capacity(usize::from(cols));
+ for x in 0..cols {
+ grid_ref.x = x;
+ cells.push(ScreenTextCell {
+ wide: grid_ref_wide(&grid_ref)?,
+ graphemes: grid_ref_graphemes(&grid_ref)?,
+ });
+ }
+ rows.push(ScreenTextRow {
+ cells,
+ soft_wrapped,
+ wrap_continuation,
+ });
+ }
+ Ok(rows)
+ }
+
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)?;
@@ -1531,6 +1581,30 @@ fn grid_ref_wide(grid_ref: &ffi::GhosttyGridRef) -> Result<CellWide, Error> {
Ok(CellWide::from_raw(wide))
}
+fn grid_ref_wrap_state(grid_ref: &ffi::GhosttyGridRef) -> Result<(bool, bool), Error> {
+ let mut row = 0;
+ unsafe {
+ ffi::ghostty_grid_ref_row(grid_ref, &mut row).into_result()?;
+ }
+ let mut soft_wrapped = false;
+ let mut wrap_continuation = false;
+ unsafe {
+ ffi::ghostty_row_get(
+ row,
+ ffi::GhosttyRowData_GHOSTTY_ROW_DATA_WRAP,
+ (&mut soft_wrapped as *mut bool).cast(),
+ )
+ .into_result()?;
+ ffi::ghostty_row_get(
+ row,
+ ffi::GhosttyRowData_GHOSTTY_ROW_DATA_WRAP_CONTINUATION,
+ (&mut wrap_continuation as *mut bool).cast(),
+ )
+ .into_result()?;
+ }
+ Ok((soft_wrapped, wrap_continuation))
+}
+
fn kitty_placement_u32(
iterator: ffi::GhosttyKittyGraphicsPlacementIterator,
data: ffi::GhosttyKittyGraphicsPlacementData,
@@ -3240,6 +3314,25 @@ mod tests {
}
#[test]
+ fn screen_text_rows_preserve_wrap_and_grapheme_cells() {
+ let mut terminal = Terminal::new(5, 3, 100).unwrap();
+ terminal.write("abcdef\r\n界e\u{301}".as_bytes());
+
+ let rows = terminal.screen_text_rows().unwrap();
+
+ assert_eq!(rows.len(), 3);
+ assert!(rows[0].soft_wrapped);
+ assert!(!rows[0].wrap_continuation);
+ assert!(!rows[1].soft_wrapped);
+ assert!(rows[1].wrap_continuation);
+ assert!(!rows[2].wrap_continuation);
+ assert_eq!(rows[2].cells[0].wide, CellWide::Wide);
+ assert_eq!(rows[2].cells[0].graphemes, vec!['界' as u32]);
+ assert_eq!(rows[2].cells[1].wide, CellWide::SpacerTail);
+ assert_eq!(rows[2].cells[2].graphemes, vec!['e' as u32, 0x301]);
+ }
+
+ #[test]
fn render_state_row_dirty_can_be_cleared_independently() {
let mut terminal = Terminal::new(8, 3, 100).unwrap();
let mut render_state = RenderState::new().unwrap();