From e85dfd2659bb179ea0fa0ac32b60fe1f29912d5e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 09:37:09 +0000 Subject: [PATCH] fix(status-bar): refresh cached cursor line on plugin cursor jump (#2301) "Go to LSP Symbol" moves the cursor through the `setBufferCursor` plugin op (`set_buffer_cursor_in_splits`), which updated the cursor position but never refreshed `state.primary_cursor_line_number`. The status bar reads the line from that cache while computing the column live, so right after the jump it reported the pre-jump line ("Ln 30") with the new column ("Col 5") until the next cursor movement recomputed the cache. Refresh the cached line number after the jump, mirroring the canonical `Window::jump_active_cursor_to` path. This fixes every plugin-driven cursor jump (Go to LSP Symbol and any other `setBufferCursor` caller), not just the LSP case. The existing `test_lsp_navigation_jumps_to_precise_column` only asserted the column, which is why the stale line went unnoticed; the new regression test asserts the full `Ln 6, Col 3` immediately after the jump, with no intervening cursor movement. --- crates/fresh-editor/src/app/window/mod.rs | 11 +++++ .../tests/e2e/plugins/lsp_navigation.rs | 42 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/crates/fresh-editor/src/app/window/mod.rs b/crates/fresh-editor/src/app/window/mod.rs index a43015dfce..f9129a6414 100644 --- a/crates/fresh-editor/src/app/window/mod.rs +++ b/crates/fresh-editor/src/app/window/mod.rs @@ -1422,6 +1422,17 @@ impl Window { state.primary_cursor_line_number = crate::model::buffer::LineNumber::Absolute(line); } + // Refresh the cached primary-cursor line number. The status bar + // reads the line from this cache (the column is computed live), + // so a programmatic jump that skips it leaves the status bar + // reporting the pre-jump line until the next cursor movement + // recomputes it (#2301). Mirror the canonical jump path in + // `Window::jump_active_cursor_to`. + let clamped = position.min(state.buffer.len()); + if let Some(pos) = state.buffer.offset_to_position(clamped) { + state.primary_cursor_line_number = + crate::model::buffer::LineNumber::Absolute(pos.line); + } }); } diff --git a/crates/fresh-editor/tests/e2e/plugins/lsp_navigation.rs b/crates/fresh-editor/tests/e2e/plugins/lsp_navigation.rs index 479d8b639e..52166bf36e 100644 --- a/crates/fresh-editor/tests/e2e/plugins/lsp_navigation.rs +++ b/crates/fresh-editor/tests/e2e/plugins/lsp_navigation.rs @@ -272,6 +272,48 @@ fn test_lsp_navigation_jumps_to_precise_column() -> anyhow::Result<()> { Ok(()) } +/// Regression test for #2301: after confirming a "Go to LSP Symbol" jump, +/// the status bar must report the symbol's *line* immediately — not just its +/// column. The jump goes through the plugin `setBufferCursor` op, which moved +/// the cursor but left the cached primary-cursor line number stale, so the +/// status bar showed the pre-jump line (`Ln 1`) until the next cursor move. +/// We assert the full `Ln 6, Col 3` (myMethod is on file line 5 / 1-indexed +/// line 6) right after the jump, with no intervening cursor movement. +#[test] +#[cfg_attr(windows, ignore)] +fn test_lsp_navigation_status_bar_line_updates_after_jump() -> anyhow::Result<()> { + let (mut harness, _temp_dir) = setup_lsp_test()?; + + // Confirm the starting cursor is on line 1 — the line the bug would leave + // stuck in the status bar after the jump. + harness.wait_until(|h| h.screen_to_string().contains("Ln 1, Col 1"))?; + + open_symbol_navigation(&mut harness)?; + + // Default selection is MyClass (index 0); step down to myMethod (index 2). + harness.send_key(KeyCode::Down, KeyModifiers::NONE)?; + harness.send_key(KeyCode::Down, KeyModifiers::NONE)?; + harness.wait_until(|h| { + selected_suggestion_text(h).is_some_and(|t| t.contains("[method] myMethod")) + })?; + + harness.send_key(KeyCode::Enter, KeyModifiers::NONE)?; + harness.process_async_and_render()?; + + // The status bar must show both the new line AND column with no further + // input. Before the fix the line was stale (`Ln 1, Col 3`). + harness.wait_until(|h| h.screen_to_string().contains("Ln 6, Col 3"))?; + let screen = harness.screen_to_string(); + assert!( + screen.contains("Ln 6, Col 3"), + "After jumping to myMethod, status bar should read 'Ln 6, Col 3' \ + immediately (not a stale line). Screen:\n{}", + screen + ); + + Ok(()) +} + /// Preview is non-destructive: browsing the list (and cancelling) never /// moves the cursor — only confirming with Enter does. #[test]