Skip to content

vim: Fix Helix mode subword motions to select traversed text#45760

Merged
dinocosta merged 13 commits intozed-industries:mainfrom
maxmalkin:fix/helix-subword-select
Mar 17, 2026
Merged

vim: Fix Helix mode subword motions to select traversed text#45760
dinocosta merged 13 commits intozed-industries:mainfrom
maxmalkin:fix/helix-subword-select

Conversation

@maxmalkin
Copy link
Copy Markdown
Contributor

@maxmalkin maxmalkin commented Dec 28, 2025

Closes #41767

Release Notes:
In Helix mode, word motions select the text they traverse, but subword motions were only moving the cursor without selecting. This was because helix_move_cursor had explicit handling for word motions using helix_find_range_forward and helix_find_range_backward, but subword motions fell back to helix_move_and_collapse handler which collapses the selection.

Changes and Tests:

  • Added is_subword_boundary_right and is_subword_boundary_left functions for subword-specific boundary detection
  • Added explicit match cases for all four subword motions in helix_move_cursor
  • Added test test_subword_motions

Release Notes:

  • Fix subword motions in Helix mode to select traversed text

@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Dec 28, 2025
@dinocosta dinocosta assigned kubkon and unassigned dinocosta Jan 5, 2026
@ConradIrwin ConradIrwin enabled auto-merge (squash) February 20, 2026 15:52
@ConradIrwin
Copy link
Copy Markdown
Member

Thanks for this!

Copy link
Copy Markdown
Member

@dinocosta dinocosta left a comment

Choose a reason for hiding this comment

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

Hey @maxmalkin, thanks for tackling this!

I’m not entirely sure how, or whether, this is actually supported in Helix, but it’s worth noting that the current implementation uses only _ as the word boundary. In the past, some folks have complained that it doesn’t quite behave like Vim’s CamelCaseMotion.

For example, #48276 updated Vim mode’s implementation to also treat $ and = as word-boundary characters, so I figured I’d flag that 🙂

auto-merge was automatically disabled March 3, 2026 05:34

Head branch was pushed to by a user without write access

@maxmalkin
Copy link
Copy Markdown
Contributor Author

Hi @dinocosta! That's a good catch, it is better in terms of UX to have $ and _ treated as subwords for things like assignments let var=0 or variables in PHP $var, for example. Added in my latest commit, thanks for the review!

@maxmalkin maxmalkin requested a review from dinocosta March 4, 2026 02:34
Copy link
Copy Markdown
Member

@dinocosta dinocosta left a comment

Choose a reason for hiding this comment

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

Thank you for tackling the suggested changes @maxmalkin ! 🙂

It seems that there's now a failing test, helix::test::test_subword_motions, let me know if you're able to see what's going on there, otherwise I can try and take a look. I've also merged main into your branch to ensure it stays up to date.

P.S.: Also added a release note which was missing from the PR's description

@maxmalkin
Copy link
Copy Markdown
Contributor Author

Hey @dinocosta ! I'm having some issues with zed-scap since I'm working in WSL, but the test should be passing now. I think the root cause was a zero-width selection for the starting state which wasn't a valid result.

@maxmalkin maxmalkin requested a review from dinocosta March 5, 2026 01:01
Copy link
Copy Markdown
Member

@dinocosta dinocosta left a comment

Choose a reason for hiding this comment

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

Hey @maxmalkin !

Unfortunately it seems your latest commit – 0407183 – doesn't actually fix the failing test, as it actually deleted assertions from existing tests.

Let me know if you're still unable to run tests on your end and I can try and take a look 🙂

@maxmalkin
Copy link
Copy Markdown
Contributor Author

Hi @dinocosta! Looks like it's still unable to run tests on my end, I would appreciate it if you could take a look! Thanks!

* Extract `vim::motion::is_next_subword_start`
* Update `vim::motion::next_subword_start` to use
  `vim::motion::is_next_subword_start`
* Update helix's handling of `NextSubwordStart` to also leverage
  `vim::motion::is_next_subword_start`
* Introduce new `vim::helix::test::test_next_subword_start` test to
  mirror the behavior that we also have on vim, with the exception that
  vim's implementation takes multiple empty lines into consideration,
  which Helix does not do right now, but I don't think it's breaking for
  these changes
I've only now just found that there's actually support for subword
motions in Helix, helix-editor/helix#8147 . As
such, it's probably better to try and mimic those as closely as
possible.

In turn, this means we likely can't re-use the vim motion's
implementation of `is_next_subword_start`, so, this commit reverts that
change and introduces a Helix specific `is_next_subword_start`
implementation that is now being used.

The tests have also been cross-checked and updated in order to match the
behavior in Helix.
* Revert the changes that introduced
  `vim::helix::is_next_subword_start`, turns out we actually don't need
  that
* Update both `vim::helix::test::test::test_next_subword_end` and
  `vim::helix::test::test::test_previous_subword_start` to ensure that
  the behavior matches the existing Helix's subword motions
* The `PreviousSubwordEnd` tests have not been updated yet, as it seems
  Helix doesn't actually offer that specific motion, so I still need to
  figure out how it should actually behave.
* Add `vim::helix::Vim::is_previous_subword_start`
* Add `vim::helix::Vim::is_previous_subword_end`
* Remove `vim::helix::test::test_subword_motions` as that attempted to
  test all motions in a single test and it's easier to reason about and
  fix motions if they're split into their own tests, which are now:

  * `vim::helix::test::test_next_subword_start`
  * `vim::helix::test::test_next_subword_end`
  * `vim::helix::test::test_previous_subword_start`
  * `vim::helix::test::test_previous_subword_end`

  The behavior in this test has been checked against Helix's subword
  commands, namely:

  * `move_next_sub_word_start`
  * `move_prev_sub_word_start`
  * `move_next_sub_word_end`
  * `move_prev_sub_word_end`
* Introduce simple `vim::helix::Vim::subword_boundary_start` and
  `vim::helix::Vim::subword_boundary_end` in order to replace
  `vim::helix::Vim::is_previous_subword_start`,
  `vim::helix::Vim::is_previous_subword_end`,`vim::helix::Vim::is_subword_boundary_left`
  and `vim::helix::Vim::is_subword_boundary_right`.
* Remove leftover `dbg!` calls.
@dinocosta dinocosta merged commit 1f442da into zed-industries:main Mar 17, 2026
29 checks passed
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

In Helix mode subword movements do not select the last subword unlike word

5 participants