Skip to content

helix-mode: add basic mode switching#1039

Open
schlich wants to merge 21 commits intonushell:mainfrom
schlich:helix/mode-switching
Open

helix-mode: add basic mode switching#1039
schlich wants to merge 21 commits intonushell:mainfrom
schlich:helix/mode-switching

Conversation

@schlich
Copy link
Copy Markdown
Contributor

@schlich schlich commented Mar 12, 2026

Summary:

This PR replaces the ad hoc Helix mode matcher with a small modalkit-backed modal machine and wires that into the experimental Helix edit mode. It adds basic insert/normal mode switching, minimal cursor movement, and insert-mode character handling, while also bringing in the dependency and MSRV changes needed to exercise modalkit in-tree.

Motivation

This is part of #960 and a follow-up to #1033. The main goal is not just to add a couple more Helix bindings, but to validate whether modalkit is the right abstraction for modal editing in reedline before more Helix behavior lands and before similar logic spreads further across modal editors.

note (Apr 7 2026): since this PR is scoped to the keybindings crate provided by modalkit, for now we add keybindings directly to the project and defer adding modalkit itself to a future PR.

Change overview:

  • Added keybindings as a dependency.
  • Replaced the earlier Helix { mode } plus (mode, modifiers, key) match in parse_event with a ModalMachine<TerminalKey, HelixStep>-based implementation.
  • Introduced small Helix-specific layers on top of modalkit:
    • HelixMode for insert/normal state
    • HelixAction for high-level actions
    • HelixBindings for the binding table
    • HelixStep = (Option<HelixAction>, Option<HelixMode>) for action + mode transitions
  • Split up edit_mode/helix module into multiple files guided by above organization levels.
  • Added the first basic bindings:
    • Esc switches insert -> normal
    • i switches normal -> insert
    • insert-mode printable characters flow through as InsertChar
    • h/left and l/right map to cursor movement in normal mode
  • Added a feature-gated Helix example and updated builder coverage around the new default insert-mode startup.
  • Added a TryFrom<ReedlineRawEvent> for KeyEvent helper (feature-gated) to make the Helix event path cleaner.

Tests/Example:

  • Added unit coverage for:
    • Helix default mode
    • Ctrl-C handling
    • insert/normal mode transitions
    • insert-mode typing
    • basic left/right movement in normal mode
  • Added example/builder coverage for the feature-gated Helix integration.
Previous shape New shape Purpose
Helix { mode } Helix { machine } Store modal state in modalkit instead of a hand-rolled enum field
(mode, modifiers, key) match in parse_event ModalMachine<TerminalKey, HelixStep> Move key-to-action routing into a reusable modal binding engine
Direct self.mode = ... transitions Option<HelixMode> in HelixStep Represent mode changes as binding results
Direct ReedlineEvent returns for each case HelixAction -> ReedlineEvent mapping Separate binding decisions from reedline event emission
Implicit insert text handling in ad hoc logic ModeKeys::unmapped for insert mode Let insert-mode printable characters fall through via modalkit

@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Mar 12, 2026

My assumption was that these bite-sized PRs would (eventually) be implementing modalkit as a standard way to do helix keybinds and then eventually move modalkit into emacs and vim keybindings replacing the existing system. If I'm wrong, let me know.

The size of this PR is fine because it's easy to view the changes and the tests supporting them. They could be a bit bigger if needed but small is ok too.

As far as matching, whatever we think is the cleanest looking and easiest to understand is probably the way to go.

@schlich
Copy link
Copy Markdown
Contributor Author

schlich commented Mar 12, 2026

great -- i will incorporate modalkit logic into this PR and we can see how it looks. stand by!

@schlich
Copy link
Copy Markdown
Contributor Author

schlich commented Mar 12, 2026

One thing to keep tabs on -- modalkit would require a bump in MSRV if we want to implement directly. I could upgrade on this PR but i would imagine it would block, alternatively i can try to mimic modalkit's API with some stub interfaces and see where that gets us just as far as getting an idea of the shape of the code, and we can defer the upgrade until we have a better idea of the impact

@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Mar 13, 2026

Our msrv now is 1.63 i think, that's quite old (Aug-2022). I'm not super worried about bumping that a bit if we have good reason. The idea of modalkit is to keep from reinventing the wheel in regards to vim, and hopefully other, keybindings. If modalkit is not right, i'm down with whatever is better too.

Let me put it like this, for years we've had nit-picky problems with our vim keybindings. I hear so much complaining like, "This is why I'm not using nushell" and it comes down to some vim nuance. I was hoping modalkit would be a standard and correct way to do vim bindings. I really don't know for sure though. So, when we started talking about helix motions I immediately was hoping for a better system where all these errors go away for vim, emacs, and helix. Maybe I'm naive but that's what I'm hoping for.

@schlich schlich force-pushed the helix/mode-switching branch 2 times, most recently from 2b7fe51 to 26b376b Compare March 13, 2026 18:21
@schlich schlich force-pushed the helix/mode-switching branch from 26b376b to 79f17af Compare March 13, 2026 18:31
@schlich
Copy link
Copy Markdown
Contributor Author

schlich commented Mar 13, 2026

Updated the PR description; this is ready for review

Comment thread Cargo.toml Outdated
Copy link
Copy Markdown
Contributor

@fdncred fdncred left a comment

Choose a reason for hiding this comment

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

I looked this over and tested the examples. The only problem I found was the crossterm version.

@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Mar 14, 2026

I pushed a commit onto this pr to try and avoid downgrading crossterm. We need to get modalkit to upgrade and then fix these dependencies but at least this won't hold us up. @schlich you should feel free to delete my commit if you have a better idea. Just trying to help out.

@schlich
Copy link
Copy Markdown
Contributor Author

schlich commented Mar 15, 2026

Will take a look! Is it conventional for rust projects to pin dependencies to specific versions? It's pretty faux pas in python land to not just specify lower bounds

@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Mar 15, 2026

it's pretty typical to pin deps if you have a reason to. no harm done. it's a good way to keep cargo from trying to update deps when you build without compiling with --locked.

@schlich
Copy link
Copy Markdown
Contributor Author

schlich commented Mar 30, 2026

pushed up some changes with 'a' key normal -> insert mode ("append"), a bunch of small refactors for ergonomics, and an example of a property-based test for insert-key fallback behavior. do we want to try to merge this before it gets much larger? are we ok with the crossterm resolution as a stopgap until modalkit upgrades?

@fdncred fdncred dismissed their stale review March 30, 2026 23:41

updated

@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Mar 30, 2026

Yes, let's not make this PR any larger. It's large enough already. Yes, I'm ok with the modalkit stopgap. We need to convince modalkit to upgrade somehow, quickly hopefully.

The only thing in this PR that I object to is there are way too many changes to the Cargo.lock file. It looks like someone ran cargo update. We don't need to do that. We just need to update the specific crates.

We also need to ensure that all the helix changes are behind the helix feature.

@schlich
Copy link
Copy Markdown
Contributor Author

schlich commented Mar 30, 2026

i dont recall running cargo update, just cargo add for proptest-- if i need to revert the proptest to plain tests that's fine, or if there's a way to prevent updating dependencies when adding a new project (I assume cargo add --locked? again, going off of my Python experience here, but usually it is what is in .toml that matters for defining safe dependency bounds and Cargo.lock is expected to have frequent changes. I'll take a closer look at what depedencies changed and figure out how much adding that one dependency affected things, but i doubt the code is that brittle that updating patch versions (which i believe is rusts default?) should be a problem. I know you mentioned you dont have a lot of faith in existing tests, but hopefully we can get things in a not so brittle state through this push

def will double check all the feature markers

@schlich
Copy link
Copy Markdown
Contributor Author

schlich commented Mar 31, 2026

ok, so i reverted the installation of proptest but we are still upgrading the lockfile when we add modalkit, aren't we? and don't forget we are changing the MSRV too. we could try to go back to the lockfile state before the PR and do cargo add --locked modalkit but that feels overly cautious to me... just lmk what you want to do

@schlich
Copy link
Copy Markdown
Contributor Author

schlich commented Mar 31, 2026

another reminder, upgrading MSRV gave us some new clippy errors. we will need to explicitly ignore them if we want to completely avoid touching existing code, or I can open a PR just focusing on that bump so we can separate things out?

@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Mar 31, 2026

ok, understood. yes, i realize we're updating things and i'm fine with that but i'm not fine with doing a cargo update. Glad you didn't do that. Sometimes updating dependencies globally can cause instability. I've seen it time and time again on nushell. We try to be very careful with the lock file. We've also seen that cargo is stupid sometimes and upgrades or downgrades dep versions when it's not needed.

Just to be clear, I'm good with whatever updates to the lock file are required for our deps and msrv. I'm not good with globally updating everything with cargo update.

I'm fine with proptest being added too. From my perspective, the more tests we can have the more stable things will get.

I think all clippy issues should be fixed in the PR that introduces them. We prefer to have a clean build with PRs.

@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Apr 6, 2026

What's the status here? Are we just waiting on a review or are there more changes incoming?

@philocalyst
Copy link
Copy Markdown

Sent the maintainer an email :)

@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Apr 7, 2026

Sent the maintainer an email :)

I assume you mean modalkit and not me. I saw they landed the PR to bump crossterm. This PR could be updated to use the latest version.

@schlich
Copy link
Copy Markdown
Contributor Author

schlich commented Apr 8, 2026

ok, based on @ulyssa 's suggestions i've updated the dependencies/imports to utilize the keybindings crate directly and we can defer MSRV/crossterm updates to a future PR. I think this is ready to merge on my end; double checked the entrypoints related to these changes and everything looks properly feature-gated.

@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Apr 8, 2026

ok, keybindings clever. I didn't consider that and didn't see the comment. I think I'm ready here too but we need to wait for the reedline/nushell release this weekend. I don't like to include anything in the nushell release that hasn't be tested, even if it's feature gated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants