vim: Fix cursor jumping past empty lines with inlay hints in visual mode (#35757)
**Summary**
Fixes #29134 - Visual mode cursor incorrectly jumps past empty lines
that contain inlay hints (type hints).
**Problem**
When in VIM visual mode, pressing j to move down from a longer line to
an empty line that contains an inlay hint would cause the cursor to skip
the empty line entirely and jump to the next line. This only occurred
when moving down (not up) and only in visual mode.
**Root Cause**
The issue was introduced by commit f9ee28db5e
which added bias-based
navigation for handling multi-line inlay hints. When using Bias::Right
while moving down, the clipping logic would place the cursor past the
inlay hint, causing it to jump to the next line.
**Solution**
Added logic in up_down_buffer_rows to detect when clipping would place
the cursor within an inlay hint position. When detected, it uses the
buffer column position instead of the display column to avoid jumping
past the hint.
**Testing**
- Added comprehensive test case
test_visual_mode_with_inlay_hints_on_empty_line that reproduces the
exact scenario
- Manually verified the fix with the reproduction case from the issue
- All 356 tests pass with `cargo test -p vim`
**Release Notes:**
- Fixed VIM visual mode cursor jumping past empty lines with type hints
when navigating down
This commit is contained in:
parent
f5fd4ac670
commit
852439452c
1 changed files with 92 additions and 4 deletions
|
@ -1610,10 +1610,20 @@ fn up_down_buffer_rows(
|
|||
map.line_len(begin_folded_line.row())
|
||||
};
|
||||
|
||||
(
|
||||
map.clip_point(DisplayPoint::new(begin_folded_line.row(), new_col), bias),
|
||||
goal,
|
||||
)
|
||||
let point = DisplayPoint::new(begin_folded_line.row(), new_col);
|
||||
let mut clipped_point = map.clip_point(point, bias);
|
||||
|
||||
// When navigating vertically in vim mode with inlay hints present,
|
||||
// we need to handle the case where clipping moves us to a different row.
|
||||
// This can happen when moving down (Bias::Right) and hitting an inlay hint.
|
||||
// Re-clip with opposite bias to stay on the intended line.
|
||||
//
|
||||
// See: https://github.com/zed-industries/zed/issues/29134
|
||||
if clipped_point.row() > point.row() {
|
||||
clipped_point = map.clip_point(point, Bias::Left);
|
||||
}
|
||||
|
||||
(clipped_point, goal)
|
||||
}
|
||||
|
||||
fn down_display(
|
||||
|
@ -3842,6 +3852,84 @@ mod test {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_visual_mode_with_inlay_hints_on_empty_line(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
// Test the exact scenario from issue #29134
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
fn main() {
|
||||
let this_is_a_long_name = Vec::<u32>::new();
|
||||
let new_oneˇ = this_is_a_long_name
|
||||
.iter()
|
||||
.map(|i| i + 1)
|
||||
.map(|i| i * 2)
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
|
||||
// Add type hint inlay on the empty line (line 3, after "this_is_a_long_name")
|
||||
cx.update_editor(|editor, _window, cx| {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
// The empty line is at line 3 (0-indexed)
|
||||
let line_start = snapshot.anchor_after(Point::new(3, 0));
|
||||
let inlay_text = ": Vec<u32>";
|
||||
let inlay = Inlay::edit_prediction(1, line_start, inlay_text);
|
||||
editor.splice_inlays(&[], vec![inlay], cx);
|
||||
});
|
||||
|
||||
// Enter visual mode
|
||||
cx.simulate_keystrokes("v");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
fn main() {
|
||||
let this_is_a_long_name = Vec::<u32>::new();
|
||||
let new_one« ˇ»= this_is_a_long_name
|
||||
.iter()
|
||||
.map(|i| i + 1)
|
||||
.map(|i| i * 2)
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
"},
|
||||
Mode::Visual,
|
||||
);
|
||||
|
||||
// Move down - should go to the beginning of line 4, not skip to line 5
|
||||
cx.simulate_keystrokes("j");
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
fn main() {
|
||||
let this_is_a_long_name = Vec::<u32>::new();
|
||||
let new_one« = this_is_a_long_name
|
||||
ˇ» .iter()
|
||||
.map(|i| i + 1)
|
||||
.map(|i| i * 2)
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
"},
|
||||
Mode::Visual,
|
||||
);
|
||||
|
||||
// Test with multiple movements
|
||||
cx.set_state("let aˇ = 1;\nlet b = 2;\n\nlet c = 3;", Mode::Normal);
|
||||
|
||||
// Add type hint on the empty line
|
||||
cx.update_editor(|editor, _window, cx| {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let empty_line_start = snapshot.anchor_after(Point::new(2, 0));
|
||||
let inlay_text = ": i32";
|
||||
let inlay = Inlay::edit_prediction(2, empty_line_start, inlay_text);
|
||||
editor.splice_inlays(&[], vec![inlay], cx);
|
||||
});
|
||||
|
||||
// Enter visual mode and move down twice
|
||||
cx.simulate_keystrokes("v j j");
|
||||
cx.assert_state("let a« = 1;\nlet b = 2;\n\nˇ»let c = 3;", Mode::Visual);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_go_to_percentage(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue