-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Enhance alt-screen scrollback handling and rendering logic #9377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
832b268
1488ffc
450fe99
7ee9375
6d42654
0753275
781b089
b0c0aa8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -284,7 +284,8 @@ impl AltScreenElement { | |
| })); | ||
| } else { | ||
| ctx.dispatch_typed_action(TerminalAction::MaybeClearAltSelect); | ||
| ctx.dispatch_typed_action(TerminalAction::AltMouseAction(mouse_state.set_point(point))); | ||
| let visible_point = Point::new(point.row.saturating_sub(self.grid_history_offset()), point.col); | ||
| ctx.dispatch_typed_action(TerminalAction::AltMouseAction(mouse_state.set_point(visible_point))); | ||
| } | ||
| true | ||
| } | ||
|
|
@@ -307,7 +308,8 @@ impl AltScreenElement { | |
| position: local_position, | ||
| }); | ||
| } else { | ||
| ctx.dispatch_typed_action(TerminalAction::AltMouseAction(mouse_state.set_point(point))); | ||
| let visible_point = Point::new(point.row.saturating_sub(self.grid_history_offset()), point.col); | ||
| ctx.dispatch_typed_action(TerminalAction::AltMouseAction(mouse_state.set_point(visible_point))); | ||
| } | ||
| true | ||
| } | ||
|
|
@@ -359,7 +361,8 @@ impl AltScreenElement { | |
| // see Linear issue at https://linear.app/warpdotdev/issue/CORE-1039/combine-the-mousebutton-and-mouseaction-enums-to-avoid-impossible. | ||
| let mouse_state = | ||
| MouseState::new(MouseButton::Move, MouseAction::Pressed, Default::default()); | ||
| ctx.dispatch_typed_action(TerminalAction::AltMouseAction(mouse_state.set_point(point))); | ||
| let visible_point = Point::new(point.row.saturating_sub(self.grid_history_offset()), point.col); | ||
| ctx.dispatch_typed_action(TerminalAction::AltMouseAction(mouse_state.set_point(visible_point))); | ||
| } | ||
|
|
||
| // Allow the event to continue propagating. | ||
|
|
@@ -401,7 +404,8 @@ impl AltScreenElement { | |
| } | ||
|
|
||
| if !should_intercept_mouse(&self.model.lock(), mouse_state.modifiers().shift, app) { | ||
| ctx.dispatch_typed_action(TerminalAction::AltMouseAction(mouse_state.set_point(point))); | ||
| let visible_point = Point::new(point.row.saturating_sub(self.grid_history_offset()), point.col); | ||
| ctx.dispatch_typed_action(TerminalAction::AltMouseAction(mouse_state.set_point(visible_point))); | ||
| } | ||
|
|
||
| true | ||
|
|
@@ -436,7 +440,8 @@ impl AltScreenElement { | |
| is_mouse_dragged = true; | ||
| } | ||
| if !should_intercept_mouse(&self.model.lock(), mouse_state.modifiers().shift, app) { | ||
| ctx.dispatch_typed_action(TerminalAction::AltMouseAction(mouse_state.set_point(point))); | ||
| let visible_point = Point::new(point.row.saturating_sub(self.grid_history_offset()), point.col); | ||
| ctx.dispatch_typed_action(TerminalAction::AltMouseAction(mouse_state.set_point(visible_point))); | ||
| } | ||
| is_mouse_dragged | ||
| } | ||
|
|
@@ -478,14 +483,15 @@ impl AltScreenElement { | |
| ctx.dispatch_typed_action(TerminalAction::AltScroll { delta }); | ||
| } else { | ||
| let point = self.coord_to_point(local_position); | ||
| let visible_point = Point::new(point.row.saturating_sub(self.grid_history_offset()), point.col); | ||
|
|
||
| ctx.dispatch_typed_action(TerminalAction::AltMouseAction( | ||
| MouseState::new( | ||
| MouseButton::Wheel, | ||
| MouseAction::Scrolled { delta }, | ||
| Default::default(), | ||
| ) | ||
| .set_point(point), | ||
| .set_point(visible_point), | ||
| )); | ||
| } | ||
| true | ||
|
|
@@ -499,20 +505,34 @@ impl AltScreenElement { | |
| ) | ||
| } | ||
|
|
||
| /// Returns the grid's scrollback size (history rows above the visible viewport). | ||
| fn grid_history_offset(&self) -> usize { | ||
| self.model.lock().alt_screen().grid_handler().history_size() | ||
| } | ||
|
|
||
| /// Converts a pixel coordinate to a point in the `AltScreen` coordinate space. | ||
| fn coord_to_point(&self, coord: Vector2F) -> Point { | ||
| let model = self.model.lock(); | ||
| let grid = model.alt_screen().grid_handler(); | ||
| let total_height = grid.total_rows(); | ||
| let size = self.grid_render_params.size_info; | ||
|
|
||
| let column = ((coord.x() - size.padding_x_px.as_f32()) / size.cell_width_px().as_f32()) | ||
| .max(0.) | ||
| .min(grid.columns() as f32 - 1.) as usize; | ||
|
|
||
| let row = (coord.y() / size.cell_height_px().as_f32()) | ||
| .max(0.) | ||
| .min(total_height as f32 - 1.) as usize; | ||
| let history_offset = grid.history_size(); | ||
| let visible_height = self | ||
| .visible_lines | ||
| .expect("should be set after layout") | ||
| .as_f64() as usize; | ||
| // coord.y already includes scroll_top via to_local(), so the clamp | ||
| // upper bound must account for it. | ||
| let max_row = (self.scroll_top.as_f64() as usize) + visible_height.saturating_sub(1); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| let row = (history_offset | ||
| + (coord.y() / size.cell_height_px().as_f32()) | ||
| .max(0.) | ||
| .min(max_row as f32) as usize) | ||
| .min(grid.total_rows().saturating_sub(1)); | ||
| Point::new(row, column) | ||
| } | ||
|
|
||
|
|
@@ -699,6 +719,8 @@ impl Element for AltScreenElement { | |
|
|
||
| let grid = model.alt_screen().grid_handler(); | ||
|
|
||
| let history_offset = grid.history_size(); | ||
|
|
||
| let cell_size = Vector2F::new( | ||
| self.grid_render_params.size_info.cell_width_px().as_f32(), | ||
| self.grid_render_params.size_info.cell_height_px().as_f32(), | ||
|
|
@@ -724,16 +746,24 @@ impl Element for AltScreenElement { | |
| let mut sampler = model.alt_screen().bg_color_sampler.lock(); | ||
| sampler.reset(); | ||
|
|
||
| // Render grid cells. Since the alt screen has no scrollback we can always start at index 0. | ||
| // Render grid cells. The alt screen preserves scrollback for apps that | ||
| // rely on cursor-up overshoot and full-buffer redraws (e.g. Claude Code). | ||
| // Start rendering from the history offset so visible content anchors to | ||
| // the bottom of the buffer rather than the top. | ||
| record_trace_event!("alt_screen_element:paint:preparing_to_render_grid"); | ||
| let start_row = self.scroll_top.as_f64(); | ||
| let start_row = history_offset as f64 + self.scroll_top.as_f64(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 [CRITICAL] |
||
| let end_row = (start_row | ||
| + self | ||
| .visible_lines | ||
| .expect("should be set after layout") | ||
| .as_f64()) | ||
| .min(grid.visible_rows() as f64); | ||
| let adjusted_grid_origin = origin - self.vertical_scroll_pixels(); | ||
| .min(grid.total_rows() as f64); | ||
| // Offset the paint origin upward by the scrollback height so that | ||
| // render_grid's absolute offset_row positioning maps cells to the | ||
| // correct screen positions (the first visible row lands at origin.y). | ||
| let adjusted_grid_origin = origin | ||
| - self.vertical_scroll_pixels() | ||
| - vec2f(0., history_offset as f32 * cell_size.y()); | ||
| let cursor_visible = model.alt_screen().is_mode_set(TermMode::SHOW_CURSOR); | ||
| grid_renderer::render_grid( | ||
| grid, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1107,7 +1107,9 @@ impl TerminalModel { | |
| ) -> Self { | ||
| let alt_screen = AltScreen::new( | ||
| sizes.size, | ||
| 0, /* max_scroll_limit */ | ||
| 1000, /* max_scroll_limit — preserve alt-screen scrollback to prevent content loss | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| * during heavy streaming from apps like Claude Code that rely on cursor-up | ||
| * overshoot and full-buffer redraws (see warpdotdev/Warp#7200, #8089) */ | ||
| event_proxy.clone(), | ||
| obfuscate_secrets, | ||
| ); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GridHandler::history_size()still reports flat-storage rows, but alt-screen scrollback is now stored inGridStorage, so this returns 0 and the render/hit-test offsets never account for preserved history. Usegrid_storage().history_size()here or update theGridHandlerdimension implementation for alt-screen grids.