diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index bf5b97607b..2bbda6647a 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -4637,7 +4637,8 @@ impl AcpThread { } if let Some(_status) = self.pending_terminal_exit.remove(&terminal_id) { - entity.update(cx, |_term, cx| { + entity.update(cx, |term, cx| { + term.inner().update(cx, |inner, _| inner.shrink_to_used()); cx.notify(); }); } @@ -4673,7 +4674,8 @@ impl AcpThread { status, } => { if let Some(entity) = self.terminals.get(&terminal_id) { - entity.update(cx, |_term, cx| { + entity.update(cx, |term, cx| { + term.inner().update(cx, |inner, _| inner.shrink_to_used()); cx.notify(); }); } else { @@ -5102,6 +5104,83 @@ mod tests { ); } + #[gpui::test] + async fn test_terminal_exit_preserves_visible_scrollback(cx: &mut gpui::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + let project = Project::test(fs, [], cx).await; + let connection = Rc::new(FakeAgentConnection::new()); + let thread = cx + .update(|cx| { + connection.new_session( + project, + PathList::new(&[std::path::Path::new(path!("/test"))]), + cx, + ) + }) + .await + .unwrap(); + + let terminal_id = acp::TerminalId::new(uuid::Uuid::new_v4().to_string()); + let lower = cx.new(|cx| { + let builder = ::terminal::TerminalBuilder::new_display_only( + ::terminal::terminal_settings::CursorShape::default(), + ::terminal::terminal_settings::AlternateScroll::On, + None, + 0, + cx.background_executor(), + PathStyle::local(), + ); + builder.subscribe(cx) + }); + + thread.update(cx, |thread, cx| { + thread.on_terminal_provider_event( + TerminalProviderEvent::Created { + terminal_id: terminal_id.clone(), + label: "Buffered Test".to_string(), + cwd: None, + output_byte_limit: None, + terminal: lower.clone(), + }, + cx, + ); + }); + + let mut output = String::new(); + for line in 0..15_000 { + output.push_str(&format!("line {line}\n")); + } + + thread.update(cx, |thread, cx| { + thread.on_terminal_provider_event( + TerminalProviderEvent::Output { + terminal_id: terminal_id.clone(), + data: output.into_bytes(), + }, + cx, + ); + thread.on_terminal_provider_event( + TerminalProviderEvent::Exit { + terminal_id: terminal_id.clone(), + status: acp::TerminalExitStatus::new().exit_code(0), + }, + cx, + ); + }); + + let content = thread.read_with(cx, |thread, cx| { + let term = thread.terminal(terminal_id.clone()).unwrap(); + term.read_with(cx, |term, cx| term.inner().read(cx).get_content()) + }); + + assert!( + content.contains("line 14999"), + "expected output to remain visible after terminal exit, got: {content}" + ); + } + #[gpui::test] async fn test_terminal_output_and_exit_buffered_before_created(cx: &mut gpui::TestAppContext) { init_test(cx); diff --git a/crates/terminal/src/alacritty.rs b/crates/terminal/src/alacritty.rs index 99b6eadf2f..5766d8b8fd 100644 --- a/crates/terminal/src/alacritty.rs +++ b/crates/terminal/src/alacritty.rs @@ -800,6 +800,10 @@ pub(super) fn clear_saved_screen(term: &mut Term) { } } +pub(super) fn shrink_to_used(term: &mut Term) { + term.grid_mut().truncate(); +} + pub(super) fn make_content(term: &Term, last_content: &Content) -> Content { let content = term.renderable_content(); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index b3b6c7aff8..9fbfd575de 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -67,9 +67,9 @@ use crate::alacritty::{ display_only_term_config, find_from_terminal_point, full_content_range, last_non_empty_lines, make_content, new_term, open_pty, pty_options, pty_term_config, resize, screen_lines, scroll_display, scroll_to_point, search_matches, selection_text, set_default_cursor_style, - set_selection as set_term_selection, spawn_event_loop, toggle_vi_mode as toggle_term_vi_mode, - total_lines, update_selection as update_term_selection, update_selection_to_vi_cursor, - update_vi_cursor_for_scroll, vi_goto_point, vi_motion, + set_selection as set_term_selection, shrink_to_used, spawn_event_loop, + toggle_vi_mode as toggle_term_vi_mode, total_lines, update_selection as update_term_selection, + update_selection_to_vi_cursor, update_vi_cursor_for_scroll, vi_goto_point, vi_motion, }; use crate::mappings::colors::to_vte_rgb; use crate::mappings::keys::to_esc_str; @@ -1888,6 +1888,10 @@ impl Terminal { self.events.push_back(InternalEvent::Clear) } + pub fn shrink_to_used(&mut self) { + shrink_to_used(&mut self.term.lock()); + } + pub fn scroll_line_up(&mut self) { self.events .push_back(InternalEvent::Scroll(Scroll::Delta(1)));