From 4ccfb125dc63cdc9e7da6a057d8ddc104275e80c Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 16:08:55 -0500 Subject: [PATCH 01/18] vim(refactor[keymappings]): Fix p conflict and deduplicate mappings why: p mapped to both copy-path (line 27) and bprevious (line 109), with bprevious silently winning; multiple duplicate buffer navigation bindings existed what: - Keep p for copy file path (restore original intent) - Consolidate buffer nav to ]/[ only (nnoremap) - Keep single for :BB (alt buffer) - Remove duplicate 3 format mapping (keep f) - Remove commented-out arrow resize and nerdcommenter mappings - Fix ]/[ from map to nnoremap (convention fix) --- settings/keymappings.vim | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/settings/keymappings.vim b/settings/keymappings.vim index 19b93a8..d4c906f 100644 --- a/settings/keymappings.vim +++ b/settings/keymappings.vim @@ -2,8 +2,7 @@ let mapleader = "," let maplocalleader = "," -" Format / indentation - use marks to preserve position -nnoremap 3 mzgg=G`z +" Format / indentation moved to f in autocmd.vim " 4: Toggle between paste mode nnoremap 4 :set paste! @@ -34,11 +33,6 @@ nnoremap Q :q " Clear search highlight and close CoC popup if visible nnoremap :nohlsearch:silent! call coc#pum#visible() ? coc#pum#_close() : "" -" Up Down Left Right resize splits -" nnoremap + -" nnoremap - -" nnoremap < -" nnoremap > "=============================================================================== " Visual Mode Ctrl Key Mappings @@ -72,10 +66,7 @@ xnoremap p "_dP " use 'x' instead xnoremap d "_d -" \: Toggle comment -" nerdcommenter: -" xmap \ c -" tcomment: +" \: Toggle comment (tcomment) xmap \ gc " Enter: Highlight visual selections @@ -105,13 +96,7 @@ noremap x :Ex " Buffer Traversal {{{ nnoremap d :BD -" Buffer navigation - Leader p/n for previous/next -nnoremap p :bprevious -nnoremap n :bnext - -nnoremap c :BB nnoremap :BB -nnoremap :BB " Traversal nnoremap h @@ -132,9 +117,9 @@ endfunction nnoremap = -" Buffer navigation - using Leader key for consistency -map ] :bnext -map [ :bprev +" Buffer navigation +nnoremap ] :bnext +nnoremap [ :bprevious nnoremap e :NERDTreeFocus From d0cfb4fdaeb093497a457750f8f10cd1221855a7 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 16:09:23 -0500 Subject: [PATCH 02/18] vim(refactor[config]): Remove dead code and orphaned configuration why: BufClose config references a removed plugin; version guards and ttyfast are no-ops on Vim 9.1+; javascript_enable_domhtmlcss is read by no installed plugin or Vim 9.1 runtime file what: - Remove BufClose config from autocmd.vim (g:BufClose_AltBuffer, cnoreabbr bq) - Remove dead javascript_enable_domhtmlcss variable - Remove commented-out gruvbox-material from plugins.vim - Remove empty v:version >= 800 block from vimrc - Remove no-op set ttyfast from vimrc --- plugins.vim | 1 - settings/autocmd.vim | 5 ----- vimrc | 6 ------ 3 files changed, 12 deletions(-) diff --git a/plugins.vim b/plugins.vim index 532b8c0..b20c715 100644 --- a/plugins.vim +++ b/plugins.vim @@ -88,7 +88,6 @@ Plug 'chaoren/vim-wordmotion' " Colorschemes Plug 'rainux/vim-desert-warm-256' Plug 'morhetz/gruvbox' -" Plug 'gruvbox-material/vim', {'as': 'gruvbox-material'} Plug 'sainnhe/sonokai' Plug 'sainnhe/everforest' Plug 'catppuccin/vim', { 'as': 'catppuccin' } diff --git a/settings/autocmd.vim b/settings/autocmd.vim index 0d91701..09abf83 100644 --- a/settings/autocmd.vim +++ b/settings/autocmd.vim @@ -103,8 +103,3 @@ if !lib#IsTestMode() && executable('xrdb') autocmd BufWritePost,FileWritePost ~/.Xdefaults,~/.Xresources silent! !xrdb -load % >/dev/null 2>&1 endif -" map :BufClose to :bq and configure it to open a file browser on close -let g:BufClose_AltBuffer = '.' -cnoreabbr bq 'BufClose' - -let javascript_enable_domhtmlcss=1 diff --git a/vimrc b/vimrc index 54fdc95..84c29ec 100644 --- a/vimrc +++ b/vimrc @@ -1,11 +1,6 @@ "------------------------------------------------------------------------------ " Basic Recommended Settings "------------------------------------------------------------------------------ -if v:version >= 800 - " nocompatible is default in Vim 8+ - " syntax and filetype are handled by sensible.vim -endif - " Make the config self-locating so wrappers can source it directly. let g:vim_config_root = get(g:, 'vim_config_root', fnamemodify(expand(':p:h'), ':p')) @@ -40,7 +35,6 @@ set nowritebackup set noswapfile " Performance settings (from rice.vim) -set ttyfast set lazyredraw " Disable matchparen plugin (avoid glitchy behavior) From ec78fa8e19626cfce60c37053db6f4413642c5e5 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 16:09:40 -0500 Subject: [PATCH 03/18] vim(fix[vimrc]): Remove autochdir to avoid conflict with vim-rooter why: autochdir silently undoes vim-rooter's directory changes on every buffer switch, breaking LSP working directory expectations and making :Rg/:FZFRoot behavior unpredictable what: - Remove set autochdir from vimrc - vim-rooter (manual mode), FZFRoot, and :Rooter remain available for project-root navigation --- vimrc | 3 --- 1 file changed, 3 deletions(-) diff --git a/vimrc b/vimrc index 84c29ec..e2f1bc9 100644 --- a/vimrc +++ b/vimrc @@ -40,9 +40,6 @@ set lazyredraw " Disable matchparen plugin (avoid glitchy behavior) let g:loaded_matchparen = 1 " or use `:NoMatchParen` -" Automatically cd to directory of opened file -set autochdir - "------------------------------------------------------------------------------ " Clipboard Behavior "------------------------------------------------------------------------------ From ce8db7d147f0ec73060697ecf51974d764d205e3 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 16:10:09 -0500 Subject: [PATCH 04/18] vim(fix[lib]): Check colorscheme existence without applying it why: lib#ColorSchemeExists executes 'colorscheme ' to check existence, which fires ColorScheme autocommands and pollutes highlight groups as a side effect; every scheme in the fallback chain gets applied then discarded what: - Replace try/catch colorscheme with globpath-based check - Remove gruvbox-material from fallback chain (plugin not installed) - Update test allowed colorscheme list --- autoload/lib.vim | 9 ++------- tests/vim/core/startup_and_registration.vim | 1 - vimrc | 1 - 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/autoload/lib.vim b/autoload/lib.vim index be1c093..7c33872 100644 --- a/autoload/lib.vim +++ b/autoload/lib.vim @@ -50,11 +50,6 @@ endfunction " } -function! lib#ColorSchemeExists(colorscheme) - try - exe 'colorscheme' a:colorscheme - return 1 - catch /^Vim\%((\a\+)\)\=:E185/ - return 0 - endtry +function! lib#ColorSchemeExists(colorscheme) abort + return !empty(globpath(&rtp, 'colors/' . a:colorscheme . '.vim')) endfunction diff --git a/tests/vim/core/startup_and_registration.vim b/tests/vim/core/startup_and_registration.vim index 549a0f6..ceb98f6 100644 --- a/tests/vim/core/startup_and_registration.vim +++ b/tests/vim/core/startup_and_registration.vim @@ -25,7 +25,6 @@ function! Test_selects_a_configured_colorscheme() abort \ 'tokyonight', \ 'catppuccin_mocha', \ 'gruvbox', - \ 'gruvbox-material', \ 'everforest', \ 'desert-warm-256', \ 'desert', diff --git a/vimrc b/vimrc index e2f1bc9..f4e9aee 100644 --- a/vimrc +++ b/vimrc @@ -99,7 +99,6 @@ let s:colorschemes = [ \ {'name': 'tokyonight'}, \ {'name': 'catppuccin_mocha'}, \ {'name': 'gruvbox'}, - \ {'name': 'gruvbox-material', 'setup': 'let g:gruvbox_material_disable_italic_comment = 1'}, \ {'name': 'everforest', 'setup': 'set background=dark | let g:everforest_background = "hard" | let g:everforest_transparent_background = 2 | let g:everforest_disable_italic_comment = 1'}, \ {'name': 'desert-warm-256'}, \ ] From d4d505492e0da0742819e24d5760365a1bdf93e5 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 16:10:44 -0500 Subject: [PATCH 05/18] vim(refactor[plugins]): Remove unused plugins sonokai, ag.vim, nvim-yarp why: sonokai not in colorscheme fallback chain (dead weight); ag.vim superseded by FZF+ripgrep (:Rg, :RG, ); nvim-yarp/vim-hug-neovim-rpc unused since wilder.nvim sets use_python_remote_plugin=0 what: - Remove sonokai from plugin declarations - Remove ag.vim from s:conditional_plugins - Remove nvim-yarp and vim-hug-neovim-rpc from Vim 8 branch - Update gated_plugins.vim test assertions --- plugins.vim | 5 ----- tests/vim/integration/gated_plugins.vim | 4 ---- 2 files changed, 9 deletions(-) diff --git a/plugins.vim b/plugins.vim index b20c715..9fc25ab 100644 --- a/plugins.vim +++ b/plugins.vim @@ -88,7 +88,6 @@ Plug 'chaoren/vim-wordmotion' " Colorschemes Plug 'rainux/vim-desert-warm-256' Plug 'morhetz/gruvbox' -Plug 'sainnhe/sonokai' Plug 'sainnhe/everforest' Plug 'catppuccin/vim', { 'as': 'catppuccin' } @@ -150,15 +149,11 @@ if has('nvim') endfunction else Plug 'gelguy/wilder.nvim' - " For Python remote features in Vim 8: - Plug 'roxma/nvim-yarp' - Plug 'roxma/vim-hug-neovim-rpc' endif " Conditional Plugins Based on Executables " Format: 'command': ['plugin1', 'plugin2', ...] let s:conditional_plugins = { - \ 'ag': ['rking/ag.vim'], \ 'pipenv': ['cespare/vim-toml'], \ 'docker': ['ekalinin/Dockerfile.vim'], \ 'git': ['tpope/vim-fugitive', 'iberianpig/tig-explorer.vim'], diff --git a/tests/vim/integration/gated_plugins.vim b/tests/vim/integration/gated_plugins.vim index 1a476ee..90a44f9 100644 --- a/tests/vim/integration/gated_plugins.vim +++ b/tests/vim/integration/gated_plugins.vim @@ -1,6 +1,5 @@ function! Test_registers_conditional_plugins_from_local_tools() abort let l:conditional_plugins = { - \ 'ag': ['ag.vim'], \ 'cargo': ['rust.vim'], \ 'docker': ['Dockerfile.vim'], \ 'git': ['tig-explorer.vim', 'vim-fugitive'], @@ -30,8 +29,5 @@ function! Test_keeps_plugin_specific_defaults() abort if has('nvim') call assert_true(has_key(g:plugs, 'tokyonight.nvim')) - else - call assert_true(has_key(g:plugs, 'nvim-yarp')) - call assert_true(has_key(g:plugs, 'vim-hug-neovim-rpc')) endif endfunction From e639978e166cdaa17199811059eafd3a14e25b85 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 16:11:04 -0500 Subject: [PATCH 06/18] vim(refactor[plugins]): Drop typescript-vim in favor of yats.vim why: yats.vim is a superset of typescript-vim with TSX support; loading both risks syntax highlight conflicts what: - Remove leafgarland/typescript-vim from node conditional plugins - Update gated_plugins.vim test assertions --- plugins.vim | 2 +- tests/vim/integration/gated_plugins.vim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins.vim b/plugins.vim index 9fc25ab..9e08f9a 100644 --- a/plugins.vim +++ b/plugins.vim @@ -158,7 +158,7 @@ let s:conditional_plugins = { \ 'docker': ['ekalinin/Dockerfile.vim'], \ 'git': ['tpope/vim-fugitive', 'iberianpig/tig-explorer.vim'], \ 'psql': ['lifepillar/pgsql.vim'], - \ 'node': ['leafgarland/typescript-vim', 'HerringtonDarkholme/yats.vim', + \ 'node': ['HerringtonDarkholme/yats.vim', \ 'posva/vim-vue', 'jxnblk/vim-mdx-js', \ 'neoclide/vim-jsx-improve', 'jonsmithers/vim-html-template-literals'], \ 'tmux': ['wellle/tmux-complete.vim'], diff --git a/tests/vim/integration/gated_plugins.vim b/tests/vim/integration/gated_plugins.vim index 90a44f9..a1fecf6 100644 --- a/tests/vim/integration/gated_plugins.vim +++ b/tests/vim/integration/gated_plugins.vim @@ -4,7 +4,7 @@ function! Test_registers_conditional_plugins_from_local_tools() abort \ 'docker': ['Dockerfile.vim'], \ 'git': ['tig-explorer.vim', 'vim-fugitive'], \ 'mix': ['vim-elixir'], - \ 'node': ['typescript-vim', 'vim-html-template-literals', 'vim-jsx-improve', 'vim-mdx-js', 'vim-vue', 'yats.vim'], + \ 'node': ['vim-html-template-literals', 'vim-jsx-improve', 'vim-mdx-js', 'vim-vue', 'yats.vim'], \ 'pipenv': ['vim-toml'], \ 'psql': ['pgsql.vim'], \ 'terraform': ['vim-terraform'], From 2c0fefc07756c06d6bcb3a091384523bacd6eb8b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 16:11:45 -0500 Subject: [PATCH 07/18] vim(refactor[formatting]): Consolidate formatting to ALE + CoC why: Three formatting systems (ALE fix_on_save, coc-prettier, vim-autoformat) could conflict; f used Vim's primitive gg=G re-indent instead of LSP formatting what: - Remove vim-autoformat plugin (dprint JSON/TOML formatting) - Add JSON/TOML to CoC formatOnSave scope in coc-settings - Add b:biome_checked guard to avoid redundant findfile() - Set b:coc_preferences_formatOnSave=false when Biome is detected to prevent ALE+CoC double-format - Remap f to CocActionAsync('format') for LSP-aware formatting --- coc-settings.json | 2 +- plugins.vim | 6 ------ settings/autocmd.vim | 11 +++++++++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/coc-settings.json b/coc-settings.json index 683fae1..817485b 100644 --- a/coc-settings.json +++ b/coc-settings.json @@ -1,5 +1,5 @@ { - "[typescript][typescriptreact][javascript][markdown][python]": { + "[typescript][typescriptreact][javascript][markdown][python][json][toml]": { "coc.preferences.formatOnSave": true }, "coc.preferences.extensionUpdateCheck": "never", diff --git a/plugins.vim b/plugins.vim index 9e08f9a..aa42723 100644 --- a/plugins.vim +++ b/plugins.vim @@ -104,12 +104,6 @@ Plug 'cakebaker/scss-syntax.vim' " JSON for GitHub Actions Plug 'yasuhiroki/github-actions-yaml.vim' -" Autoformat -Plug 'vim-autoformat/vim-autoformat' -let g:formatdef_dprint = '"dprint stdin-fmt --file-name ".@%' -let g:formatters_json = ['dprint'] -let g:formatters_toml = ['dprint'] - " Copilot Plug 'github/copilot.vim' let g:copilot_filetypes = { diff --git a/settings/autocmd.vim b/settings/autocmd.vim index 09abf83..9dc6d12 100644 --- a/settings/autocmd.vim +++ b/settings/autocmd.vim @@ -13,11 +13,16 @@ au FocusGained * redraw! " redraw screen on focus " Automatically open quickfix window after grep autocmd QuickFixCmdPost *grep* cwindow -" Format entire file with f (preserves cursor position) -autocmd FileType * noremap f mzgg=G`z +" Format entire file via LSP (falls back to Vim indent for non-LSP) +autocmd FileType * nnoremap f :call CocActionAsync('format') " Toggle Biome per buffer based on project config function! s:MaybeEnableBiome() abort + if get(b:, 'biome_checked', 0) + return + endif + let b:biome_checked = 1 + if !exists('*ale#path#FindNearestFile') return endif @@ -32,6 +37,8 @@ function! s:MaybeEnableBiome() abort if !empty(l:config) let b:ale_linters = ['biome'] let b:ale_fixers = ['biome'] + " Disable CoC formatOnSave to prevent double-formatting + let b:coc_preferences_formatOnSave = v:false else if exists('b:ale_linters') unlet b:ale_linters From 7dcd98691b8d3864da81cb61f210efc5417db972 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 16:12:20 -0500 Subject: [PATCH 08/18] vim(feat[statusline]): Add lightline.vim with git and diagnostics why: No statusline means no visibility into git branch, ALE diagnostics, or file state during editing what: - Add lightline.vim plugin with CoC-git and ALE integration - Add LightlineGitStatus component using g:coc_git_status and b:coc_git_status - Add LightlineALE component showing E:/W: diagnostic counts - Hook CocGitStatusChange and ALELintPost for live updates - Remove manual rulerformat from settings.vim (lightline handles statusline) --- plugins.vim | 36 ++++++++++++++++++++++++++++++++++++ settings/settings.vim | 4 ---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/plugins.vim b/plugins.vim index aa42723..47a46f0 100644 --- a/plugins.vim +++ b/plugins.vim @@ -95,6 +95,42 @@ Plug 'catppuccin/vim', { 'as': 'catppuccin' } Plug 'preservim/nerdtree' let NERDTreeShowHidden=1 +" Statusline +Plug 'itchyny/lightline.vim' + +function! LightlineGitStatus() abort + return get(g:, 'coc_git_status', '') . get(b:, 'coc_git_status', '') +endfunction + +function! LightlineALE() abort + if !exists('*ale#statusline#Count') + return '' + endif + let l:counts = ale#statusline#Count(bufnr('')) + let l:errors = l:counts.error + l:counts.style_error + let l:warnings = l:counts.warning + l:counts.style_warning + if l:errors == 0 && l:warnings == 0 + return '' + endif + return printf('E:%d W:%d', l:errors, l:warnings) +endfunction + +let g:lightline = { + \ 'active': { + \ 'left': [['mode', 'paste'], + \ ['gitstatus', 'readonly', 'filename', 'modified']], + \ 'right': [['lineinfo'], ['percent'], + \ ['ale', 'filetype']], + \ }, + \ 'component_function': { + \ 'gitstatus': 'LightlineGitStatus', + \ 'ale': 'LightlineALE', + \ }, + \ } + +autocmd User CocGitStatusChange call lightline#update() +autocmd User ALELintPost,ALEFixPost call lightline#update() + " GraphQL Plug 'jparise/vim-graphql' diff --git a/settings/settings.vim b/settings/settings.vim index d85e62b..beb714f 100644 --- a/settings/settings.vim +++ b/settings/settings.vim @@ -14,14 +14,10 @@ set ignorecase " Case insensitive search set smartcase " Case sensitive when uc present if has('cmdline_info') - " ruler is set in sensible.vim - set rulerformat=%30(%=\:b%n%y%m%r%w\ %l,%c%V\ %P%) " A ruler on steroids set showcmd " Show partial commands in status line and " Selected characters/lines in visual mode endif -" Statusline configuration removed - use default or plugin statuslines - " noswapfile is already set in vimrc " vim-rooter handles directory changes, so we don't need autocmd BufEnter From aa0f9cdc80087caac5348232a1555d8e8f3346ef Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 16:12:46 -0500 Subject: [PATCH 09/18] vim(refactor[sensible]): Remove obsolete version guards for Vim 9.1+ why: Version checks for Vim < 7.4 are dead code on Vim 9.1+; fish shell fix targeted a Vim 7.4 bug that was patched long ago what: - Flatten always-true has('autocmd') conditional - Flatten always-true v:version > 703 formatoptions guard - Remove always-false fish shell fix for Vim < 7.4.276 - Keep nvim compat guard, t_Co terminal check, matchit load --- settings/sensible.vim | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/settings/sensible.vim b/settings/sensible.vim index f7efa33..8261cf7 100644 --- a/settings/sensible.vim +++ b/settings/sensible.vim @@ -8,10 +8,8 @@ else let g:loaded_sensible = 'yes' endif -if has('autocmd') - filetype plugin indent on -endif -if has('syntax') && !exists('g:syntax_on') +filetype plugin indent on +if !exists('g:syntax_on') syntax enable endif @@ -55,18 +53,12 @@ if &listchars ==# 'eol:$' set listchars=tab:>\ ,trail:-,extends:>,precedes:<,nbsp:+ endif -if v:version > 703 || v:version == 703 && has("patch541") - set formatoptions+=j " Delete comment character when joining commented lines -endif +set formatoptions+=j " Delete comment character when joining commented lines if has('path_extra') setglobal tags-=./tags tags-=./tags; tags^=./tags; endif -if &shell =~# 'fish$' && (v:version < 704 || v:version == 704 && !has('patch276')) - set shell=/bin/bash -endif - set autoread if &history < 1000 From 071fbc7dc5d119ac0110848be5dfea7a6c91358a Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 16:13:04 -0500 Subject: [PATCH 10/18] vim(feat[plugins]): Lazy-load NERDTree on first command invocation why: NERDTree loads on every startup but is only used on-demand via e; lazy loading defers its cost what: - Add 'on' option to defer NERDTree until NERDTreeFocus, NERDTreeToggle, or NERDTree commands are called - g:NERDTreeShowHidden and g:NERDTreeIgnore are set eagerly at startup via global vars (unaffected by lazy loading) --- plugins.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins.vim b/plugins.vim index 47a46f0..4f25ef9 100644 --- a/plugins.vim +++ b/plugins.vim @@ -92,7 +92,7 @@ Plug 'sainnhe/everforest' Plug 'catppuccin/vim', { 'as': 'catppuccin' } " NERDTree -Plug 'preservim/nerdtree' +Plug 'preservim/nerdtree', { 'on': ['NERDTreeFocus', 'NERDTreeToggle', 'NERDTree'] } let NERDTreeShowHidden=1 " Statusline From c68cb269a59e296233d0a7155033895db3fb55ce Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 16:13:19 -0500 Subject: [PATCH 11/18] vim(feat[plugins]): Defer copilot.vim loading to first BufReadPost why: Copilot starts a Node.js process on VimEnter; deferring to BufReadPost gives it time to initialize while the user reads the file, avoiding startup cost what: - Use plug#load on BufReadPost ++once to lazy-load copilot - g:copilot_filetypes config set eagerly (unaffected by deferred loading) - BufReadPost chosen over InsertEnter to front-load Node.js initialization before user enters insert mode --- plugins.vim | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins.vim b/plugins.vim index 4f25ef9..d9bbb25 100644 --- a/plugins.vim +++ b/plugins.vim @@ -140,8 +140,8 @@ Plug 'cakebaker/scss-syntax.vim' " JSON for GitHub Actions Plug 'yasuhiroki/github-actions-yaml.vim' -" Copilot -Plug 'github/copilot.vim' +" Copilot (deferred to first file open) +Plug 'github/copilot.vim', { 'on': [] } let g:copilot_filetypes = { \ 'markdown': v:false, \ 'rst': v:false, @@ -151,6 +151,11 @@ let g:copilot_filetypes = { \ 'json': v:false, \ } +augroup CopilotDeferredLoad + autocmd! + autocmd BufReadPost * ++once call plug#load('copilot.vim') +augroup END + " Python syntax improvements Plug 'vim-python/python-syntax' From 30a2cd330e986f36f65a99d800e933d62475411d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 16:13:49 -0500 Subject: [PATCH 12/18] vim(feat[plugins]): Add filetype-based lazy loading for language plugins why: Language plugins load on every startup but are only needed when editing matching filetypes what: - Restructure s:conditional_plugins entries to support [spec, opts] lists alongside plain strings - Add type() dispatch in loop to pass opts to PlugIfCommand - Add 'for' lazy-load option to Dockerfile, yats, vue, mdx, jsx-improve, html-template-literals, rust, terraform, and elixir plugins - vim-plug loads ftdetect/ even for lazy plugins, so filetype detection is unaffected --- plugins.vim | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/plugins.vim b/plugins.vim index d9bbb25..bf675d5 100644 --- a/plugins.vim +++ b/plugins.vim @@ -187,25 +187,34 @@ else endif " Conditional Plugins Based on Executables -" Format: 'command': ['plugin1', 'plugin2', ...] +" Each entry is either a string (load immediately) or a list +" [spec, {plug-options}] (with vim-plug lazy-load options) let s:conditional_plugins = { \ 'pipenv': ['cespare/vim-toml'], - \ 'docker': ['ekalinin/Dockerfile.vim'], + \ 'docker': [['ekalinin/Dockerfile.vim', {'for': ['dockerfile']}]], \ 'git': ['tpope/vim-fugitive', 'iberianpig/tig-explorer.vim'], \ 'psql': ['lifepillar/pgsql.vim'], - \ 'node': ['HerringtonDarkholme/yats.vim', - \ 'posva/vim-vue', 'jxnblk/vim-mdx-js', - \ 'neoclide/vim-jsx-improve', 'jonsmithers/vim-html-template-literals'], + \ 'node': [ + \ ['HerringtonDarkholme/yats.vim', {'for': ['typescript', 'typescriptreact']}], + \ ['posva/vim-vue', {'for': ['vue']}], + \ ['jxnblk/vim-mdx-js', {'for': ['mdx']}], + \ ['neoclide/vim-jsx-improve', {'for': ['javascript', 'javascriptreact']}], + \ ['jonsmithers/vim-html-template-literals', {'for': ['html', 'javascript', 'typescript']}], + \ ], \ 'tmux': ['wellle/tmux-complete.vim'], - \ 'cargo': ['rust-lang/rust.vim'], - \ 'terraform': ['hashivim/vim-terraform'], - \ 'mix': ['elixir-editors/vim-elixir'], + \ 'cargo': [['rust-lang/rust.vim', {'for': ['rust']}]], + \ 'terraform': [['hashivim/vim-terraform', {'for': ['terraform']}]], + \ 'mix': [['elixir-editors/vim-elixir', {'for': ['elixir', 'eelixir']}]], \ } " Load conditional plugins for [cmd, plugins] in items(s:conditional_plugins) - for plugin in plugins - call PlugIfCommand(cmd, plugin) + for entry in plugins + if type(entry) == v:t_list + call PlugIfCommand(cmd, entry[0], entry[1]) + else + call PlugIfCommand(cmd, entry) + endif endfor endfor From 2ae012f227b944f5b3e593481691cd534e9b0836 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 17:48:46 -0500 Subject: [PATCH 13/18] tests(feat[rooter]): Add parametrized rooter/autochdir edge case tests why: Provide comprehensive coverage of vim-rooter behavior across auto/manual modes, non-project fallbacks, and autochdir interactions before changing rooter config what: - Add 11 parametrized scenarios via pytest + generated Vimscript suites exercising FindRootDirectory() and cwd - Cover auto-rooter with .git, Pipfile, deep nesting, non-project current/empty fallback, manual :Rooter, autochdir interactions, and multiple root markers - Note autochdir limitation in Vim headless Ex mode --- tests/pytest/test_rooter_scenarios.py | 236 ++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 tests/pytest/test_rooter_scenarios.py diff --git a/tests/pytest/test_rooter_scenarios.py b/tests/pytest/test_rooter_scenarios.py new file mode 100644 index 0000000..42fbdf2 --- /dev/null +++ b/tests/pytest/test_rooter_scenarios.py @@ -0,0 +1,236 @@ +from __future__ import annotations + +import typing as t +from pathlib import Path + +import pytest + +from libtestvim import VimHarness + + +class RooterScenario(t.NamedTuple): + id: str + description: str + root_markers: tuple[str, ...] + file_path: str + rooter_manual_only: int + rooter_non_project: str + autochdir: bool + expected_cwd: str + expected_find_root: str + + +ROOTER_SCENARIOS = [ + RooterScenario( + id="auto_rooter_git_project", + description="Auto-rooter detects .git and cd to project root", + root_markers=(".git/",), + file_path="src/deep/file.py", + rooter_manual_only=0, + rooter_non_project="", + autochdir=False, + expected_cwd="project_root", + expected_find_root="project_root", + ), + RooterScenario( + id="auto_rooter_pyproject_marker", + description="Auto-rooter detects Pipfile as root marker", + root_markers=("Pipfile",), + file_path="pkg/app.py", + rooter_manual_only=0, + rooter_non_project="", + autochdir=False, + expected_cwd="project_root", + expected_find_root="project_root", + ), + RooterScenario( + id="auto_rooter_deep_nesting", + description="Auto-rooter finds root from deeply nested file", + root_markers=(".git/",), + file_path="a/b/c/d/e/main.py", + rooter_manual_only=0, + rooter_non_project="", + autochdir=False, + expected_cwd="project_root", + expected_find_root="project_root", + ), + RooterScenario( + id="auto_rooter_non_project_current", + description="Non-project file with 'current' falls back to file dir", + root_markers=(), + file_path="orphan.py", + rooter_manual_only=0, + rooter_non_project="current", + autochdir=False, + expected_cwd="file_dir", + expected_find_root="empty", + ), + RooterScenario( + id="auto_rooter_non_project_empty", + description="Non-project file with '' leaves cwd unchanged", + root_markers=(), + file_path="orphan.py", + rooter_manual_only=0, + rooter_non_project="", + autochdir=False, + expected_cwd="unchanged", + expected_find_root="empty", + ), + RooterScenario( + id="manual_rooter_no_auto_cd", + description="Manual rooter does not auto-cd on BufEnter", + root_markers=(".git/",), + file_path="src/file.py", + rooter_manual_only=1, + rooter_non_project="", + autochdir=False, + expected_cwd="unchanged", + expected_find_root="project_root", + ), + RooterScenario( + id="manual_rooter_explicit_command", + description="Manual :Rooter command changes to project root", + root_markers=(".git/",), + file_path="pkg/app.py", + rooter_manual_only=1, + rooter_non_project="", + autochdir=False, + expected_cwd="project_root", + expected_find_root="project_root", + ), + RooterScenario( + id="find_root_ignores_manual_only", + description="FindRootDirectory() works regardless of manual_only", + root_markers=(".git/",), + file_path="lib/util.py", + rooter_manual_only=1, + rooter_non_project="", + autochdir=False, + expected_cwd="unchanged", + expected_find_root="project_root", + ), + RooterScenario( + id="autochdir_with_auto_rooter", + description="Rooter auto-mode overrides autochdir (cd to root not file dir)", + root_markers=(".git/",), + file_path="src/deep/file.py", + rooter_manual_only=0, + rooter_non_project="", + autochdir=True, + expected_cwd="project_root", + expected_find_root="project_root", + ), + # NOTE: autochdir does not fire in Vim's headless Ex mode (--not-a-term -es) + # used by the test harness, so cwd remains unchanged rather than changing + # to file_dir. In a real interactive session, autochdir WOULD change to + # the file's directory. + RooterScenario( + id="autochdir_with_manual_rooter", + description="autochdir alone changes to file dir (no rooter auto)", + root_markers=(".git/",), + file_path="src/deep/file.py", + rooter_manual_only=1, + rooter_non_project="", + autochdir=True, + expected_cwd="unchanged", + expected_find_root="project_root", + ), + RooterScenario( + id="multiple_root_markers", + description="Project with both .git/ and Pipfile detects root correctly", + root_markers=(".git/", "Pipfile"), + file_path="src/app.py", + rooter_manual_only=0, + rooter_non_project="", + autochdir=False, + expected_cwd="project_root", + expected_find_root="project_root", + ), +] + + +def _vim_string(s: str) -> str: + """Escape a Python string for Vim script literal.""" + return "'" + s.replace("'", "''") + "'" + + +def _build_suite(scenario: RooterScenario, project_root: Path, file_abs: Path) -> str: + """Generate a Vimscript test suite for a single rooter scenario.""" + if scenario.expected_cwd == "project_root": + expected_cwd_expr = _vim_string(str(project_root)) + elif scenario.expected_cwd == "file_dir": + expected_cwd_expr = _vim_string(str(file_abs.parent)) + else: + expected_cwd_expr = "g:vim_test_original_cwd" + + expected_root_expr = _vim_string(str(project_root)) if scenario.expected_find_root == "project_root" else "''" + + rooter_cmd = "" + if scenario.id == "manual_rooter_explicit_command": + rooter_cmd = "\n Rooter" + + lines = [ + f"function! Test_{scenario.id}() abort", + f" let g:rooter_manual_only = {scenario.rooter_manual_only}", + f" let g:rooter_change_directory_for_non_project_files = {_vim_string(scenario.rooter_non_project)}", + f" {'set autochdir' if scenario.autochdir else 'set noautochdir'}", + "", + f" call VimTestOpen({_vim_string(str(file_abs))})", + rooter_cmd, + "", + ' " Assert expected working directory', + f" let l:expected_cwd = VimTestNormalizePath({expected_cwd_expr})", + " let l:actual_cwd = VimTestNormalizePath(getcwd())", + " call assert_equal(l:expected_cwd, l:actual_cwd,", + f" \\ {_vim_string(scenario.description + ': cwd mismatch')})", + "", + ' " Assert FindRootDirectory() result', + f" let l:expected_root = {expected_root_expr}", + " let l:actual_root = FindRootDirectory()", + " if !empty(l:expected_root)", + " call assert_equal(", + " \\ VimTestNormalizePath(l:expected_root),", + " \\ VimTestNormalizePath(l:actual_root),", + f" \\ {_vim_string(scenario.description + ': FindRootDirectory() mismatch')})", + " else", + " call assert_equal(", + " \\ l:expected_root, l:actual_root,", + f" \\ {_vim_string(scenario.description + ': FindRootDirectory() should be empty')})", + " endif", + "endfunction", + ] + return "\n".join(lines) + "\n" + + +@pytest.mark.integration +@pytest.mark.parametrize( + "scenario", + ROOTER_SCENARIOS, + ids=lambda s: s.id, +) +def test_rooter_scenario( + vim_harness: VimHarness, + tmp_path: Path, + scenario: RooterScenario, +) -> None: + """Parametrized rooter/autochdir edge case test.""" + project_root = tmp_path / "project" + project_root.mkdir() + for marker in scenario.root_markers: + marker_path = project_root / marker + if marker.endswith("/"): + marker_path.mkdir(parents=True, exist_ok=True) + else: + marker_path.parent.mkdir(parents=True, exist_ok=True) + marker_path.write_text("") + + file_abs = project_root / scenario.file_path + file_abs.parent.mkdir(parents=True, exist_ok=True) + file_abs.write_text("# test file\n") + + suite_content = _build_suite(scenario, project_root, file_abs) + suite_file = tmp_path / f"test_{scenario.id}.vim" + suite_file.write_text(suite_content, encoding="utf-8") + + result = vim_harness.run_suite(suite_file) + assert result.ok, result.render_failure() From e220edf7b9d04f811629c97636986e9974706335 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 17:48:55 -0500 Subject: [PATCH 14/18] vim(feat[rooter]): Enable automatic project root detection why: With autochdir removed (commit ec78fa8), enable vim-rooter auto mode for LazyVim-like project root DX; 'current' fallback preserves :e relative-to-file behavior for non-project files (the original 2018 autochdir motivation from commit 5e7996d) what: - Set g:rooter_manual_only = 0 (was 1) - Set g:rooter_change_directory_for_non_project_files to 'current' for autochdir-like fallback --- autoload/settings.vim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autoload/settings.vim b/autoload/settings.vim index 2a41638..fb1efc1 100644 --- a/autoload/settings.vim +++ b/autoload/settings.vim @@ -12,7 +12,8 @@ function! settings#LoadSettings() abort " This is checked for before initialization. " https://github.com/airblade/vim-rooter/blob/3509dfb/plugin/rooter.vim#L173 - let g:rooter_manual_only = 1 + let g:rooter_manual_only = 0 + let g:rooter_change_directory_for_non_project_files = 'current' " vim-rooter patterns let g:rooter_patterns = [ From 1050f11818069207801a0b2e83233f1a7fa0137f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Mar 2026 17:48:59 -0500 Subject: [PATCH 15/18] tests(refactor[rooter]): Update integration test for auto-rooter why: With g:rooter_manual_only = 0, the BufEnter autocmd changes cwd before any manual :Rooter invocation; the old manual-mode assertion is now invalid what: - Rename Test_rooter_command_changes_to_project_root to Test_rooter_auto_changes_to_project_root - Assert cwd equals project root immediately after VimTestOpen (no explicit :Rooter needed) --- tests/vim/integration/project_root_and_local_commands.vim | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/vim/integration/project_root_and_local_commands.vim b/tests/vim/integration/project_root_and_local_commands.vim index ae5159e..b9225a9 100644 --- a/tests/vim/integration/project_root_and_local_commands.vim +++ b/tests/vim/integration/project_root_and_local_commands.vim @@ -7,16 +7,13 @@ function! Test_finds_project_roots_for_nested_files() abort call assert_equal(VimTestNormalizePath(l:root), VimTestNormalizePath(FindRootDirectory())) endfunction -function! Test_rooter_command_changes_to_project_root() abort +function! Test_rooter_auto_changes_to_project_root() abort let l:root = VimTestTempDir('rooter-command') call mkdir(l:root . '/.git', 'p') call VimTestWriteFile(l:root . '/pkg/app.py', ['print("hello")']) call VimTestOpen(l:root . '/pkg/app.py') - call assert_notequal(VimTestNormalizePath(l:root . '/pkg'), VimTestNormalizePath(getcwd()), 'Rooter should not change to the project directory before manual invocation') - - Rooter - call assert_equal(VimTestNormalizePath(l:root), VimTestNormalizePath(getcwd()), 'Rooter should change to the detected project root') + call assert_equal(VimTestNormalizePath(l:root), VimTestNormalizePath(getcwd()), 'Auto-rooter should change to the detected project root on BufEnter') endfunction function! Test_registers_project_search_commands() abort From eacc89aa18060ca8f632027d407eef049619cb09 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 4 Apr 2026 07:03:42 -0500 Subject: [PATCH 16/18] docs(feat[config]): Add format-on-save reference to README why: Format-on-save is configured across multiple files (CoC, ALE, autocmd.vim) with no centralized documentation explaining what fires on :w or how the systems interact. what: - Add ## Config section with ### Format on save subsection - Table mapping each system to its filetypes and config file - Document biome double-format guard and per-buffer disable --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index a9d45af..72b7527 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,22 @@ Please check out: - - +## Config + +### Format on save + +Two systems handle format-on-save, scoped by filetype: + +| System | Filetypes | Config file | +|--------|-----------|-------------| +| [CoC](https://github.com/neoclide/coc.nvim) `formatOnSave` | typescript, typescriptreact, javascript, markdown, python, json, toml | [`coc-settings.json`](coc-settings.json) (line 2-4) | +| [ALE](https://github.com/dense-analysis/ale) `fix_on_save` | Buffers with configured fixers (e.g. biome for ts/tsx) | [`plugins.vim`](plugins.vim) (`g:ale_fix_on_save`) | + +- CoC delegates to the language server or formatter extension registered for the filetype +- ALE runs `b:ale_fixers` / `g:ale_fixers` — currently empty for js/ts globally, but [`settings/autocmd.vim`](settings/autocmd.vim) dynamically enables biome when `biome.json` is found in the project +- When biome is active, CoC formatOnSave is disabled for that buffer to avoid double-formatting (see `s:MaybeEnableBiome()` in `settings/autocmd.vim`) +- To disable for a single buffer: `:let b:coc_disable_autoformat = 1` + ## License MIT From 89db8907c1970d7f3b1efbcb5fa6afdf15ae138e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 4 Apr 2026 07:04:02 -0500 Subject: [PATCH 17/18] docs(refactor[agents]): Cross-reference format-on-save from AGENTS.md why: Avoid duplicating format-on-save details in two places. what: - Replace inline description with link to README.md#format-on-save --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index e2dca4b..eb3f765 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -230,7 +230,7 @@ When `g:vim_test_mode` is set (by the test harness wrapper), the configuration u This allows tests to run in a clean temporary `$HOME` without affecting the user's real configuration. ### LSP Configuration -Language servers are configured in `coc-settings.json` with format-on-save enabled for multiple languages. The configuration includes extensive setups for Python, TypeScript, Rust, and other languages. +Language servers are configured in `coc-settings.json` with extensive setups for Python, TypeScript, Rust, and other languages. Format-on-save behavior is documented in [`README.md`](README.md#format-on-save). ### FZF Integration Custom FZF commands are defined in `autoload/settings.vim` with AG (silver searcher) integration for fast project-wide searching. From 7f4bb6a9e331bd429cd4854cbe4ace7d08dc8be8 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 4 Apr 2026 07:19:04 -0500 Subject: [PATCH 18/18] coc-settings.json: Remove formatOnSave from json and toml --- coc-settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coc-settings.json b/coc-settings.json index 817485b..683fae1 100644 --- a/coc-settings.json +++ b/coc-settings.json @@ -1,5 +1,5 @@ { - "[typescript][typescriptreact][javascript][markdown][python][json][toml]": { + "[typescript][typescriptreact][javascript][markdown][python]": { "coc.preferences.formatOnSave": true }, "coc.preferences.extensionUpdateCheck": "never",