fix: hide pane cursor while scrolled back

This commit is contained in:
Ogulcan Celik 2026-05-11 18:25:44 +03:00
parent 733b109e20
commit e12c7a2cef
4 changed files with 44 additions and 3 deletions

View File

@ -1962,6 +1962,40 @@ mod tests {
);
}
#[tokio::test]
async fn virtual_render_hides_focused_pane_cursor_while_scrolled_back() {
let mut state = AppState::test_new();
let mut ws = crate::workspace::Workspace::test_new("test");
let pane_id = ws.tabs[0].root_pane;
let mut bytes = Vec::new();
for line in 0..80 {
bytes.extend_from_slice(format!("line {line:02}\r\n").as_bytes());
}
let runtime = crate::pane::PaneRuntime::test_with_scrollback_bytes(20, 5, 4096, &bytes);
ws.tabs[0].runtimes.insert(pane_id, runtime);
state.workspaces = vec![ws];
state.active = Some(0);
state.selected = 0;
state.mode = crate::app::Mode::Terminal;
let area = Rect::new(0, 0, 80, 24);
let _ = crate::server::render_stream::render_virtual(&mut state, area, true);
let runtime = state.workspaces[0]
.runtime(pane_id)
.expect("pane runtime after initial render");
runtime.scroll_up(6);
assert!(crate::ui::pane_is_scrolled_back(runtime));
let (_buffer, cursor) =
crate::server::render_stream::render_virtual(&mut state, area, true);
assert!(
cursor.as_ref().is_none_or(|cursor| !cursor.visible),
"cursor: {cursor:?}"
);
}
#[test]
fn latest_active_client_drives_shared_size_theme_and_fallback() {
let mut server = test_headless_server();

View File

@ -257,11 +257,11 @@ fn focused_terminal_cursor(app_state: &AppState) -> Option<CursorState> {
.pane_infos
.iter()
.find(|info| info.is_focused)?;
let rt = ws.runtimes.get(&info.id)?;
let rt = ws.runtime(info.id)?;
let cursor = rt.cursor_state(info.inner_rect, true)?;
Some(CursorState {
x: cursor.x,
y: cursor.y,
visible: cursor.visible,
visible: cursor.visible && !crate::ui::pane_is_scrolled_back(rt),
})
}

View File

@ -63,6 +63,7 @@ pub(crate) use self::{
mobile_switcher_areas, mobile_switcher_max_scroll, mobile_switcher_target_at,
mobile_switcher_workspace_doc_range, MobileSwitcherTarget,
},
panes::pane_is_scrolled_back,
tabs::compute_tab_bar_view,
widgets::{centered_popup_rect, modal_stack_areas},
};

View File

@ -13,6 +13,11 @@ use crate::app::{AppState, Mode};
use crate::layout::PaneInfo;
use crate::pane::PaneRuntime;
pub(crate) fn pane_is_scrolled_back(rt: &PaneRuntime) -> bool {
rt.scroll_metrics()
.is_some_and(|metrics| metrics.offset_from_bottom > 0)
}
fn stable_terminal_inner_rect(pane_inner: Rect) -> Rect {
if pane_inner.width <= 4 {
return pane_inner;
@ -176,7 +181,8 @@ pub(super) fn render_panes(app: &AppState, frame: &mut Frame, area: Rect) {
frame.render_widget(block, info.rect);
}
rt.render(frame, info.inner_rect, info.is_focused && terminal_active);
let show_cursor = info.is_focused && terminal_active && !pane_is_scrolled_back(rt);
rt.render(frame, info.inner_rect, show_cursor);
render_pane_scrollbar(app, frame, info, rt);
let should_dim = !info.is_focused && multi_pane && !terminal_active;