diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-24 04:00:13 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-24 04:00:13 +0300 |
| commit | c0fb777ed7c7950c6a2f397113c1842c2e679306 (patch) | |
| tree | 56a1b58314e292433946c1eed5c025abb832595f /src/pane/terminal.rs | |
| parent | e608a75159fc5cca70485d83ffb8ee681c9da385 (diff) | |
fix: report truncated pane reads
refs #1717
Diffstat (limited to 'src/pane/terminal.rs')
| -rw-r--r-- | src/pane/terminal.rs | 136 |
1 files changed, 98 insertions, 38 deletions
diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index 81c57ef9..d5070b9b 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -141,6 +141,12 @@ pub(crate) struct ProcessBytesResult { pub terminal_responses: Vec<Bytes>, } +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub(crate) struct TerminalReadSnapshot { + pub text: String, + pub truncated: bool, +} + pub(crate) struct GhosttyPaneTerminal { pub core: Mutex<GhosttyPaneCore>, key_encoder: Mutex<crate::ghostty::KeyEncoder>, @@ -387,18 +393,26 @@ impl PaneTerminal { self.ghostty.recent_text(lines) } - pub fn recent_ansi(&self, lines: usize) -> String { - self.ghostty.recent_ansi(lines) + pub(crate) fn recent_text_snapshot(&self, lines: usize) -> TerminalReadSnapshot { + self.ghostty.recent_text_snapshot(lines) } - pub fn recent_unwrapped_text(&self, lines: usize) -> String { - self.ghostty.recent_unwrapped_text(lines) + pub(crate) fn recent_ansi_snapshot(&self, lines: usize) -> TerminalReadSnapshot { + self.ghostty.recent_ansi_snapshot(lines) + } + + pub(crate) fn recent_unwrapped_text_snapshot(&self, lines: usize) -> TerminalReadSnapshot { + self.ghostty.recent_unwrapped_text_snapshot(lines) } pub fn recent_unwrapped_ansi(&self, lines: usize) -> String { self.ghostty.recent_unwrapped_ansi(lines) } + pub(crate) fn recent_unwrapped_ansi_snapshot(&self, lines: usize) -> TerminalReadSnapshot { + self.ghostty.recent_unwrapped_ansi_snapshot(lines) + } + pub fn extract_selection(&self, selection: &crate::selection::Selection) -> Option<String> { self.ghostty.extract_selection(selection) } @@ -1708,34 +1722,52 @@ impl GhosttyPaneTerminal { } pub fn recent_text(&self, lines: usize) -> String { + self.recent_text_snapshot(lines).text + } + + pub(crate) fn recent_text_snapshot(&self, lines: usize) -> TerminalReadSnapshot { self.core .lock() .ok() - .and_then(|core| ghostty_recent_text(&core, lines).ok()) + .and_then(|core| ghostty_recent_text_snapshot(&core, lines).ok()) .unwrap_or_default() } + #[cfg(test)] pub fn recent_ansi(&self, lines: usize) -> String { + self.recent_ansi_snapshot(lines).text + } + + pub(crate) fn recent_ansi_snapshot(&self, lines: usize) -> TerminalReadSnapshot { self.core .lock() .ok() - .and_then(|core| ghostty_recent_ansi(&core, lines, false).ok()) + .and_then(|core| ghostty_recent_ansi_snapshot(&core, lines, false).ok()) .unwrap_or_default() } + #[cfg(test)] pub fn recent_unwrapped_text(&self, lines: usize) -> String { + self.recent_unwrapped_text_snapshot(lines).text + } + + pub(crate) fn recent_unwrapped_text_snapshot(&self, lines: usize) -> TerminalReadSnapshot { self.core .lock() .ok() - .and_then(|core| ghostty_recent_text_unwrapped(&core, lines).ok()) + .and_then(|core| ghostty_recent_text_unwrapped_snapshot(&core, lines).ok()) .unwrap_or_default() } pub fn recent_unwrapped_ansi(&self, lines: usize) -> String { + self.recent_unwrapped_ansi_snapshot(lines).text + } + + pub(crate) fn recent_unwrapped_ansi_snapshot(&self, lines: usize) -> TerminalReadSnapshot { self.core .lock() .ok() - .and_then(|core| ghostty_recent_ansi(&core, lines, true).ok()) + .and_then(|core| ghostty_recent_ansi_snapshot(&core, lines, true).ok()) .unwrap_or_default() } @@ -2289,34 +2321,23 @@ fn ghostty_recent_text( core: &GhosttyPaneCore, lines: usize, ) -> Result<String, crate::ghostty::Error> { + ghostty_recent_text_snapshot(core, lines).map(|snapshot| snapshot.text) +} + +fn ghostty_recent_text_snapshot( + core: &GhosttyPaneCore, + lines: usize, +) -> Result<TerminalReadSnapshot, crate::ghostty::Error> { let text = ghostty_recent_text_for_terminal(&core.terminal, lines)?; - #[cfg(windows)] - { - if text.trim().is_empty() { - let fallback = windows_recent_fallback::recent_text(core, lines, false); - if !fallback.trim().is_empty() { - return Ok(fallback); - } - } - } - Ok(text) + Ok(finish_recent_snapshot(core, text, lines, false)) } -fn ghostty_recent_text_unwrapped( +fn ghostty_recent_text_unwrapped_snapshot( core: &GhosttyPaneCore, lines: usize, -) -> Result<String, crate::ghostty::Error> { +) -> Result<TerminalReadSnapshot, crate::ghostty::Error> { let text = ghostty_recent_text_unwrapped_for_terminal(&core.terminal, lines)?; - #[cfg(windows)] - { - if text.trim().is_empty() { - let fallback = windows_recent_fallback::recent_text(core, lines, true); - if !fallback.trim().is_empty() { - return Ok(fallback); - } - } - } - Ok(text) + Ok(finish_recent_snapshot(core, text, lines, true)) } fn ghostty_recent_ansi( @@ -2324,17 +2345,42 @@ fn ghostty_recent_ansi( lines: usize, unwrap: bool, ) -> Result<String, crate::ghostty::Error> { - let ansi = ghostty_recent_ansi_for_terminal(&core.terminal, lines, unwrap)?; + ghostty_recent_ansi_snapshot(core, lines, unwrap).map(|snapshot| snapshot.text) +} + +fn ghostty_recent_ansi_snapshot( + core: &GhosttyPaneCore, + lines: usize, + unwrap: bool, +) -> Result<TerminalReadSnapshot, crate::ghostty::Error> { + let text = ghostty_recent_ansi_for_terminal(&core.terminal, lines, unwrap)?; + Ok(finish_recent_snapshot(core, text, lines, unwrap)) +} + +fn finish_recent_snapshot( + core: &GhosttyPaneCore, + text: String, + lines: usize, + unwrap: bool, +) -> TerminalReadSnapshot { + #[cfg(not(windows))] + let _ = unwrap; #[cfg(windows)] - { - if ansi.trim().is_empty() { - let fallback = windows_recent_fallback::recent_text(core, lines, unwrap); - if !fallback.trim().is_empty() { - return Ok(fallback); - } + if text.trim().is_empty() { + let fallback = windows_recent_fallback::recent_text(core, lines, unwrap); + if !fallback.text.trim().is_empty() { + return fallback; } } - Ok(ansi) + + // Recent read limits are measured in rendered rows, including blank or styled rows. + TerminalReadSnapshot { + text, + truncated: core + .terminal + .total_rows() + .is_ok_and(|total_rows| total_rows > lines), + } } fn ghostty_recent_text_for_terminal( @@ -4342,6 +4388,20 @@ mod tests { } #[test] + fn recent_snapshots_report_omitted_rendered_rows() { + let (tx, _rx) = mpsc::channel(4); + let mut terminal = crate::ghostty::Terminal::new(20, 3, 100).unwrap(); + terminal.write(b"one\r\ntwo\r\nthree\r\nfour"); + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + + assert!(pane.recent_text_snapshot(2).truncated); + assert!(pane.recent_ansi_snapshot(2).truncated); + assert!(pane.recent_unwrapped_text_snapshot(2).truncated); + assert!(pane.recent_unwrapped_ansi_snapshot(2).truncated); + assert!(!pane.recent_text_snapshot(100).truncated); + } + + #[test] fn plain_text_reads_skip_wide_character_spacer_cells() { let (tx, _rx) = mpsc::channel(4); let mut terminal = crate::ghostty::Terminal::new(40, 3, 100).unwrap(); |
