diff --git a/docs/adrs/002.tabs-vim.themes.md b/docs/adrs/002.tabs-vim.themes.md new file mode 100644 index 0000000..952c425 --- /dev/null +++ b/docs/adrs/002.tabs-vim.themes.md @@ -0,0 +1,48 @@ +# 002. Themes + +**SPEC:** tabs.vim +**Status:** Accepted +**Last Updated:** 2026-04-04 + +--- + +## Decision + +Expose mode colors via a single `g:tabs_vim_colors` dict. Each key is a lowercase mode name (`normal`, `insert`, `visual`, `replace`, `command`, `terminal`); each value is a four-element list `[guifg, guibg, ctermfg, ctermbg]`. Users may override any subset; omitted modes fall back to the built-in Dracula defaults. The dict is read once at plugin load after `colorscheme` is applied. + +## Context + +The plugin hard-codes a Dracula palette directly in `hi` statements. Users with different colorschemes (gruvbox, tokyonight, catppuccin, etc.) have no way to adapt the tab bar without patching the plugin source. This blocks adoption for anyone not on Dracula. + +The solution must be: +- **Simple to configure** — one variable, no theme files, no function calls +- **Partial** — users shouldn't have to restate all six modes to change one +- **Familiar** — borrow a convention from the Vim plugin ecosystem + +## Considered Options + +| Option | Pros | Cons | +|--------|------|------| +| **`g:tabs_vim_colors` dict with `[fg, bg, ctermfg, ctermbg]` arrays** *(chosen)* | Compact; partial overrides; same format as lightline.vim palettes | Positional array is less self-documenting than named keys | +| Named-key dicts per mode (`{'fg': ..., 'bg': ...}`) | More readable | ~3× more verbose for a full override; no established Vim convention | +| Theme names (`g:tabs_vim_theme = 'gruvbox'`) | One-line for known themes | Requires bundling per-theme files; doesn't cover custom palettes | +| Let users define `TabsVim_*` highlight groups directly | Maximum control | No discovery; plugin must not clobber user hi definitions set before load | + +The positional array format is the de facto standard in the Vim statusline plugin ecosystem (lightline.vim uses the identical `[fg, bg, ctermfg, ctermbg]` layout for its palette dicts). Familiarity outweighs the self-documentation benefit of named keys. + +## Reference: Comparable Plugins + +| Plugin | Config mechanism | +|--------|-----------------| +| **lightline.vim** | `g:lightline = {'colorscheme': 'wombat'}` + palette dicts with `[fg, bg, ctermfg, ctermbg]` arrays | +| **vim-airline** | `g:airline_theme = 'gruvbox'` (named theme files in `autoload/airline/themes/`) | +| **buftabline** | `g:buftabline_indicators = 1` style flags; no color config (relies on existing `TabLine`/`TabLineSel` groups) | + +tabs.vim sits between lightline (full palette control, many modes) and buftabline (no control). The chosen approach borrows lightline's array format but without a named-theme layer — appropriate for a focused plugin with only six mode colors. + +## Consequences + +- `g:tabs_vim_colors` is the single configuration point for all mode colors +- The plugin applies `hi` statements from the merged dict (user overrides + defaults) at load time +- If a user sets `TabsVim_*` highlight groups themselves *after* the plugin loads, those are respected as-is (plugin does not re-apply on colorscheme change in this phase) +- Phase 4 (Polish) may add `ColorScheme` autocmd re-application, but that is out of scope here diff --git a/docs/specs/tabs.vim.md b/docs/specs/tabs.vim.md index e64c832..f0e09c0 100644 --- a/docs/specs/tabs.vim.md +++ b/docs/specs/tabs.vim.md @@ -129,7 +129,38 @@ Tabs plugin plays well with others (fzf, Fern, vim-fugitive). It doesn't reimple All keybindings are configurable. Users can disable features (e.g., if they don't use tabs) without source code changes. **4. Integrated Theming** -Dracula color scheme integration built-in; tab bar includes mode indicator (Normal/Insert/Visual/Replace/Command/Terminal) with context-aware highlighting. +Dracula color scheme is the built-in default; tab bar includes mode indicator (Normal/Insert/Visual/Replace/Command/Terminal) with context-aware highlighting. Users may override any or all mode colors via `g:tabs_vim_colors`. + +### Color Configuration Contract + +Mode colors are configurable via the `g:tabs_vim_colors` global dict. Each key is a lowercase mode name; each value is a four-element list `[guifg, guibg, ctermfg, ctermbg]`. Unspecified modes fall back to the built-in Dracula defaults. The dict is read once at plugin load time (after `colorscheme` is applied). + +**Supported keys:** `normal`, `insert`, `visual`, `replace`, `command`, `terminal` + +**Default (Dracula palette):** + +```vim +let g:tabs_vim_colors = { + \ 'normal': ['#282a36', '#bd93f9', 235, 141], + \ 'insert': ['#282a36', '#50fa7b', 235, 84 ], + \ 'visual': ['#282a36', '#ffb86c', 235, 215], + \ 'replace': ['#282a36', '#ff5555', 235, 203], + \ 'command': ['#282a36', '#bd93f9', 235, 141], + \ 'terminal': ['#282a36', '#8be9fd', 235, 117], +\ } +``` + +**Partial override example (gruvbox-style):** + +```vim +" Only change normal and insert; other modes keep Dracula defaults +let g:tabs_vim_colors = { + \ 'normal': ['#282828', '#d79921', 235, 172], + \ 'insert': ['#282828', '#b8bb26', 235, 142], +\ } +``` + +**Design rationale:** This format is borrowed from lightline.vim's palette convention — compact, no named theme indirection, trivially composable. See ADR-002 for alternatives considered. --- @@ -175,7 +206,7 @@ Dracula color scheme integration built-in; tab bar includes mode indicator (Norm | **Direct Tab Jump** | Jump to tab 1-9 with `[1-9]` | ⬜ | — | | **Tab Creation** | Create new tab with `wt`, via file picker with `ft` | ⬜ | — | | **Tab Closing** | Close current tab or all but current with `x` / `X` | ⬜ | — | -| **Tab Appearance** | Dracula theme integration, minimal visual overhead | ⬜ | — | +| **Tab Appearance** | Dracula default theme; user-configurable colors via `g:tabs_vim_colors` | ⬜ | ADR-002 | | **File Tree Integration** | Open files in tabs from Fern file browser (`t` key) | ⬜ | — | | **Git Integration** | Open git-related output (diffs, logs) in tabs | ⬜ | — | | **Terminal in Tabs** | Spawn terminal windows in new tabs (separate from splits) | ⬜ | — | diff --git a/plugin/tabs.vim b/plugin/tabs.vim index fb4845f..e8a0873 100644 --- a/plugin/tabs.vim +++ b/plugin/tabs.vim @@ -175,21 +175,35 @@ endif """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " ══════════════════════════════════════════════════════════════════════════════ -" MODE COLORS — edit guibg to restyle each mode pill (fg is always dark #282a36) -hi TabsVim_Normal guifg=#282a36 guibg=#bd93f9 ctermfg=235 ctermbg=141 gui=bold cterm=bold -hi TabsVim_Insert guifg=#282a36 guibg=#50fa7b ctermfg=235 ctermbg=84 gui=bold cterm=bold -hi TabsVim_Visual guifg=#282a36 guibg=#ffb86c ctermfg=235 ctermbg=215 gui=bold cterm=bold -hi TabsVim_Replace guifg=#282a36 guibg=#ff5555 ctermfg=235 ctermbg=203 gui=bold cterm=bold -hi TabsVim_Command guifg=#282a36 guibg=#bd93f9 ctermfg=235 ctermbg=141 gui=bold cterm=bold -hi TabsVim_Terminal guifg=#282a36 guibg=#8be9fd ctermfg=235 ctermbg=117 gui=bold cterm=bold -hi TabsVim_Accent guifg=#282a36 guibg=#bd93f9 ctermfg=235 ctermbg=141 gui=bold cterm=bold -" Active tab text — same hue as mode pill, no background -hi TabsVim_SelNormal guifg=#bd93f9 guibg=NONE ctermbg=NONE ctermfg=141 gui=bold cterm=bold -hi TabsVim_SelInsert guifg=#50fa7b guibg=NONE ctermbg=NONE ctermfg=84 gui=bold cterm=bold -hi TabsVim_SelVisual guifg=#ffb86c guibg=NONE ctermbg=NONE ctermfg=215 gui=bold cterm=bold -hi TabsVim_SelReplace guifg=#ff5555 guibg=NONE ctermbg=NONE ctermfg=203 gui=bold cterm=bold -hi TabsVim_SelCommand guifg=#bd93f9 guibg=NONE ctermbg=NONE ctermfg=141 gui=bold cterm=bold -hi TabsVim_SelTerminal guifg=#8be9fd guibg=NONE ctermbg=NONE ctermfg=117 gui=bold cterm=bold +" MODE COLORS — override any mode via g:tabs_vim_colors (see docs/specs/tabs.vim.md) +" Each entry: [guifg, guibg, ctermfg, ctermbg] +let s:tabs_vim_defaults = { + \ 'normal': ['#282a36', '#bd93f9', 235, 141], + \ 'insert': ['#282a36', '#50fa7b', 235, 84 ], + \ 'visual': ['#282a36', '#ffb86c', 235, 215], + \ 'replace': ['#282a36', '#ff5555', 235, 203], + \ 'command': ['#282a36', '#bd93f9', 235, 141], + \ 'terminal': ['#282a36', '#8be9fd', 235, 117], +\ } + +function! s:ApplyColors() abort + let l:user = exists('g:tabs_vim_colors') ? g:tabs_vim_colors : {} + for [l:key, l:Cap] in [['normal', 'Normal'], ['insert', 'Insert'], ['visual', 'Visual'], + \ ['replace', 'Replace'], ['command', 'Command'], ['terminal', 'Terminal']] + let l:ov = get(l:user, l:key, []) + let l:col = (type(l:ov) == type([]) && len(l:ov) == 4) ? l:ov : s:tabs_vim_defaults[l:key] + execute printf('hi TabsVim_%s guifg=%s guibg=%s ctermfg=%s ctermbg=%s gui=bold cterm=bold', + \ l:Cap, l:col[0], l:col[1], l:col[2], l:col[3]) + execute printf('hi TabsVim_Sel%s guifg=%s guibg=NONE ctermfg=%s ctermbg=NONE gui=bold cterm=bold', + \ l:Cap, l:col[1], l:col[3]) + endfor + let l:ov = get(l:user, 'normal', []) + let l:n = (type(l:ov) == type([]) && len(l:ov) == 4) ? l:ov : s:tabs_vim_defaults['normal'] + execute printf('hi TabsVim_Accent guifg=%s guibg=%s ctermfg=%s ctermbg=%s gui=bold cterm=bold', + \ l:n[0], l:n[1], l:n[2], l:n[3]) +endfunction + +call s:ApplyColors() " ══════════════════════════════════════════════════════════════════════════════ set showtabline=2