diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-01 23:05:23 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-01 23:05:51 +0300 |
| commit | 5bd21b7ed468b5ff4d1a5b2ed7e211f401eb138a (patch) | |
| tree | 672fa4625cca0ecb3e9376fc7f8e33d7b0e9deef /src | |
| parent | 32abb3776668ee768200019c170956f67fe33a86 (diff) | |
chore: update libghostty vendor
refs #276
Diffstat (limited to 'src')
| -rw-r--r-- | src/ghostty/bindings.rs | 37 | ||||
| -rw-r--r-- | src/ghostty/mod.rs | 173 | ||||
| -rw-r--r-- | src/pane/terminal.rs | 162 |
3 files changed, 288 insertions, 84 deletions
diff --git a/src/ghostty/bindings.rs b/src/ghostty/bindings.rs index 4998e2b6..4676c327 100644 --- a/src/ghostty/bindings.rs +++ b/src/ghostty/bindings.rs @@ -104,6 +104,34 @@ const _: () = { ["Offset of field: GhosttyString::ptr"][::std::mem::offset_of!(GhosttyString, ptr) - 0usize]; ["Offset of field: GhosttyString::len"][::std::mem::offset_of!(GhosttyString, len) - 8usize]; }; +#[doc = " A caller-provided byte buffer.\n\n APIs that write to this type use `len` for the number of bytes written on\n GHOSTTY_SUCCESS and the required byte capacity on GHOSTTY_OUT_OF_SPACE."] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct GhosttyBuffer { + #[doc = " Destination buffer for bytes. May be NULL when cap is 0 to query required size."] + pub ptr: *mut u8, + #[doc = " Capacity of ptr in bytes."] + pub cap: usize, + #[doc = " Bytes written on success, or required byte capacity on GHOSTTY_OUT_OF_SPACE."] + pub len: usize, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of GhosttyBuffer"][::std::mem::size_of::<GhosttyBuffer>() - 24usize]; + ["Alignment of GhosttyBuffer"][::std::mem::align_of::<GhosttyBuffer>() - 8usize]; + ["Offset of field: GhosttyBuffer::ptr"][::std::mem::offset_of!(GhosttyBuffer, ptr) - 0usize]; + ["Offset of field: GhosttyBuffer::cap"][::std::mem::offset_of!(GhosttyBuffer, cap) - 8usize]; + ["Offset of field: GhosttyBuffer::len"][::std::mem::offset_of!(GhosttyBuffer, len) - 16usize]; +}; +impl Default for GhosttyBuffer { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::<Self>::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} impl Default for GhosttyString { fn default() -> Self { let mut s = ::std::mem::MaybeUninit::<Self>::uninit(); @@ -1605,6 +1633,15 @@ pub const GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_BG_ #[doc = " The resolved foreground color of the cell (GhosttyColorRgb).\n Resolves palette indices through the palette. Bold color handling\n is not applied; the caller should handle bold styling separately.\n Returns GHOSTTY_INVALID_VALUE if the cell has no explicit foreground\n color, in which case the caller should use whatever default foreground\n color it wants (e.g. the terminal foreground)."] pub const GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_FG_COLOR: GhosttyRenderStateRowCellsData = 6; +#[doc = " Whether the cell is contained within the current selection (bool)."] +pub const GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_SELECTED: + GhosttyRenderStateRowCellsData = 7; +#[doc = " Whether the cell has any explicit styling (bool)."] +pub const GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_HAS_STYLING: + GhosttyRenderStateRowCellsData = 8; +#[doc = " Encode the current cell's full grapheme cluster as UTF-8 into a GhosttyBuffer."] +pub const GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_UTF8: + GhosttyRenderStateRowCellsData = 9; #[doc = " Queryable data kinds for ghostty_render_state_row_cells_get().\n\n @ingroup render"] pub type GhosttyRenderStateRowCellsData = ::std::os::raw::c_uint; unsafe extern "C" { diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs index e2b592d2..57114de4 100644 --- a/src/ghostty/mod.rs +++ b/src/ghostty/mod.rs @@ -410,8 +410,15 @@ unsafe extern "C" fn write_pty_trampoline( if userdata.is_null() { return; } + if data.is_null() && len != 0 { + return; + } let state = unsafe { &mut *(userdata.cast::<WritePtyCallbackState>()) }; - let bytes = unsafe { slice::from_raw_parts(data, len) }; + let bytes = if len == 0 { + &[] + } else { + unsafe { slice::from_raw_parts(data, len) } + }; (state.callback)(bytes); } @@ -565,6 +572,8 @@ impl Terminal { cell_width_px: u32, cell_height_px: u32, ) -> Result<(), Error> { + let cell_width_px = cell_width_px.max(1); + let cell_height_px = cell_height_px.max(1); // SAFETY: self.raw is valid and sizes are plain values. unsafe { ffi::ghostty_terminal_resize(self.raw, cols, rows, cell_width_px, cell_height_px) @@ -726,6 +735,19 @@ impl Terminal { grid_ref_graphemes(&grid_ref) } + 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)?; + let mut style = ffi::GhosttyStyle { + size: mem::size_of::<ffi::GhosttyStyle>(), + ..Default::default() + }; + unsafe { + ffi::ghostty_grid_ref_style(&grid_ref, &mut style).into_result()?; + } + Ok((graphemes, style.into())) + } + 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) @@ -1182,29 +1204,20 @@ impl Terminal { return Ok(Vec::new()); } - let cols = self.cols()?.max(1) as u32; - let rows = self.rows()?.max(1) as u32; - let cell_width = (self.width_px()? / cols).max(1); - let cell_height = (self.height_px()? / rows).max(1); - let mut render_state = RenderState::new()?; - render_state.update(self)?; - let mut row_iterator = RowIterator::new()?; - let mut row_cells = RowCells::new()?; - let mut row_iter = render_state.populate_row_iterator(&mut row_iterator)?; - let mut graphemes = Vec::new(); + let viewport_cols = self.cols()?.max(1); + let viewport_rows = self.rows()?.max(1); + let cell_width = (self.width_px()? / u32::from(viewport_cols)).max(1); + let cell_height = (self.height_px()? / u32::from(viewport_rows)).max(1); let mut runs = Vec::new(); - let mut y = 0u16; - while row_iter.next() { - let mut cells = row_iter.populate_cells(&mut row_cells)?; + for y in 0..viewport_rows { let mut current: Option<KittyVirtualRun> = None; - let mut x = 0u16; - while cells.next() { - let cell = kitty_virtual_cell(x, y, &cells, &mut graphemes)?; + for x in 0..viewport_cols { + let (graphemes, style) = self.viewport_graphemes_and_style(x, u32::from(y))?; + let cell = kitty_virtual_cell(x, y, &graphemes, style); match cell { Some(cell) => { if let Some(run) = current.as_mut() { if run.append(cell) { - x = x.saturating_add(1); continue; } runs.push(*run); @@ -1217,12 +1230,10 @@ impl Terminal { } } } - x = x.saturating_add(1); } if let Some(run) = current { runs.push(run); } - y = y.saturating_add(1); } let mut placements = Vec::new(); @@ -1455,14 +1466,12 @@ fn find_virtual_placement_spec( fn kitty_virtual_cell( x: u16, y: u16, - cells: &RowCellIter<'_>, - graphemes: &mut Vec<u32>, -) -> Result<Option<KittyVirtualCell>, Error> { - cells.graphemes_into(graphemes)?; + graphemes: &[u32], + style: CellStyle, +) -> Option<KittyVirtualCell> { if graphemes.first().copied() != Some(KITTY_UNICODE_PLACEHOLDER) { - return Ok(None); + return None; } - let style = cells.style()?; let image_id_low = style .fg_color .map(kitty_placeholder_color_to_id) @@ -1482,7 +1491,7 @@ fn kitty_virtual_cell( .and_then(|codepoint| kitty_placeholder_diacritic_index(*codepoint)) .filter(|high| *high <= u32::from(u8::MAX)); - Ok(Some(KittyVirtualCell { + Some(KittyVirtualCell { x, y, image_id_low, @@ -1490,7 +1499,7 @@ fn kitty_virtual_cell( placement_id, row, col, - })) + }) } fn kitty_placeholder_color_to_id(color: CellColor) -> u32 { @@ -2365,42 +2374,95 @@ impl<'a> RowCellIter<'a> { } } - pub fn grapheme_len(&self) -> Result<u32, Error> { - let mut len = 0u32; - // SAFETY: len output matches requested cell data type. + fn raw_cell_text_into(&self, text: &mut String) -> Result<(), Error> { + let raw = self.raw_cell()?; + let mut has_text = false; unsafe { - ffi::ghostty_render_state_row_cells_get( - self.cells.raw, - ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_LEN, - (&mut len as *mut u32).cast(), + ffi::ghostty_cell_get( + raw, + ffi::GhosttyCellData_GHOSTTY_CELL_DATA_HAS_TEXT, + (&mut has_text as *mut bool).cast(), ) .into_result()?; } - Ok(len) + if !has_text { + return Ok(()); + } + + let mut codepoint = 0u32; + unsafe { + ffi::ghostty_cell_get( + raw, + ffi::GhosttyCellData_GHOSTTY_CELL_DATA_CODEPOINT, + (&mut codepoint as *mut u32).cast(), + ) + .into_result()?; + } + if let Some(ch) = char::from_u32(codepoint) { + text.push(ch); + } + Ok(()) } - pub fn graphemes(&self) -> Result<Vec<u32>, Error> { - let mut out = Vec::new(); - self.graphemes_into(&mut out)?; - Ok(out) + pub fn grapheme_text(&self) -> Result<String, Error> { + let mut bytes = Vec::new(); + let mut text = String::new(); + self.grapheme_text_into(&mut bytes, &mut text)?; + Ok(text) } - pub fn graphemes_into(&self, out: &mut Vec<u32>) -> Result<(), Error> { - let len = self.grapheme_len()? as usize; - out.clear(); - out.resize(len, 0); - if len == 0 { - return Ok(()); + pub fn grapheme_text_into(&self, bytes: &mut Vec<u8>, text: &mut String) -> Result<(), Error> { + text.clear(); + bytes.clear(); + + let mut buffer = ffi::GhosttyBuffer { + ptr: ptr::null_mut(), + cap: 0, + len: 0, + }; + let result = unsafe { + ffi::ghostty_render_state_row_cells_get( + self.cells.raw, + ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_UTF8, + (&mut buffer as *mut ffi::GhosttyBuffer).cast(), + ) + }; + match result { + ffi::GhosttyResult_GHOSTTY_SUCCESS if buffer.len == 0 => { + return self.raw_cell_text_into(text); + } + ffi::GhosttyResult_GHOSTTY_SUCCESS => { + return Err(Error(ffi::GhosttyResult_GHOSTTY_INVALID_VALUE)); + } + ffi::GhosttyResult_GHOSTTY_OUT_OF_SPACE => {} + other => return Err(Error(other)), + } + + if buffer.len == 0 { + return self.raw_cell_text_into(text); } - // SAFETY: out buffer is allocated for the grapheme count returned by the API. + bytes.resize(buffer.len, 0); + let mut buffer = ffi::GhosttyBuffer { + ptr: bytes.as_mut_ptr(), + cap: bytes.len(), + len: 0, + }; unsafe { ffi::ghostty_render_state_row_cells_get( self.cells.raw, - ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_BUF, - out.as_mut_ptr().cast::<c_void>(), + ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_UTF8, + (&mut buffer as *mut ffi::GhosttyBuffer).cast(), ) .into_result()?; } + if buffer.len > bytes.len() { + return Err(Error(ffi::GhosttyResult_GHOSTTY_OUT_OF_SPACE)); + } + bytes.truncate(buffer.len); + match std::str::from_utf8(bytes) { + Ok(value) => text.push_str(value), + Err(_) => text.push_str(&String::from_utf8_lossy(bytes)), + } Ok(()) } } @@ -2513,7 +2575,8 @@ mod tests { let mut terminal = Terminal::new(10, 5, 0).unwrap(); terminal.enable_kitty_graphics().unwrap(); terminal.resize(10, 5, 8, 16).unwrap(); - terminal.write(b"\x1b_Gq=2,a=T,C=1,U=1,f=32,s=1,v=1,i=1193046,c=2,r=1,m=0;/wAA/w==\x1b\\"); + terminal.write(b"\x1b_Gq=2,a=t,t=d,f=32,s=1,v=1,i=1193046,m=0;/wAA/w==\x1b\\"); + terminal.write(b"\x1b_Gq=2,a=p,U=1,i=1193046,c=2,r=1\x1b\\"); terminal.write("\x1b[2;3H\x1b[38;2;18;52;86m\u{10eeee}\u{0305}\u{0305}\u{10eeee}\u{0305}\u{030d}\x1b[0m".as_bytes()); let placements = terminal.kitty_image_placements().unwrap(); @@ -2721,13 +2784,11 @@ mod tests { let mut cells = row_iter.populate_cells(&mut row_cells).unwrap(); let mut line = String::new(); while cells.next() { - let graphemes = cells.graphemes().unwrap(); - if let Some(codepoint) = graphemes.first().copied() { - if let Some(ch) = char::from_u32(codepoint) { - line.push(ch); - } - } else { + let text = cells.grapheme_text().unwrap(); + if text.is_empty() { line.push(' '); + } else { + line.push_str(&text); } } let trimmed = line.trim_end().to_string(); diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index db05d1fa..8841bad0 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -562,9 +562,58 @@ impl GhosttyPaneTerminal { pub fn resize(&self, rows: u16, cols: u16, cell_width_px: u32, cell_height_px: u32) { if let Ok(mut core) = self.core.lock() { + let offset_from_bottom = core + .terminal + .scrollbar() + .ok() + .map(|scrollbar| { + scrollbar + .total + .saturating_sub(scrollbar.offset + scrollbar.len) + }) + .unwrap_or(0); + let bottom_before_resize = ghostty_detection_text(&core) + .map(|text| !text.trim().is_empty()) + .unwrap_or(false); + let resize_recovery_probe_lines = usize::from(rows) + .saturating_mul(8) + .max(DEFAULT_DETECTION_ROWS); + let replay_ansi = if core.terminal.active_screen().ok() + == Some(crate::ghostty::ActiveScreen::Primary) + && bottom_before_resize + { + ghostty_recent_ansi(&core, resize_recovery_probe_lines, true) + .ok() + .filter(|ansi| !ansi.trim().is_empty()) + } else { + None + }; + let _ = core .terminal .resize(cols, rows, cell_width_px, cell_height_px); + + let bottom_is_blank = ghostty_detection_text(&core) + .map(|text| text.trim().is_empty()) + .unwrap_or(false); + if bottom_is_blank { + if let Some(ansi) = replay_ansi.as_deref() { + core.terminal.scroll_viewport_bottom(); + core.terminal.write(ansi.as_bytes()); + } + } + ghostty_restore_scroll_offset_from_bottom(&mut core.terminal, offset_from_bottom); + if offset_from_bottom > 0 { + let mut remaining = offset_from_bottom.min(resize_recovery_probe_lines); + while remaining > 0 + && ghostty_visible_text(&mut core) + .map(|text| text.trim().is_empty()) + .unwrap_or(false) + { + core.terminal.scroll_viewport_delta(1); + remaining -= 1; + } + } } } @@ -889,7 +938,7 @@ impl GhosttyPaneTerminal { Ok(rows) => rows, Err(_) => return, }; - let mut grapheme_scratch = Vec::new(); + let mut grapheme_bytes = Vec::new(); let mut symbol_scratch = String::new(); let mut y = 0u16; while y < area.height && rows.next() { @@ -911,7 +960,7 @@ impl GhosttyPaneTerminal { &cells, wide, hide_kitty_placeholders, - &mut grapheme_scratch, + &mut grapheme_bytes, &mut symbol_scratch, ) { Ok(symbol) => symbol, @@ -1034,6 +1083,9 @@ fn ghostty_recent_text( ) -> Result<String, crate::ghostty::Error> { let total_rows = core.terminal.total_rows()?; let cols = core.terminal.cols()?; + if total_rows == 0 || cols == 0 { + return Ok(String::new()); + } let start = total_rows.saturating_sub(lines); let mut rows = Vec::with_capacity(total_rows.saturating_sub(start)); for y in start..total_rows { @@ -1053,7 +1105,7 @@ fn ghostty_recent_text_unwrapped( return Ok(String::new()); } let start = total_rows.saturating_sub(lines) as u32; - let end = (total_rows.saturating_sub(1)) as u32; + let end = total_rows.saturating_sub(1) as u32; core.terminal .read_text_screen((0, start), (cols.saturating_sub(1), end), false) } @@ -1069,11 +1121,29 @@ fn ghostty_recent_ansi( return Ok(String::new()); } let start = total_rows.saturating_sub(lines) as u32; - let end = (total_rows.saturating_sub(1)) as u32; + let end = total_rows.saturating_sub(1) as u32; core.terminal .read_ansi_screen((0, start), (cols.saturating_sub(1), end), false, unwrap) } +fn ghostty_restore_scroll_offset_from_bottom( + terminal: &mut crate::ghostty::Terminal, + offset_from_bottom: usize, +) { + terminal.scroll_viewport_bottom(); + if offset_from_bottom == 0 { + return; + } + let Ok(scrollbar) = terminal.scrollbar() else { + return; + }; + let max_offset = scrollbar.total.saturating_sub(scrollbar.len); + let offset = offset_from_bottom.min(max_offset).min(isize::MAX as usize) as isize; + if offset > 0 { + terminal.scroll_viewport_delta(-offset); + } +} + fn ghostty_extract_selection( core: &mut GhosttyPaneCore, selection: &crate::selection::Selection, @@ -1091,7 +1161,9 @@ fn ghostty_screen_row( let mut line = String::new(); for x in 0..cols { let graphemes = core.terminal.screen_graphemes(x, y)?; - if graphemes.is_empty() { + if graphemes.is_empty() + || graphemes.first().copied() == Some(crate::ghostty::KITTY_UNICODE_PLACEHOLDER) + { line.push(' '); } else { for codepoint in graphemes { @@ -1117,18 +1189,12 @@ fn ghostty_line_from_cells( fn ghostty_cell_symbol( cells: &crate::ghostty::RowCellIter<'_>, ) -> Result<String, crate::ghostty::Error> { - let graphemes = cells.graphemes()?; - if graphemes.is_empty() { + let text = cells.grapheme_text()?; + if text.chars().next().map(u32::from) == Some(crate::ghostty::KITTY_UNICODE_PLACEHOLDER) { return Ok(" ".to_string()); } - let mut text = String::new(); - for codepoint in graphemes { - if let Some(ch) = char::from_u32(codepoint) { - text.push(ch); - } - } if text.is_empty() { - text.push(' '); + return Ok(" ".to_string()); } Ok(text) } @@ -1167,7 +1233,7 @@ fn ghostty_buffer_symbol_into<'a>( cells: &crate::ghostty::RowCellIter<'_>, wide: crate::ghostty::CellWide, hide_kitty_placeholders: bool, - grapheme_scratch: &mut Vec<u32>, + grapheme_bytes: &mut Vec<u8>, symbol_scratch: &'a mut String, ) -> Result<&'a str, crate::ghostty::Error> { symbol_scratch.clear(); @@ -1175,21 +1241,13 @@ fn ghostty_buffer_symbol_into<'a>( crate::ghostty::CellWide::SpacerTail => {} crate::ghostty::CellWide::SpacerHead => symbol_scratch.push(' '), crate::ghostty::CellWide::Narrow | crate::ghostty::CellWide::Wide => { - cells.graphemes_into(grapheme_scratch)?; + cells.grapheme_text_into(grapheme_bytes, symbol_scratch)?; let hidden_kitty_placeholder = hide_kitty_placeholders - && grapheme_scratch.first().copied() + && symbol_scratch.chars().next().map(u32::from) == Some(crate::ghostty::KITTY_UNICODE_PLACEHOLDER); - if hidden_kitty_placeholder || grapheme_scratch.is_empty() { + if hidden_kitty_placeholder || symbol_scratch.is_empty() { + symbol_scratch.clear(); symbol_scratch.push(' '); - } else { - for &codepoint in grapheme_scratch.iter() { - if let Some(ch) = char::from_u32(codepoint) { - symbol_scratch.push(ch); - } - } - if symbol_scratch.is_empty() { - symbol_scratch.push(' '); - } } } } @@ -2106,8 +2164,18 @@ mod tests { let metrics = pane.scroll_metrics().expect("scroll metrics after resize"); assert_eq!(metrics.viewport_rows, rows as usize); assert!(metrics.offset_from_bottom <= metrics.max_offset_from_bottom); + assert!( + metrics.offset_from_bottom > 0, + "resize should preserve a scrolled viewport instead of jumping to bottom" + ); assert!(metrics.max_offset_from_bottom > 0); - assert!(!pane.visible_text().trim().is_empty()); + let visible = pane.visible_text(); + assert!( + !visible.trim().is_empty(), + "visible text should not be empty after resize to {rows}x{cols}; metrics={metrics:?}; detection={:?}; recent={:?}", + pane.detection_text(), + pane.recent_text(6) + ); assert!( pane.detection_text().contains("END"), "bottom detection should remain independent from the scrolled viewport after resize" @@ -2116,6 +2184,42 @@ mod tests { } #[test] + fn resize_recovery_does_not_replay_history_when_visible_screen_was_blank() { + let (tx, _rx) = mpsc::channel(4); + let mut terminal = crate::ghostty::Terminal::new(20, 3, 10_000).unwrap(); + terminal.write(b"old history\r\n\x1b[2J\x1b[H"); + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + + assert!(pane.visible_text().trim().is_empty()); + assert!(pane.detection_text().trim().is_empty()); + + pane.resize(3, 20, 0, 0); + + assert!(pane.visible_text().trim().is_empty()); + assert!(pane.detection_text().trim().is_empty()); + assert!(pane.recent_text(3).trim().is_empty()); + } + + #[test] + fn resize_recovery_does_not_replay_scrolled_history_over_blank_bottom() { + let (tx, _rx) = mpsc::channel(4); + let mut terminal = crate::ghostty::Terminal::new(20, 3, 10_000).unwrap(); + write_numbered_lines(&mut terminal, 20); + terminal.write(b"\x1b[2J\x1b[H"); + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + + assert!(pane.detection_text().trim().is_empty()); + let metrics = pane.scroll_metrics().expect("scroll metrics"); + pane.set_scroll_offset_from_bottom(metrics.max_offset_from_bottom); + assert!(!pane.visible_text().trim().is_empty()); + + pane.resize(3, 20, 0, 0); + + assert!(pane.detection_text().trim().is_empty()); + assert!(pane.recent_text(3).trim().is_empty()); + } + + #[test] fn synchronized_output_suppresses_intermediate_render_requests_until_batch_ends() { let (tx, _rx) = mpsc::channel(4); let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); @@ -2198,6 +2302,8 @@ mod tests { assert_eq!(buffer[(0, 0)].symbol(), "b"); assert_eq!(buffer[(6, 0)].symbol(), " "); assert_eq!(buffer[(7, 0)].symbol(), "a"); + assert_eq!(pane.visible_text().lines().next(), Some("before after")); + assert_eq!(pane.recent_text(5), "before after\n"); } #[test] |
