Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions docs/adrs/002.tabs-vim.themes.md
Original file line number Diff line number Diff line change
@@ -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
35 changes: 33 additions & 2 deletions docs/specs/tabs.vim.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand Down Expand Up @@ -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 `<leader>[1-9]` | ⬜ | — |
| **Tab Creation** | Create new tab with `<leader>wt`, via file picker with `<leader>ft` | ⬜ | — |
| **Tab Closing** | Close current tab or all but current with `<leader>x` / `<leader>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) | ⬜ | — |
Expand Down
44 changes: 29 additions & 15 deletions plugin/tabs.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Comment thread
jesse23 marked this conversation as resolved.
endfunction

call s:ApplyColors()
" ══════════════════════════════════════════════════════════════════════════════

set showtabline=2
Expand Down
Loading