diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-03 14:24:33 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-03 14:24:33 +0300 |
| commit | 9a2a7af5402f2bc67ab24c8b4c14c6dd20a43bb2 (patch) | |
| tree | 902f701de84d6fb7e08d16baaa85f99367cb9912 /src/ghostty | |
| parent | 94f6d9c0d9bb9cf9ffae99d8bbfb09e9bf2fc9e0 (diff) | |
perf: compress idle terminal scrollback (#3556)
Diffstat (limited to 'src/ghostty')
| -rw-r--r-- | src/ghostty/mod.rs | 74 |
1 files changed, 74 insertions, 0 deletions
diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs index 261638af..691160e6 100644 --- a/src/ghostty/mod.rs +++ b/src/ghostty/mod.rs @@ -74,6 +74,13 @@ pub enum Dirty { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum TerminalCompressionResult { + Unsupported, + Pending, + Complete, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct RowSelection { pub start_x: u16, pub end_x: u16, @@ -871,6 +878,41 @@ impl Terminal { } } + pub(crate) fn compression_activity(&self) -> Result<u64, Error> { + let mut activity = 0; + // SAFETY: self.raw is a live terminal handle and activity is a valid out pointer. + unsafe { + ffi::ghostty_terminal_compression_activity(self.raw, &mut activity).into_result()?; + } + Ok(activity) + } + + pub(crate) fn compress_incremental(&mut self) -> Result<TerminalCompressionResult, Error> { + let mut result = + ffi::GhosttyTerminalCompressionResult_GHOSTTY_TERMINAL_COMPRESSION_RESULT_UNSUPPORTED; + // SAFETY: self.raw is a live terminal handle and result is a valid out pointer. + unsafe { + ffi::ghostty_terminal_compress( + self.raw, + ffi::GhosttyTerminalCompressionMode_GHOSTTY_TERMINAL_COMPRESSION_MODE_INCREMENTAL, + &mut result, + ) + .into_result()?; + } + match result { + ffi::GhosttyTerminalCompressionResult_GHOSTTY_TERMINAL_COMPRESSION_RESULT_UNSUPPORTED => { + Ok(TerminalCompressionResult::Unsupported) + } + ffi::GhosttyTerminalCompressionResult_GHOSTTY_TERMINAL_COMPRESSION_RESULT_PENDING => { + Ok(TerminalCompressionResult::Pending) + } + ffi::GhosttyTerminalCompressionResult_GHOSTTY_TERMINAL_COMPRESSION_RESULT_COMPLETE => { + Ok(TerminalCompressionResult::Complete) + } + _ => Err(Error(ffi::GhosttyResult_GHOSTTY_INVALID_VALUE)), + } + } + pub fn set_default_palette(&mut self, palette: &[RgbColor; 256]) -> Result<(), Error> { let palette = palette.map(|color| ffi::GhosttyColorRgb { r: color.r, @@ -3304,6 +3346,38 @@ mod tests { } #[test] + fn incremental_compression_preserves_cold_scrollback() { + let mut terminal = Terminal::new(80, 24, 20_000_000).unwrap(); + let initial_activity = terminal.compression_activity().unwrap(); + let suffix = "x".repeat(66); + for line in 1..=10_000 { + terminal.write(format!("{line:05} {suffix}\r\n").as_bytes()); + } + assert_ne!(terminal.compression_activity().unwrap(), initial_activity); + + let mut complete = false; + for _ in 0..10_000 { + match terminal.compress_incremental().unwrap() { + TerminalCompressionResult::Unsupported => return, + TerminalCompressionResult::Pending => {} + TerminalCompressionResult::Complete => { + complete = true; + break; + } + } + } + assert!(complete, "incremental compression did not converge"); + + let oldest = terminal.read_text_screen((0, 0), (79, 0), false).unwrap(); + assert!(oldest.starts_with("00001 ")); + let last_row = terminal.total_rows().unwrap() as u32 - 1; + let newest = terminal + .read_text_screen((0, last_row - 1), (79, last_row), false) + .unwrap(); + assert!(newest.contains("10000")); + } + + #[test] fn kitty_image_fingerprint_covers_full_payload() { let mut data = vec![1u8; 4096 * 4]; let original = |
