Skip to content

Tree-sitter tab navigation (Smart tab)#41321

Closed
vamsi10010 wants to merge 20 commits intozed-industries:mainfrom
vamsi10010:smart-tab
Closed

Tree-sitter tab navigation (Smart tab)#41321
vamsi10010 wants to merge 20 commits intozed-industries:mainfrom
vamsi10010:smart-tab

Conversation

@vamsi10010
Copy link
Contributor

@vamsi10010 vamsi10010 commented Oct 27, 2025

Closes #22349
Closes #14803

  • This PR introduces a syntax-aware navigation feature (using tree-sitter nodes) that enables fast cursor navigation within code structures. This PR adds two new components:
    • Adds two actions MoveToEndOfLargerSyntaxNode and MoveToStartOfLargerSyntaxNode. These actions can be bound to any keybinding if the user does not like the default smart tab settings.
    • Binds tab and shift-tab to perform the two actions respectively. In particular, tab will check if there is whitespace to the left of the cursor. If there is only whitespace, a regular tab is inserted, otherwise, the cursor will move to the end of its parent node. shift-tab will always move the cursor to the start of its parent node. This can be activated using a new setting called Smart Tab.
  • If smart tab is activated, the navigation will respect snippet tab stops, completions, and edit predictions. Tab and shift-tab also act normally when text is selected. Smart tab also does not activate in normal mode for vim (it does activate in insert mode). Additional settings are available to supersede accepting completions and edit predictions in the smart tab settings page.
  • This PR was heavily inspired by its namesake feature in Helix (Smart tab (aka tabout) helix-editor/helix#4443). It also provides the behavior users might expect when using albert.TabOut in VSCode (and more). There was also an attempt to do this in Tab-out feature #23412 which was rejected in favor of a more sophisticated approach. I took the feedback there and opted to use the builtin tree-sitter features for navigation.

Questions:

  1. I currently hooked the smart tab functionality into the existing editor::Tab and editor::Backtab actions. Is this approach preferred to making separate actions for binding to tab and shift-tab keys?
  2. Helix provides a setting where shift-tab can be used as a backup tab key. I believe they did so because not all terminals support shift-tab. However, since zed does not have that issue, I have mapped shift-tab to always move to the start of the parent node (if the setting is enabled). Do we want a setting where shift-tab can be configured as a tab backup?
  3. I'm not sure what the best place would be to document this feature in zed docs.
  4. Should this feature be enabled by default? I have it set to disabled.
  5. While I have settings to supersede completions and edit predictions, I would love some feedback on their design so that I don't introduce any code debt there.

Here is smart tab in action:

zed-smart-tab.mp4

This is my first contribution to zed, so I would really appreciate any feedback on this! I believe this feature could be very helpful for users coming from other IDEs like VSCode or JetBrains.

Release Notes:

  • Added new actions to move to start/end of tree-sitter nodes
  • Added settings to bind tab and shift-tab to those actions

@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Oct 27, 2025
@zed-industries-bot
Copy link
Contributor

zed-industries-bot commented Oct 27, 2025

Warnings
⚠️

This PR is missing release notes.

Please add a "Release Notes" section that describes the change:

Release Notes:

- Added/Fixed/Improved ...

If your change is not user-facing, you can use "N/A" for the entry:

Release Notes:

- N/A

Generated by 🚫 dangerJS against 38f335a

@vamsi10010 vamsi10010 marked this pull request as draft October 28, 2025 21:33
@vamsi10010 vamsi10010 marked this pull request as ready for review October 29, 2025 02:35
@ConradIrwin
Copy link
Member

Thanks for this!

I think the actions of move to start/end of tree-sitter node make sense, but I don't see a world where we can bind them to tab by default (that is already overloading for indent, auto-complete, and AI suggestions).

Is it possible to reduce this to just creating the actions, or would we need to add more information to the key-context to determine what to do when you hit tab?

@vamsi10010
Copy link
Contributor Author

Is it possible to reduce this to just creating the actions, or would we need to add more information to the key-context to determine what to do when you hit tab?

Thanks for the feedback!

Its definitely possible to reduce this just to those two actions. I would have to add new key contexts that determine whether forward or backward movement is active. They would essentially do the same checks I hardcoded into tab and backtab and set two new key contexts (for ex. smart_tab_forward_active and smart_tab_backward_active). I will revisit my implementation soon and make these changes.

@ConradIrwin
Copy link
Member

Hmm; I'm not sure what the best option is.

What happens on this branch when you hit tab and have an autocompleter open, an AI suggestion visible, or a snippet with tab-stops?

I am nervous about tab just because it does so much already, but maybe this works if it's right at the end of the fallback list.

Either way a PR to just add the actions seems totally reasonable.

@vamsi10010
Copy link
Contributor Author

vamsi10010 commented Nov 13, 2025

What happens on this branch when you hit tab and have an autocompleter open, an AI suggestion visible, or a snippet with tab-stops?

Hitting tab will accept the autocompletion/edit prediction, or move to the next tab stop as expected.

I also created settings that let you supersede accepting autocompletions/edit predictions, but I am not sure if we want to keep them.

However, by default, tab key as movement is at the end of the fallback list and happens only before indentation.

@ConradIrwin
Copy link
Member

Closing this for now as it conflicts with everything. Let's start with a PR that does the actions you want, and then figure out separately how to get the tab behavior perfect.

@ConradIrwin ConradIrwin closed this Dec 2, 2025
@github-project-automation github-project-automation bot moved this from Community PRs to Done in Quality Week – December 2025 Dec 2, 2025
ConradIrwin pushed a commit that referenced this pull request Jan 14, 2026
Release Notes:

- Added two actions `move_to_start_of_larger_syntax_node` and
`move_to_end_of_larger_syntax_node` that move cursors to the start or
end of the parent tree-sitter node

Following up on my PR #41321, this PR only adds the actions that are
used to enable code navigation across syntax nodes, without binding them
to any keys (such as tab) by default. Both actions use the tree-sitter
syntax tree to find parent nodes of the nodes the cursors are currently
in. `move_to_start_of_larger_syntax_node` will then move each cursor to
the first position of the parent nodes while
`move_to_end_of_larger_syntax_node` to a position right after the parent
nodes.

Related issues and discussions: #22349, #14803, #42828, #13736.

This PR doesn't achieve "tab out" functionality in the exact sense as is
requested in these issues as it does not bind the actions to the tab
key. I hope this PR can start some discussion on what the best way
forward for these issues is. In the meantime, users can configure keys
to use these actions as they see fit to emulate "tab out" behavior. For
example,
```
"context": "Editor && vim_mode == insert && !in_snippet && !showing_completions",
"bindings": {
  "tab": "editor::MoveToEndOfLargerSyntaxNode",
  "shift-tab": "editor::Tab"
}
```
This will enable tab to skip past code structures like brackets when the
cursor is not in a snippet or the autocomplete menu is not open. At the
same time, shift tab will act as a backup tab.
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

Development

Successfully merging this pull request may close these issues.

Implement something like the VSCode TabOut extension Smart tab support

3 participants