-
Notifications
You must be signed in to change notification settings - Fork 0
feat: expose TabsVim_* public functions, remove all OOTB keybindings #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
a3aa696
docs: add ADR-004 and key-binding SPEC for keybinding cleanup
5b6f7d5
docs: remove TabsVim_JumpTab — redundant with native <count>gt
8b5b43b
feat: expose TabsVim_* public functions, remove all OOTB keybindings
770f02b
fix: address PR review comments from Copilot
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # 004. Keybinding Cleanup — Expose Functions, Minimal OOTB Bindings | ||
|
|
||
| **SPEC:** tabs.vim / key-binding | ||
| **Status:** Accepted | ||
| **Last Updated:** 2026-04-04 | ||
|
|
||
| --- | ||
|
|
||
| ## Decision | ||
|
|
||
| Refactor `plugin/tabs.vim` to expose public `TabsVim_*` functions for all non-trivial operations, and reduce out-of-the-box bindings to only what cannot be cleanly delegated to the user's vimrc. | ||
|
|
||
| **OOTB behavior retained:** | ||
| - Mouse drag-and-drop file opening (bracketed-paste infrastructure) | ||
|
|
||
| **All keybindings removed from the plugin.** Users wire them via their own vimrc using the exposed functions. Vim's native `gt` / `gT` / `<count>gt` cover tab navigation without any plugin binding. | ||
|
|
||
| --- | ||
|
|
||
| ## Context | ||
|
|
||
| The current plugin ships ~20 hard-coded keybindings covering terminals, window splits, buffer rename, redo, file path copy, fzf integration, and tab jumps. This creates several problems: | ||
|
|
||
| 1. **Conflicts**: Bindings like `<C-]>` (native: jump to tag), `gF` (native: open file under cursor), `<leader>r` (commonly user-assigned), and terminal mode maps override keys that users or other plugins rely on. | ||
| 2. **Scope creep**: Some bindings (`<leader>r` for redo, `<leader>fy` for clipboard) are general editor utilities with no tab-management justification. | ||
| 3. **Opaque defaults**: Users cannot audit what the plugin has bound without reading source code. | ||
| 4. **No opt-out granularity**: No per-binding disable flag exists; users must shadow every conflicting map. | ||
|
|
||
| Vim already provides `gt`, `gT`, and `<count>gt` for tab navigation — there is no need for the plugin to bind `<Tab>`/`<S-Tab>` either. | ||
|
|
||
| Mouse DnD is retained OOTB because it requires bracketed-paste terminal infrastructure (`t_BE`/`t_BD` sequences and `<F30>`/`<F31>` key aliases) that cannot meaningfully be expressed as a user-side vimrc binding. It also has zero keymap conflict risk. | ||
|
|
||
| The plugin's declared principles are *Locality*, *Discoverability*, *Speed*, and *Integration* — none of which require owning the user's keymap. | ||
|
|
||
| --- | ||
|
|
||
| ## Considered Options | ||
|
|
||
| | Option | Pros | Cons | | ||
| |--------|------|------| | ||
| | **Expose functions, only mouse DnD OOTB** *(chosen)* | Zero keymap conflicts; users own their keymap; functions are stable API | Users must add vimrc bindings | | ||
| | Keep `<Tab>`/`<S-Tab>` OOTB | Convenient defaults | Redundant with native `gt`/`gT`; `<Tab>` conflicts with completion plugins (e.g. copilot.vim) | | ||
| | Keep all bindings, add `g:tabs_vim_no_mappings` flag | Easy single opt-out | All-or-nothing; doesn't help with partial conflicts | | ||
| | Keep all bindings, add per-binding `g:tabs_vim_map_*` flags | Fine-grained opt-out | Dozens of config variables; documentation burden | | ||
|
|
||
| Terminal mode escape mappings (`tnoremap <Esc>`, `tnoremap <C-]>`) are removed — especially collision-prone and purely a user preference. The `<C-]>` recommendation (use it to enter terminal insert mode, overriding its native tag-jump role) is documented in the key-binding SPEC as an opt-in with the trade-off explained. | ||
|
|
||
| --- | ||
|
|
||
| ## Consequences | ||
|
|
||
| - Only functions with real logic are promoted to `TabsVim_*` public API: `TabsVim_ToggleHorizTerm`, `TabsVim_ToggleVertTerm`, `TabsVim_NewTabTerm`, `TabsVim_CloseOrHide`, `TabsVim_RenameBuffer`, `TabsVim_FzfOpenInTab`. Simple one-liner native commands (`:tabnew`, `:sp`, `:tabonly`, `:tabedit <cfile>`, etc.) are documented as direct vimrc mappings — no wrapper is exposed. | ||
| - A new SPEC `key-binding.md` documents the public function API, direct native-command wiring patterns, and trade-off notes (`g<number>`, `<C-]>`). | ||
| - The existing `tabs.vim` SPEC is updated to reference the key-binding SPEC and mark the refactor feature. | ||
| - Existing users who relied on the removed bindings must add them to their vimrc — the key-binding SPEC provides a ready-made example block. | ||
|
jesse23 marked this conversation as resolved.
|
||
| - No behavior changes: the logic inside each function is unchanged; only the binding wiring moves to the user. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| # SPEC: Key Binding | ||
|
|
||
| **Last Updated:** 2026-04-04 | ||
|
|
||
| --- | ||
|
|
||
| ## Description | ||
|
|
||
| Defines the keybinding contract for `tabs.vim`: what the plugin installs out-of-the-box (OOTB), what public functions it exposes for users to bind themselves, and the recommended vimrc wiring pattern. | ||
|
|
||
| The plugin intentionally keeps OOTB bindings minimal to avoid conflicts with user keymaps and other plugins. All operations are exposed as stable `TabsVim_*` public functions. Users adopt the ones they want and bind them to their preferred keys. | ||
|
|
||
| **Persona:** Vim/Neovim users who want tab-management features without the plugin imposing a full keymap. | ||
|
|
||
| --- | ||
|
|
||
| ## OOTB Behavior | ||
|
|
||
| The plugin installs **no user-facing keybindings** by default. Tab navigation is already covered by Vim natively (`gt` / `gT` / `<count>gt`). | ||
|
|
||
| The one OOTB behavior is **mouse drag-and-drop file opening**: dropping a file from a file manager into the terminal opens it in a new tab. This is implemented via bracketed-paste terminal sequences (`t_BE`/`t_BD`) and internal synthetic key aliases (`<F30>`/`<F31>`) — these are not user-facing mappings. It activates only in terminal Vim (not GVim) on Vim 8.0.0210+. | ||
|
|
||
| --- | ||
|
|
||
| ## Public Function API | ||
|
|
||
| Only operations with real logic are exposed as `TabsVim_*` functions. Simple one-liner native commands (`:tabnew`, `:sp`, `:tabonly`, `:only`, `:tabedit <cfile>`, `let @+ = expand("%:p")`) are documented as direct vimrc mappings below — no wrapper needed. | ||
|
|
||
| ### Terminal Operations | ||
|
|
||
| | Function | Description | | ||
| |----------|-------------| | ||
| | `TabsVim_ToggleHorizTerm()` | Toggle a persistent horizontal split terminal (below, 15 rows) | | ||
| | `TabsVim_ToggleVertTerm()` | Toggle a persistent vertical split terminal (right, 80 cols) | | ||
| | `TabsVim_NewTabTerm()` | Open a new terminal in its own tab | | ||
|
jesse23 marked this conversation as resolved.
|
||
|
|
||
| ### Window / Buffer Operations | ||
|
|
||
| | Function | Description | | ||
| |----------|-------------| | ||
| | `TabsVim_CloseOrHide()` | Close the current window; if last window, prompt to quit Vim; if a terminal buffer, toggle-hide it instead | | ||
| | `TabsVim_RenameBuffer()` | Prompt to rename the current buffer | | ||
|
|
||
|
jesse23 marked this conversation as resolved.
|
||
| ### FZF Integration | ||
|
|
||
| | Function | Description | | ||
| |----------|-------------| | ||
| | `TabsVim_FzfOpenInTab()` | Open fzf file picker with `tabedit` as the sink (requires fzf.vim) | | ||
|
|
||
|
jesse23 marked this conversation as resolved.
|
||
| --- | ||
|
|
||
| ## Recommended vimrc Wiring | ||
|
|
||
| Copy and adapt the block below. Remove any line you don't use. | ||
|
|
||
| ```vim | ||
| " ── Tab navigation ──────────────────────────────────────────────────────────── | ||
| " Native: gt / gT / <count>gt — no plugin binding needed. | ||
| nnoremap <silent> <leader>wt :tabnew<CR> | ||
| nnoremap <silent> <leader>x :call TabsVim_CloseOrHide()<CR> | ||
| nnoremap <silent> <leader>X :tabonly<CR> | ||
|
|
||
| " Direct tab jumps: <leader>1 … <leader>9 (native <count>gt) | ||
| for s:i in range(1, 9) | ||
| execute 'nnoremap <silent> <leader>' . s:i . ' ' . s:i . 'gt' | ||
| endfor | ||
|
|
||
| " ── Window / split — native commands ───────────────────────────────────────── | ||
| nnoremap <silent> <leader>ws :sp<CR> | ||
| nnoremap <silent> <leader>wv :vsp<CR> | ||
| nnoremap <silent> <leader>wm :only<CR> | ||
| nnoremap <silent> <leader>wr :call TabsVim_RenameBuffer()<CR> | ||
|
|
||
| " ── Terminal ────────────────────────────────────────────────────────────────── | ||
| nnoremap <silent> <leader>ts :call TabsVim_ToggleHorizTerm()<CR> | ||
| nnoremap <silent> <leader>tv :call TabsVim_ToggleVertTerm()<CR> | ||
| nnoremap <silent> <leader>tt :call TabsVim_NewTabTerm()<CR> | ||
|
|
||
| " ── File operations — native commands ──────────────────────────────────────── | ||
| nnoremap <silent> gF :tabedit <cfile><CR> | ||
| nnoremap <silent> <leader>fy :let @+ = expand("%:p")<CR> | ||
| nnoremap <silent> <leader>ft :call TabsVim_FzfOpenInTab()<CR> | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Keybinding Notes | ||
|
|
||
| ### `g<number>` for direct tab jumps | ||
|
|
||
| `<number>gt` (e.g. `3gt`) is native Vim and requires no plugin function. If you want a shorter shorthand, `g1`–`g9` are unbound in stock Vim and generally free — but `g0` is taken (go to first character of the screen line), so never bind `g0`. | ||
|
|
||
| ```vim | ||
| for s:i in range(1, 9) | ||
| execute 'nnoremap <silent> g' . s:i . ' ' . s:i . 'gt' | ||
| endfor | ||
| ``` | ||
|
|
||
| This is a pure vimrc concern — no plugin function is needed. | ||
|
|
||
| ### `<C-]>` for terminal navigation | ||
|
|
||
| The primary use case is quickly exiting terminal mode to normal mode so you can scroll, copy, and perform window operations (splits, focus changes, etc.). Vim's native way to do this is `<C-\><C-n>`, which is an awkward two-key chord. `<C-]>` is a natural single-key replacement: | ||
|
|
||
| ```vim | ||
| tnoremap <C-]> <C-\><C-n> " exit terminal mode → normal mode (scroll, copy, splits) | ||
| ``` | ||
|
|
||
| This mapping is **safe** — `<C-]>` has no meaningful default binding in terminal mode. | ||
|
|
||
| **Why `<C-]>` and not `<C-[>`?** `<C-[>` is the ASCII equivalent of `<Esc>` and most terminals send an identical byte sequence for both — Vim cannot distinguish them. Using `<C-[>` as a terminal-exit binding would conflict with any `<Esc>` mapping and produce unreliable behavior across terminal emulators. `<C-]>` sends a distinct byte (`0x1D`) that terminals reliably deliver as-is. | ||
|
|
||
| The companion normal-mode mapping is optional and carries a trade-off: | ||
|
|
||
| ```vim | ||
| nnoremap <C-]> i " re-enter terminal insert mode from normal mode | ||
| ``` | ||
|
|
||
| In normal mode, `<C-]>` is the native "jump to tag under cursor" (ctags / cscope / LSP). Override it only if you do not rely on tag jumping. If you do use tags, leave this one out and use `i` or `a` to re-enter terminal insert mode manually. | ||
|
|
||
| --- | ||
|
|
||
| ## Features | ||
|
|
||
| | Feature | Description | ADR | Done? | | ||
| |---------|-------------|-----|-------| | ||
| | **No OOTB keybindings** | Plugin installs zero keymaps; mouse DnD infrastructure only | ADR-004 | ✅ | | ||
| | **Public function API** | All operations promoted to `TabsVim_*` public functions | ADR-004 | ✅ | | ||
| | **Example vimrc block** | Ready-made mapping block for users to copy into vimrc | — | ✅ | | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.