From aea908a7cf72bb0d2ff94fe4c5079f780e69318c Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Sun, 5 Apr 2026 21:03:17 +0300 Subject: [PATCH] fix: respect synchronized terminal output batches --- src/app/input.rs | 33 ++++++++++++++++++++++++++++----- src/ghostty/mod.rs | 3 ++- src/pane.rs | 23 ++++++++++++++++++++++- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/app/input.rs b/src/app/input.rs index 900ec214..fdb668c6 100644 --- a/src/app/input.rs +++ b/src/app/input.rs @@ -3284,12 +3284,18 @@ mod tests { app.state.workspaces = vec![Workspace::test_new("a"), Workspace::test_new("b")]; app.state.active = Some(0); app.state.selected = 0; + crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20)); + let target_row = app.state.view.workspace_card_areas[1].rect.y; - app.handle_mouse(mouse(MouseEventKind::Down(MouseButton::Left), 2, 2)); + app.handle_mouse(mouse( + MouseEventKind::Down(MouseButton::Left), + 2, + target_row, + )); assert_eq!(app.state.active, Some(0)); assert!(app.state.workspace_press.is_some()); - app.handle_mouse(mouse(MouseEventKind::Up(MouseButton::Left), 2, 2)); + app.handle_mouse(mouse(MouseEventKind::Up(MouseButton::Left), 2, target_row)); assert_eq!(app.state.active, Some(1)); assert_eq!(app.state.selected, 1); assert!(app.state.workspace_press.is_none()); @@ -3307,9 +3313,25 @@ mod tests { let selected_id = app.state.workspaces[2].id.clone(); app.state.active = Some(1); app.state.selected = 2; + crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20)); + let source_row = app.state.view.workspace_card_areas[1].rect.y; + let target_row = crate::ui::workspace_drop_indicator_row( + &app.state.view.workspace_card_areas, + app.state.workspace_list_rect(), + 0, + ) + .unwrap(); - app.handle_mouse(mouse(MouseEventKind::Down(MouseButton::Left), 2, 2)); - app.handle_mouse(mouse(MouseEventKind::Drag(MouseButton::Left), 2, 0)); + app.handle_mouse(mouse( + MouseEventKind::Down(MouseButton::Left), + 2, + source_row, + )); + app.handle_mouse(mouse( + MouseEventKind::Drag(MouseButton::Left), + 2, + target_row, + )); assert!(matches!( app.state.drag.as_ref().map(|drag| &drag.target), Some(DragTarget::WorkspaceReorder { @@ -3317,7 +3339,7 @@ mod tests { insert_idx: Some(0), }) )); - app.handle_mouse(mouse(MouseEventKind::Up(MouseButton::Left), 2, 0)); + app.handle_mouse(mouse(MouseEventKind::Up(MouseButton::Left), 2, target_row)); let names: Vec<_> = app .state @@ -3336,6 +3358,7 @@ mod tests { fn top_drop_slot_is_distinct_from_gap_below_first_workspace() { let mut app = app_for_mouse_test(); app.state.workspaces = vec![Workspace::test_new("a"), Workspace::test_new("b")]; + crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20)); assert_eq!(app.state.workspace_drop_index_at_row(0), Some(0)); assert_eq!(app.state.workspace_drop_index_at_row(1), Some(1)); diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs index e2bd409c..a4846db4 100644 --- a/src/ghostty/mod.rs +++ b/src/ghostty/mod.rs @@ -121,10 +121,11 @@ pub const MOUSE_BUTTON_WHEEL_RIGHT: ffi::GhosttyMouseButton = pub const MODE_APPLICATION_CURSOR_KEYS: u16 = 1; pub const MODE_FOCUS_EVENT: u16 = 1004; -pub const MODE_BRACKETED_PASTE: u16 = 2004; pub const MODE_MOUSE_UTF8: u16 = 1005; pub const MODE_MOUSE_SGR: u16 = 1006; pub const MODE_MOUSE_ALTERNATE_SCROLL: u16 = 1007; +pub const MODE_BRACKETED_PASTE: u16 = 2004; +pub const MODE_SYNCHRONIZED_OUTPUT: u16 = 2026; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ActiveScreen { diff --git a/src/pane.rs b/src/pane.rs index 02bf5033..314554e1 100644 --- a/src/pane.rs +++ b/src/pane.rs @@ -449,9 +449,13 @@ impl GhosttyPaneTerminal { }; core.terminal.write(bytes); + let synchronized_output = core + .terminal + .mode_get(crate::ghostty::MODE_SYNCHRONIZED_OUTPUT) + .unwrap_or(false); let _ = response_writer; ProcessBytesResult { - request_render: true, + request_render: !synchronized_output, } } @@ -1964,6 +1968,23 @@ mod tests { ); } + #[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(); + let pane_terminal = GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap(); + let pane_id = PaneId::from_raw(1); + + let begin = pane_terminal.process_pty_bytes(pane_id, b"\x1b[?2026h", &tx); + assert!(!begin.request_render); + + let body = pane_terminal.process_pty_bytes(pane_id, b"hello", &tx); + assert!(!body.request_render); + + let end = pane_terminal.process_pty_bytes(pane_id, b"\x1b[?2026l", &tx); + assert!(end.request_render); + } + #[tokio::test] async fn focus_events_are_forwarded_when_enabled() { let (tx, mut rx) = mpsc::channel(4);