aboutsummaryrefslogtreecommitdiff
path: root/src/pane/terminal.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-17 03:26:33 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-17 03:26:33 +0300
commit8d2af4e046c2a95dfc54aafd6f88da12389d76e8 (patch)
tree5643a1ec2f04b150d465817f2896ada4f2fbc46b /src/pane/terminal.rs
parent6f9ac7c108e22510e1b27e3242477fac660fa4cb (diff)
perf: adopt libghostty integration improvements
refs #276
Diffstat (limited to 'src/pane/terminal.rs')
-rw-r--r--src/pane/terminal.rs110
1 files changed, 83 insertions, 27 deletions
diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs
index cf7a82f4..8e33b1b3 100644
--- a/src/pane/terminal.rs
+++ b/src/pane/terminal.rs
@@ -28,10 +28,10 @@ use super::{
kitty_keyboard::KittyKeyboardTracker,
osc::{
contains_scrollback_clear_sequence, current_transient_default_color_owner,
- maybe_filter_primary_screen_scrollback_clear, restore_host_terminal_theme_if_needed,
- write_host_terminal_theme_selective, AgentOscStateTracker, CwdOscTracker,
- DefaultColorEvent, DefaultColorEventTracker, DefaultColorOscTracker, DefaultColorQuery,
- DefaultColorTrackedEvent, Osc52Forwarder, OscDebugTracker,
+ maybe_filter_primary_screen_scrollback_clear, parse_reported_cwd,
+ restore_host_terminal_theme_if_needed, write_host_terminal_theme_selective,
+ AgentOscStateTracker, DefaultColorEvent, DefaultColorEventTracker, DefaultColorOscTracker,
+ DefaultColorQuery, DefaultColorTrackedEvent, Osc52Forwarder, OscDebugTracker,
},
xtgettcap::{XtgettcapQueryTracker, XtgettcapResponse},
};
@@ -162,7 +162,6 @@ pub(crate) struct GhosttyPaneCore {
pub child_default_foreground_changed: bool,
pub child_default_background_changed: bool,
pub osc52_forwarder: Osc52Forwarder,
- pub cwd_osc_tracker: CwdOscTracker,
pub osc_debug_tracker: OscDebugTracker,
pub agent_osc_state: AgentOscStateTracker,
pub xtgettcap_query_tracker: XtgettcapQueryTracker,
@@ -917,7 +916,6 @@ impl GhosttyPaneTerminal {
child_default_foreground_changed: false,
child_default_background_changed: false,
osc52_forwarder: Osc52Forwarder::default(),
- cwd_osc_tracker: CwdOscTracker::default(),
osc_debug_tracker: OscDebugTracker::default(),
agent_osc_state: AgentOscStateTracker::default(),
xtgettcap_query_tracker: XtgettcapQueryTracker::default(),
@@ -1050,6 +1048,7 @@ impl GhosttyPaneTerminal {
};
};
+ let _ = core.terminal.take_pwd_changes();
let default_color_observation = core.default_color_tracker.observe(bytes);
if shell_pid > 0 && default_color_observation {
if let Some(owner_pgid) = current_transient_default_color_owner(shell_pid) {
@@ -1063,8 +1062,6 @@ impl GhosttyPaneTerminal {
core.osc52_forwarder.observe(bytes);
let clipboard_writes = core.osc52_forwarder.drain_pending();
- core.cwd_osc_tracker.observe(bytes);
- let reported_cwd = core.cwd_osc_tracker.drain_latest();
core.osc_debug_tracker.observe(bytes);
for event in core.osc_debug_tracker.drain_pending() {
debug!(
@@ -1119,6 +1116,12 @@ impl GhosttyPaneTerminal {
xtgettcap_responses,
&mut terminal_responses,
);
+ let reported_cwd = core
+ .terminal
+ .take_pwd_changes()
+ .into_iter()
+ .filter_map(|value| parse_reported_cwd(&value))
+ .next_back();
#[cfg(windows)]
windows_recent_fallback::update(&mut core);
crate::render_prof::duration_since("pty.ghostty_write", write_started);
@@ -1406,7 +1409,7 @@ impl GhosttyPaneTerminal {
core.terminal.write(ansi.as_bytes());
}
}
- ghostty_restore_scroll_offset_from_bottom(&mut core.terminal, offset_from_bottom);
+ ghostty_set_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
@@ -1444,10 +1447,7 @@ impl GhosttyPaneTerminal {
pub fn set_scroll_offset_from_bottom(&self, lines: usize) {
if let Ok(mut core) = self.core.lock() {
- core.terminal.scroll_viewport_bottom();
- if lines > 0 {
- core.terminal.scroll_viewport_delta(-(lines as isize));
- }
+ ghostty_set_scroll_offset_from_bottom(&mut core.terminal, lines);
}
}
@@ -2381,22 +2381,16 @@ fn ghostty_recent_read_range(
Ok(Some((start, end, cols)))
}
-fn ghostty_restore_scroll_offset_from_bottom(
+fn ghostty_set_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 {
+ terminal.scroll_viewport_bottom();
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);
- }
+ terminal.scroll_viewport_row(max_offset.saturating_sub(offset_from_bottom));
}
fn ghostty_extract_selection(
@@ -3188,6 +3182,52 @@ mod tests {
Bytes::from(format!("\x1b]{command};rgb:{r:04x}/{g:04x}/{b:04x}\x1b\\"))
}
+ #[test]
+ fn process_pty_bytes_reports_latest_libghostty_pwd_callback() {
+ let (tx, _rx) = mpsc::channel(4);
+ let terminal = crate::ghostty::Terminal::new(80, 24, 100).unwrap();
+ let pane = GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap();
+ let pane_id = PaneId::from_raw(1);
+
+ let partial = pane.process_pty_bytes(pane_id, 0, b"\x1b]7;file:///tmp/herdr%20", &tx);
+ assert_eq!(partial.reported_cwd, None);
+
+ let completed = pane.process_pty_bytes(pane_id, 0, b"repo\x07", &tx);
+ #[cfg(not(windows))]
+ assert_eq!(
+ completed.reported_cwd,
+ Some(std::path::PathBuf::from("/tmp/herdr repo"))
+ );
+ #[cfg(windows)]
+ assert_eq!(
+ completed.reported_cwd,
+ Some(std::path::PathBuf::from("\\tmp\\herdr repo"))
+ );
+
+ let latest = pane.process_pty_bytes(
+ pane_id,
+ 0,
+ b"\x1b]9;9;/tmp/conemu\x1b\\\x1b]1337;CurrentDir=/tmp/iterm2\x1b\\",
+ &tx,
+ );
+ assert_eq!(
+ latest.reported_cwd,
+ Some(std::path::PathBuf::from("/tmp/iterm2"))
+ );
+ }
+
+ #[test]
+ fn seeded_history_pwd_does_not_leak_into_live_output() {
+ let (tx, _rx) = mpsc::channel(4);
+ let terminal = crate::ghostty::Terminal::new(80, 24, 100).unwrap();
+ let pane = GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap();
+ pane.seed_history_ansi("\x1b]7;file:///tmp/restored\x07");
+
+ let result = pane.process_pty_bytes(PaneId::from_raw(1), 0, b"live output", &tx);
+
+ assert_eq!(result.reported_cwd, None);
+ }
+
#[cfg(windows)]
#[test]
fn windows_powershell_prompt_cwd_uses_latest_default_prompt_path() {
@@ -4125,7 +4165,7 @@ mod tests {
}
#[test]
- fn pane_scrollback_controls_reach_top_without_ui_interference() {
+ fn pane_scrollback_controls_round_trip_and_clamp_without_ui_interference() {
let (tx, _rx) = mpsc::channel(4);
let mut terminal = crate::ghostty::Terminal::new(80, 3, 100).unwrap();
write_numbered_lines(&mut terminal, 1000);
@@ -4135,10 +4175,20 @@ mod tests {
assert!(before.max_offset_from_bottom > 0);
assert_eq!(before.offset_from_bottom, 0);
- pane.set_scroll_offset_from_bottom(before.max_offset_from_bottom);
+ for offset in [
+ 0,
+ before.max_offset_from_bottom / 2,
+ before.max_offset_from_bottom,
+ usize::MAX,
+ ] {
+ pane.set_scroll_offset_from_bottom(offset);
+ let after = pane.scroll_metrics().expect("scroll metrics after scroll");
+ assert_eq!(
+ after.offset_from_bottom,
+ offset.min(after.max_offset_from_bottom)
+ );
+ }
- let after = pane.scroll_metrics().expect("scroll metrics after scroll");
- assert_eq!(after.offset_from_bottom, after.max_offset_from_bottom);
assert!(pane.visible_text().contains("000000"));
}
@@ -4270,11 +4320,17 @@ mod tests {
assert!(!pane.visible_text().trim().is_empty());
for (rows, cols) in [(4, 10), (4, 7), (6, 18), (3, 9), (5, 12)] {
+ let before_resize = pane.scroll_metrics().expect("scroll metrics before resize");
pane.resize(rows, cols, 0, 0);
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_eq!(
+ metrics.offset_from_bottom,
+ before_resize
+ .offset_from_bottom
+ .min(metrics.max_offset_from_bottom)
+ );
assert!(
metrics.offset_from_bottom > 0,
"resize should preserve a scrolled viewport instead of jumping to bottom"