Skip to content

Add pane join actions for vim-like split close workflows#50035

Open
baldwindavid wants to merge 1 commit into
zed-industries:mainfrom
baldwindavid:join-into-inactive
Open

Add pane join actions for vim-like split close workflows#50035
baldwindavid wants to merge 1 commit into
zed-industries:mainfrom
baldwindavid:join-into-inactive

Conversation

@baldwindavid
Copy link
Copy Markdown
Contributor

@baldwindavid baldwindavid commented Feb 24, 2026

Follow-up to #47079 which added tab_switcher::OpenInActivePane for vim/helix-like buffer management (i.e. tabs hidden, buffers available in every pane). That PR noted the need for better pane close behavior — in vim/helix, closing a split (:q) closes the window but buffers remain available. Zed's tab model ties items to panes, so the closest equivalent is joining items into an adjacent pane without disrupting the destination's active tab.

The existing pane::JoinIntoNext gets partway there, but it always activates the source pane's tab in the destination, and only searches in the "next" direction (Right → Down → Left → Up). These new actions fill the gaps:

Actions:

  • pane::JoinIntoNext (existing) — Joins this pane into the next pane. The source pane's active tab remains active.
  • pane::JoinIntoNextAsInactive (new) — Joins this pane into the next pane. The destination pane's active tab remains active.
  • pane::JoinIntoPrevious (new) — Joins this pane into the previous pane. The source pane's active tab remains active.
  • pane::JoinIntoPreviousAsInactive (new) — Joins this pane into the previous pane. The destination pane's active tab remains active.

Example keymap for vim-like :q behavior:

{
  "context": "Pane",
  "bindings": {
    "cmd-w": "pane::JoinIntoPreviousAsInactive"
  }
}

This closes the current split and moves its items to the previous pane (typically left/above), without disrupting the destination pane's active tab — approximating how vim/helix close windows.

Future work:

  • Map vim's :q to pane::JoinIntoPreviousAsInactive so closing a split behaves like vim/helix out of the box.

Before you mark this PR as ready for review, make sure that you have:

  • Added a solid test coverage and/or screenshots from doing manual testing
  • Done a self-review taking into account security and performance aspects
  • Aligned any UI changes with the UI checklist (N/A — no UI changes)

Release Notes:

  • Added pane::JoinIntoNextAsInactive, pane::JoinIntoPrevious, and pane::JoinIntoPreviousAsInactive actions for more flexible pane join behavior, enabling vim/helix-like split close workflows.

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Feb 24, 2026
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 @baldwindavid 👋

Thank you so much for suggesting this change! I can see how introducing something like this can be useful for users that are trying to get a setup as close as possible to Neovim/Vim with tab_bar hidden 🙂

Am I missing a specific use case or would simply supporting the JoinIntoNextAsInactive (or similar) be enough to support Neovim's behavior? I tested the :q behavior a bit with 3 splits in Neovim and it seemed to always focus on the next (right) split, when closing the middle split, regardless of whether the previously focused split was the left or right one.

@baldwindavid
Copy link
Copy Markdown
Contributor Author

Thanks, @dinocosta

I tested the :q behavior a bit with 3 splits in Neovim and it seemed to always focus on the next (right) split, when closing the middle split, regardless of whether the previously focused split was the left or right one.

Oh, right, that's the default behavior in vim. I guess I've had this in my vim config since forever...

" Open splits to right and bottom rather than default
set splitbelow
set splitright

...and, with those settings, it does the opposite. The additional actions would allow for whatever devs are used to, but I'm also not going to argue it's strictly necessary. Frankly, my preference would be for it to move to the most recent pane, but vim didn't choose that. I'm pretty okay with whether we go three additional actions or just one; the key thing is just having some way to approximate keeping buffers around and either of the *AsInactive actions would do that.

Also, I'm not really enamored with JoinIntoNextAsInactive, but didn't land on anything better. A few others considered:

  • JoinIntoNextPreserved
  • JoinIntoNextUnfocused
  • JoinIntoNextAfter
  • JoinAfterNext

And then there's also the option of keeping just JoinIntoNext/Previous and being able to provide config options to it (e.g. preserve: true), but it can be nice to be able to call via the command palette rather than only via configured keybinding.

@dinocosta
Copy link
Copy Markdown
Member

the key thing is just having some way to approximate keeping buffers around and either of the *AsInactive actions would do that.

Makes total sense to me! 🙂

Also, I'm not really enamored with JoinIntoNextAsInactive, but didn't land on anything better. A few others considered:

  • JoinIntoNextPreserved
  • JoinIntoNextUnfocused
  • JoinIntoNextAfter
  • JoinAfterNext

And then there's also the option of keeping just JoinIntoNext/Previous and being able to provide config options to it (e.g. preserve: true), but it can be nice to be able to call via the command palette rather than only via configured keybinding.

Yes, was also thinking whether we could simply update/extend JoinIntoNext . Will dwell a little bit more on this and get back to you.

Thanks!

@dinocosta
Copy link
Copy Markdown
Member

dinocosta commented Feb 26, 2026

I've also just noticed a couple of things that might be relevant:

  • With the provided mapping, that is, mapping cmd-w to JoinIntoNext or any of its variants, we stop being able to close buffers/files when there's only a single pane remaining
  • The approach also doesn't seem to directly tackle the :q behavior, which is probably what we'd actually want
    • Having this in mind, I'm not entirely sure if we can achieve the proposed goal through updating keybindings, which is not possible for commands like :q[uit] and I'm not sure if we can split the single-pane vs multi-pane scenario in keymap contexts
    • Wonder if we'll need an entirely new action (maybe vim::Quit) for this or if we can repurpose workspace::CloseActiveItem, as if we're trying to chase this behavior, it would probably be nice to have it work on :q?

Still thinking about this! 😅

@baldwindavid
Copy link
Copy Markdown
Contributor Author

baldwindavid commented Feb 26, 2026

Yeah, it doesn't cover :q because of said dragons. We can make :q do the same thing as this new action and it mostly approximates. We could also make it aware of being the "last pane".

The bigger issue I've been concerned with is that I'm not sure we can just force that behavior of merging panes onto users for :q. Yes, they are almost surely in vim mode, but they've been working in Zed, which is a very tab-centric paradigm. Unless they really want to hide tabs and have something resembling that buffer-centric workflow, this might be weird behavior to them.

To your point though, it might be cleanest to just do an entirely new action purpose built (i.e. last pane aware) for this need. Even so, I'm just not sure we should make :q run it. I ran into the same quandary with #47079 ...it's in service of a buffer-centric workflow, but didn't feel like I could just force it on vim users so made it a separate tab switcher action.

One route is to not change :q and instead just add a section to the vim docs about approximating a buffer-centric workflow (i.e. hide tabs, use tab_switcher::OpenInActivePane, keybinding for vim::Quit or whatever we call it). What we lose out of that is being able to use :q in that workflow though, which would be somewhat incomplete. I am betting some day we actually can have keybindings for commands like :q.

@baldwindavid
Copy link
Copy Markdown
Contributor Author

Related: I apparently also set this probably a couple years ago in my Zed settings and forgot about it...

"pane_split_direction_horizontal": "down",
"pane_split_direction_vertical": "right",

So if we were to end up going with a standalone action (e.g. vim::Quit) we'd likely want it to look at this setting to determine which way to try to join first.

@BCastilloAT3W
Copy link
Copy Markdown

I just want to add another user voice here: this workflow would be genuinely useful. For people using hidden tabs and a more vim buffer workflow, closing a split without disrupting the destination pane’s active item while keeping the buffers open is a big quality of life improvement.

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.

3 participants