aboutsummaryrefslogtreecommitdiff
path: root/src/pane/terminal.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-24 04:00:13 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-24 04:00:13 +0300
commitc0fb777ed7c7950c6a2f397113c1842c2e679306 (patch)
tree56a1b58314e292433946c1eed5c025abb832595f /src/pane/terminal.rs
parente608a75159fc5cca70485d83ffb8ee681c9da385 (diff)
fix: report truncated pane reads
refs #1717
Diffstat (limited to 'src/pane/terminal.rs')
-rw-r--r--src/pane/terminal.rs136
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();