Skip to content

Add tab switcher mode similar to vim/helix buffer picker#47079

Merged
ConradIrwin merged 5 commits intozed-industries:mainfrom
baldwindavid:tab-switcher-follow-mode
Feb 5, 2026
Merged

Add tab switcher mode similar to vim/helix buffer picker#47079
ConradIrwin merged 5 commits intozed-industries:mainfrom
baldwindavid:tab-switcher-follow-mode

Conversation

@baldwindavid
Copy link
Contributor

@baldwindavid baldwindavid commented Jan 17, 2026

Implements the enhancement requested in #24655 - enables tab switcher to show all open files across all panes deduped. Whichever is the active pane is ALWAYS where the file, terminal, etc will be opened from the tab switcher.

Adds tab_switcher::OpenInActivePane. When enabled:

  • Shows all tabs across panes - Same file in multiple panes appears once (deduped by path)
  • Opens in active pane - Selecting a file opens it in the active/current pane, creating independent editor instances
  • Moves terminals - Non-file items (terminals, multibuffers, etc.) move to current pane instead of duplicating

Intended for vim/helix :buffers command workflow while respecting Zed's tab-based paradigm.

Q&A

Q: Why not tie this to vim mode?

A: This feature is different enough from vim/helix buffer behavior that I opted not to couple them. It is an approximation using Zed's tab switcher/picker and would take architectural changes to implement real vim/helix buffers. Additionally, both this and regular tab switcher mode (tab_switcher::ToggleAll) are useful window/tab management workflows that vim or non-vim users may want regardless of their editing mode preference.

Q: How to use this?

A: For a vim/helix-like workflow:

  1. Bind the tab switcher to a key - Add to your keymap:

    {
      "context": "Workspace",
      "bindings": {
        "cmd-i": "tab_switcher::OpenInActivePane"
      }
    }
  2. Hide the tab bar (optional) - Add to settings:

    {
      "tab_bar": {
        "show": false
      }
    }
  3. Move tabs to other pane on close (optional) - Add to your keymap to join tabs into next pane when closing:

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

This makes closing a tab behave more like vim's split closure - items move to the adjacent pane instead of being closed.

If you find that you'd rather have the tab bar visible, there's a pretty good chance you don't need/want this feature. Vim/helix don't have visible tab bars, so this feature is primarily for users who want that sort of experience where buffers are not tied to a specific split/pane and closing a split does not impact the available buffers.

Future Work

A few additional features could make for more complete vim/helix-like buffer workflow:

:bd (buffer delete) improvements:

  • Should/could close the buffer from ALL panes, not just the current one
  • Current implementation: workspace::CloseActiveItem only closes in current pane
  • Needed: New action like workspace::CloseActiveItemAcrossPanes and/or update vim command mapping

:q (quit) improvements:

  • Should/could close the split/pane
  • Current implementation: workspace::CloseActiveItem closes active item, not the pane
  • Needed: Perhaps just call pane::CloseAllItems to actually close the pane. This would be closer to what vim/helix does although those only close the "window" into buffers rather than all buffers within. Users of the behavior in this PR would likely want to have a keybinding to pane::JoinIntoNext like mentioned above. I don't think it's completely clear what the best course of action is, but something to consider.

Previews:
When first opened, this PR had preview functionality. However, that added a whole lot of complexity for a nice feature, but not the critical part of this workflow. Perhaps that could be approached in the future.

Release Notes:

  • Add tab_switcher::OpenInActivePane to allow a more vim-like tab switching experience.

@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Jan 17, 2026
@baldwindavid baldwindavid changed the title Add tab switcher follow mode (similar to vim buffer picker) Add tab switcher mode similar to vim/helix buffer picker Jan 17, 2026
@mkcode
Copy link
Contributor

mkcode commented Jan 20, 2026

Thank you for creating this @baldwindavid. It's much appreciated.

To share some perspective, I tried getting a similar feature merged into vscode 4 years ago and there were follow ups.

I bet this PR will go a lot better than the vscode folks. :)

@baldwindavid
Copy link
Contributor Author

baldwindavid commented Jan 20, 2026

You got it @mkcode . Yeah, we'll see. Definitely tried to avoid impacting the existing tab-based paradigm, but there are a lot of different approaches that could be taken. The vim implementation in Zed is really nice, but I just haven't been able to get over having to constantly babysit tabs confined to panes.

@ConradIrwin ConradIrwin self-assigned this Jan 21, 2026
@alexispurslane
Copy link
Contributor

Just chiming in to say that I am another user that would very much appreciate this functionality, as it would also allow emulating Emacs better as well. I found the existing behavior extremely confusing and opaque lol

@ConradIrwin
Copy link
Member

Thanks for this.

  • When testing the implementation I noticed that items were not removed from the pane (I had 4 files open on the left, and 1 on the right. I used cmd-i to open the toggle and selected the 1 on the right; it ended up duplicated, and I saw errors in the log 1: Sqlite call failed with code 1555 and message: Some("UNIQUE constraint failed: items.item_id, items.workspace_id")).
  • (maybe related to the above) in the code you're closing and re-opening project items (
    if let Some(project_path) = selected_match.item.project_path(cx) {
    let was_preview = selected_match.preview;
    ). I think you can remove them from one pane and add to another - as you do in the else branch.
  • It's a bit unclear what to do when previewing - because you want to end up with two copies of the item. You can do this for most (maybe all) items with clone_on_split.
  • So many tests; can you please condense them and remove the vacuous and repetitive ones.
  • I'm not a big fan of making this an option on tab_switcher::ToggleAll - it feels like a third mode. How about tab_switcher::ToggleMerge? I dunno, that's not great. Any better ideas?

It feels a bit broken in its current state, but happy to take another look once you've cleaned it up.

@baldwindavid
Copy link
Contributor Author

baldwindavid commented Jan 28, 2026

Thanks, @ConradIrwin
My first run at this was without previews and I really should have stuck with that since it got out of hand and with oh so many tests. I've pulled out the preview functionality which simplifies things quite a lot and cleaned out some tests.

This has also been changed to a standalone command named tab_switcher::ToggleUnique. Not sure that's better than Merged or there's also Deduped.

That it is a standalone command makes me think maybe its okay to diverge from the other modes in lacking the previews, but also understandable if that feels like an incomplete/inconsistent feature. I know for my own usage, the critical part is not the previews, but it would also be nice to have. If this gets to a shippable state perhaps I'd take another run at previews later too.

The other thing I suspect is that all the modals will eventually end up going in a direction similar to #44530 with a preview decoupled from tabs, but that might be a big assumption.

When testing the implementation I noticed that items were not removed from the pane

One thing I want to make sure is clear is that the intent is very much that you can easily end up with a file open in multiple panes. Regular files/buffers should "activate or clone" (never removed) in whichever pane you're in while terminals, multibuffers and such should "move" to whichever pane you're in. You wouldn't typically necessarily know or care which tabs are open in which panes because, if you're using this feature, you probably also hide the tab bar altogether, similar to vim/helix.

@ConradIrwin
Copy link
Member

Makes sense, thanks for fixing it up. The code looks good to me, but I'm super hung up on the name.

Do we need the original ToggleAll if we have this? Because that really is the best name for it. If we do then our AI overlords suggest any of:

  • ToggleFetch / ToggleAllHere
  • ToggleAllAndMoveToPane

I'm not sure what to do... and it doesn't seem fair to make you wait any longer...

maybe rename the existing ToggleAll to ToggleGlobal and make this one ToggleAll (not great if we want to keep that feature as-is), or this could be tab_switcher::FindAny or something to lose that dratted Toggle verb.

@baldwindavid
Copy link
Contributor Author

Great, sounds like we're close. Yeah, we're in a bit of pickle with the naming. ToggleAll is the best name, but I'd be real hesitant to take over that name. The existing ToggleAll totally makes sense for Zed's tab/pane-centric paradigm, so should probably take priority over this buffer-ish flavor.

The intent with ToggleUnique was to focus on the dedupe/unique buffer behavior in that, if you have the same buffer already open in two panes, only one would show. But, to your suggestions, it does make a lot of sense to focus more on the behavior of opening in the active pane. That the tabs are deduped/unique in the modal is sort of then implied anyway.

Maybe we just go real explicit with tab_switcher::OpenInActivePane.

Something like OpenHere is also interesting, but "Here" might not always be obvious because the tab switcher can be opened from anywhere in the workspace.

I'm not too concerned with how quickly this gets in; lets get a name we're comfortable with. Thanks!

@baldwindavid
Copy link
Contributor Author

I've pushed the OpenInActivePane name change only as an option that's better than ToggleUnique, but it's of course real simple to change to anything from here.

@ConradIrwin ConradIrwin enabled auto-merge (squash) February 5, 2026 20:14
@ConradIrwin
Copy link
Member

tab_switcher::OpenInActivePane works for me, thanks!

@ConradIrwin ConradIrwin merged commit 46017f9 into zed-industries:main Feb 5, 2026
28 checks passed
naaiyy added a commit to Glass-HQ/Glass that referenced this pull request Feb 16, 2026
Key changes:
- Thermal state detection (zed-industries#45638) - GPUI detects system thermal state, throttles to ~60fps when overheating
- Checkerboard shader for side-by-side diff (zed-industries#48417) - visual pattern for diff backgrounds
- cosmic-text v0.17 (zed-industries#48504) - fixes font ligatures on Linux
- Middle click tab close (zed-industries#44916) - on_aux_click/is_middle_click API additions
- Soft wrap modes for wrap width (zed-industries#46422)
- Tab switcher mode similar to vim/helix buffer picker (zed-industries#47079)
- Multi_buffer optimization batch (zed-industries#48519)
- TreeMap for diagnostics (zed-industries#48482) - performance improvement
- Semantic token follow-up fixes (zed-industries#48485)
- Claude Opus 4.6 and 1M context window model variants (zed-industries#48508)
- Anthropic adaptive thinking types (zed-industries#48517)
- Side-by-side diff: hunk gutter highlights restored, toolbar buttons for SplittableEditor
- Shell quote bypass fix in terminal permission system (zed-industries#48436)
- Project panel: Collapse All improvements (zed-industries#47328, zed-industries#48443)
- Edit prediction: trailing newlines fix, cursor position in global coords
- Properly discard tokens on language server stop (zed-industries#48490)
- AgentTool::NAME const instead of hardcoded strings (zed-industries#48506)

Conflict resolution:
- collab/editor_tests.rs: deleted (collab removed)
- vim (helix, motion, increment): deleted (vim removed)
- GPUI (17 files): deleted from Glass (handled in Obsydian-HQ/gpui)
- editor/items.rs: merged imports (added BufferId, kept Theme)
- project_diff.rs: removed old native_button toggle (upstream uses toolbar buttons now)
- lsp_store.rs: added SemanticTokenConfig, removed GlobalLogStore/LanguageServerKind
- project_panel.rs: merged UI imports (added ContextMenuEntry, ScrollAxes)
- Keymaps: took upstream JetBrains bindings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

4 participants