Skip to content

vim: Fix selection motions when trailing newline is selected - #61253

Merged
ChristopherBiscardi merged 3 commits into
zed-industries:mainfrom
lingyaochu:helix_trailing_newline
Jul 20, 2026
Merged

vim: Fix selection motions when trailing newline is selected#61253
ChristopherBiscardi merged 3 commits into
zed-industries:mainfrom
lingyaochu:helix_trailing_newline

Conversation

@lingyaochu

@lingyaochu lingyaochu commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Objective

Closes #41743

When selecting text on the last line, excluding the trailing newline, Vim and Helix modes behave differently from native Vim and Helix when the file has a trailing newline:

  1. We cannot use l (move right) to include the final \n in the selection, while native Vim and Helix allow this.
  2. By default, the rendered cursor should appear on the selected character. When that character is the final \n, however, Zed renders the cursor on the synthetic empty line after it rather than at the end of the preceding line. This differs from native Vim and Helix, as well as from Zed's behavior for newlines elsewhere in the file.
  3. When the final \n is selected, it affects subsequent selection motions, as reported in Line misindentified in Helix select mode when ␤newline is selected #41743.

The cause is several special-case guards for trailing newlines in Visual and Select modes. These guards exist in the cursor rendering logic:

// any vim visual mode (including line mode)
if cursor_offset && !range.is_empty() && !selection.reversed {
if head.column() > 0 {
head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left);
} else if head.row().0 > 0 && head != map.max_point() {
head = map.clip_point(
DisplayPoint::new(
head.row().previous_row(),
map.line_len(head.row().previous_row()),
),
Bias::Left,
);
// updating range.end is a no-op unless you're cursor is

They also exist in the Visual motion logic for Vim mode and the Select motion logic for Helix mode (the Helix implementation was adopted directly from Vim in #43234):

if !selection.reversed
&& !selection.is_empty()
&& !(selection.end.column() == 0 && selection.end == map.max_point())
{
current_head = movement::left(map, selection.end)
}

Finally, they exist in the selection extension logic for Vim and Helix (the Helix logic was also adopted from Vim):

// ensure the current character is included in the selection.
if !selection.reversed {
let next_point = if vim.mode == Mode::VisualBlock {
movement::saturating_right(map, selection.end)
} else {
movement::right(map, selection.end)
};
if !(next_point.column() == 0 && next_point == map.max_point()) {
selection.end = next_point;
}
}

These guards cause trailing-newline selections to behave inconsistently with selections containing newlines elsewhere in the file.

Solution

Remove all the guards mentioned above, including those in cursor rendering, Visual and Select motions, and selection extension. And due to the cursor rendering logic is also changed, an exsisting test is updated.

After removing these guards, I could not reproduce the unexpected behavior described in the existing comments:

// our motions assume the current character is after the cursor,
// but in (forward) visual mode the current character is just
// before the end of the selection.
// If the file ends with a newline (which is common) we don't do this.
// so that if you go to the end of such a file you can use "up" to go
// to the previous line and have it work somewhat as expected.

The only remaining difference is that, when the cursor is on the last line in Visual mode, pressing j moves it to the final \n rather than to the synthetic empty line after it. In comparison, native Vim and Helix do nothing in this case.

Testing

  • Built and tested locally.
  • Added new GPUI tests.

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content adheres to Zed's UI standards (UX/UI and icon guidelines)
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Release Notes:

  • Improved Vim Visual and Helix Select modes when selections include a trailing newline.

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Jul 18, 2026
@zed-community-bot zed-community-bot Bot added community champion Issues filed by our amazing community champions! 🫶 guild Pull requests by someone in Zed Guild. NOTE: the label application is automated via github actions labels Jul 18, 2026
@lingyaochu lingyaochu changed the title vim: Fix selection motion when trailing newline is covered vim: Fix selection motions when trailing newline is selected Jul 18, 2026

@ChristopherBiscardi ChristopherBiscardi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@ChristopherBiscardi
ChristopherBiscardi added this pull request to the merge queue Jul 20, 2026
Merged via the queue into zed-industries:main with commit d735d22 Jul 20, 2026
63 checks passed
@ChristopherBiscardi ChristopherBiscardi self-assigned this Jul 20, 2026
@lingyaochu
lingyaochu deleted the helix_trailing_newline branch July 20, 2026 04:54
jolutz pushed a commit to jolutz/zed that referenced this pull request Aug 8, 2026
…ustries#61253)

# Objective

Closes zed-industries#41743

When selecting text on the last line, excluding the trailing newline,
Vim and Helix modes behave differently from native Vim and Helix when
the file has a trailing newline:
1. We cannot use `l` (move right) to include the final `\n` in the
selection, while native Vim and Helix allow this.
2. By default, the rendered cursor should appear on the selected
character. When that character is the final `\n`, however, Zed renders
the cursor on the synthetic empty line after it rather than at the end
of the preceding line. This differs from native Vim and Helix, as well
as from Zed's behavior for newlines elsewhere in the file.
3. When the final `\n` is selected, it affects subsequent selection
motions, as reported in zed-industries#41743.

The cause is several special-case guards for trailing newlines in Visual
and Select modes. These guards exist in the cursor rendering logic:

https://github.com/zed-industries/zed/blob/1d2ca5ba580c616ec8404ec37ee30c6f69022e99/crates/editor/src/element.rs#L201-L213

They also exist in the Visual motion logic for Vim mode and the Select
motion logic for Helix mode (the Helix implementation was adopted
directly from Vim in zed-industries#43234):

https://github.com/zed-industries/zed/blob/1d2ca5ba580c616ec8404ec37ee30c6f69022e99/crates/vim/src/visual.rs#L254-L259

Finally, they exist in the selection extension logic for Vim and Helix
(the Helix logic was also adopted from Vim):

https://github.com/zed-industries/zed/blob/1d2ca5ba580c616ec8404ec37ee30c6f69022e99/crates/vim/src/visual.rs#L273-L284

These guards cause trailing-newline selections to behave inconsistently
with selections containing newlines elsewhere in the file.

## Solution

Remove all the guards mentioned above, including those in cursor
rendering, Visual and Select motions, and selection extension. And due
to the cursor rendering logic is also changed, an exsisting test is
updated.

After removing these guards, I could not reproduce the unexpected
behavior described in the existing comments:

https://github.com/zed-industries/zed/blob/1d2ca5ba580c616ec8404ec37ee30c6f69022e99/crates/vim/src/visual.rs#L247-L253

The only remaining difference is that, when the cursor is on the last
line in Visual mode, pressing `j` moves it to the final `\n` rather than
to the synthetic empty line after it. In comparison, native Vim and
Helix do nothing in this case.

## Testing

- Built and tested locally.
- Added new GPUI tests.

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable


Release Notes:

- Improved Vim Visual and Helix Select modes when selections include a
trailing newline.
playdohface pushed a commit to playdohface/zed that referenced this pull request Aug 29, 2026
…ustries#61253)

# Objective

Closes zed-industries#41743

When selecting text on the last line, excluding the trailing newline,
Vim and Helix modes behave differently from native Vim and Helix when
the file has a trailing newline:
1. We cannot use `l` (move right) to include the final `\n` in the
selection, while native Vim and Helix allow this.
2. By default, the rendered cursor should appear on the selected
character. When that character is the final `\n`, however, Zed renders
the cursor on the synthetic empty line after it rather than at the end
of the preceding line. This differs from native Vim and Helix, as well
as from Zed's behavior for newlines elsewhere in the file.
3. When the final `\n` is selected, it affects subsequent selection
motions, as reported in zed-industries#41743.

The cause is several special-case guards for trailing newlines in Visual
and Select modes. These guards exist in the cursor rendering logic:

https://github.com/zed-industries/zed/blob/c9e8e611dbc279afa0914d28c4d37ad07f38c03b/crates/editor/src/element.rs#L201-L213

They also exist in the Visual motion logic for Vim mode and the Select
motion logic for Helix mode (the Helix implementation was adopted
directly from Vim in zed-industries#43234):

https://github.com/zed-industries/zed/blob/c9e8e611dbc279afa0914d28c4d37ad07f38c03b/crates/vim/src/visual.rs#L254-L259

Finally, they exist in the selection extension logic for Vim and Helix
(the Helix logic was also adopted from Vim):

https://github.com/zed-industries/zed/blob/c9e8e611dbc279afa0914d28c4d37ad07f38c03b/crates/vim/src/visual.rs#L273-L284

These guards cause trailing-newline selections to behave inconsistently
with selections containing newlines elsewhere in the file.

## Solution

Remove all the guards mentioned above, including those in cursor
rendering, Visual and Select motions, and selection extension. And due
to the cursor rendering logic is also changed, an exsisting test is
updated.

After removing these guards, I could not reproduce the unexpected
behavior described in the existing comments:

https://github.com/zed-industries/zed/blob/c9e8e611dbc279afa0914d28c4d37ad07f38c03b/crates/vim/src/visual.rs#L247-L253

The only remaining difference is that, when the cursor is on the last
line in Visual mode, pressing `j` moves it to the final `\n` rather than
to the synthetic empty line after it. In comparison, native Vim and
Helix do nothing in this case.

## Testing

- Built and tested locally.
- Added new GPUI tests.

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable


Release Notes:

- Improved Vim Visual and Helix Select modes when selections include a
trailing newline.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement community champion Issues filed by our amazing community champions! 🫶 guild Pull requests by someone in Zed Guild. NOTE: the label application is automated via github actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Line misindentified in Helix select mode when ␤newline is selected

2 participants