Compare commits

..

No commits in common. "825633d62341d900d8bc346834d805a8797f1245" and "9442aa0499d4864dc0bf236c609a79050e273823" have entirely different histories.

6 changed files with 423 additions and 1205 deletions

234
.vimrc
View file

@ -73,15 +73,9 @@ set wildmenu
" Make wildmenu behave like similar to Bash completion " Make wildmenu behave like similar to Bash completion
set wildmode=list:longest set wildmode=list:longest
" Case-insensitive filename completion in wildmenu (spf13, YADR) " There are certain files that we would never want to edit with Vim
set wildignorecase " Wildmenu will ignore files with these extensions
" Files and directories to exclude from wildmenu and :find
set wildignore=*.docx,*.jpg,*.png,*.gif,*.pdf,*.pyc,*.exe,*.flv,*.img,*.xlsx set wildignore=*.docx,*.jpg,*.png,*.gif,*.pdf,*.pyc,*.exe,*.flv,*.img,*.xlsx
set wildignore+=*/node_modules/*,*/.git/*,*/__pycache__/*,*/dist/*,*/build/*
" Recursive :find across the project tree (works with wildignore above)
set path+=**
" Enable mouse support " Enable mouse support
set mouse=a set mouse=a
@ -125,11 +119,6 @@ set novisualbell
set t_vb= set t_vb=
set tm=500 set tm=500
" Separate timeout for keycodes (arrow keys, Esc) vs leader sequences
" ttimeoutlen=10 eliminates the ~500ms ESC lag in terminal Vim (vim-sensible)
set ttimeout
set ttimeoutlen=10
" Enable 256 colors palette in Gnome Terminal " Enable 256 colors palette in Gnome Terminal
if $COLORTERM == 'gnome-terminal' if $COLORTERM == 'gnome-terminal'
set t_Co=256 set t_Co=256
@ -143,9 +132,6 @@ if has("gui_running")
set guitablabel=%M\ %t set guitablabel=%M\ %t
endif endif
" Show last line partially instead of replacing it with @@@ (vim-sensible)
set display+=lastline
" Use Unix as the standard file type " Use Unix as the standard file type
set ffs=unix,dos,mac set ffs=unix,dos,mac
@ -221,7 +207,6 @@ Plug 'dhruvasagar/vim-prosession' " Better session management
Plug 'tpope/vim-unimpaired' " Handy bracket mappings Plug 'tpope/vim-unimpaired' " Handy bracket mappings
Plug 'wellle/targets.vim' " Additional text objects Plug 'wellle/targets.vim' " Additional text objects
Plug 'honza/vim-snippets' " Snippet collection Plug 'honza/vim-snippets' " Snippet collection
Plug 'christoomey/vim-tmux-navigator' " Seamless vim/tmux pane navigation
" ===== Native LSP (vim-lsp: works without Node.js, Vim 8.0+ only) ===== " ===== Native LSP (vim-lsp: works without Node.js, Vim 8.0+ only) =====
" Used as fallback when CoC/Node.js is unavailable " Used as fallback when CoC/Node.js is unavailable
@ -302,14 +287,6 @@ endif
" => Text, Tab and Indent Related " => Text, Tab and Indent Related
" ============================================================================ " ============================================================================
" Visible whitespace characters (toggled with <F6>)
" TTY: ASCII equivalents; GUI/modern terminal: Unicode symbols
if g:is_tty
set listchars=tab:>-,trail:.,extends:>,precedes:<,nbsp:_
else
set listchars=tab:→\ ,trail,extends:▸,precedes:◂,nbsp
endif
" Use spaces instead of tabs " Use spaces instead of tabs
set expandtab set expandtab
@ -346,8 +323,11 @@ nmap <leader>x :x<cr>
" Disable highlight when <leader><cr> is pressed " Disable highlight when <leader><cr> is pressed
map <silent> <leader><cr> :noh<cr> map <silent> <leader><cr> :noh<cr>
" Window navigation — owned by vim-tmux-navigator plugin (Ctrl+h/j/k/l works " Smart way to move between windows
" seamlessly across Vim splits and tmux panes; no manual maps needed here) map <C-j> <C-W>j
map <C-k> <C-W>k
map <C-h> <C-W>h
map <C-l> <C-W>l
" Close the current buffer (Bclose preserves window layout) " Close the current buffer (Bclose preserves window layout)
map <leader>bd :Bclose<cr> map <leader>bd :Bclose<cr>
@ -379,30 +359,17 @@ augroup END
" Opens a new tab with the current buffer's path " Opens a new tab with the current buffer's path
map <leader>te :tabedit <C-r>=expand("%:p:h")<cr>/ map <leader>te :tabedit <C-r>=expand("%:p:h")<cr>/
" Change window-local CWD to current file's directory (lcd = local, safer than cd) " Switch CWD to the directory of the open buffer
map <leader>cd :lcd %:p:h<cr>:pwd<cr> map <leader>wd :cd %:p:h<cr>:pwd<cr>
" Open built-in file browser (works on any Vim, no plugins needed — tpope)
nnoremap <leader>e :Explore<CR>
" Remap VIM 0 to first non-blank character " Remap VIM 0 to first non-blank character
map 0 ^ map 0 ^
" Reselect last pasted text (gV = visual select last paste) — spf13, YADR " Move a line of text using ALT+[jk] or Command+[jk] on mac
nnoremap gV `[v`]
" Command-line history navigation with Ctrl+p/n (amix, spf13)
cnoremap <C-p> <Up>
cnoremap <C-n> <Down>
" Move a line of text using ALT+[jk] (normal mode)
nmap <M-j> mz:m+<cr>`z nmap <M-j> mz:m+<cr>`z
nmap <M-k> mz:m-2<cr>`z nmap <M-k> mz:m-2<cr>`z
vmap <M-j> :m'>+<cr>`<my`>mzgv`yo`z
" Move selected lines up/down and re-indent (visual mode) — ThePrimeagen vmap <M-k> :m'<-2<cr>`>my`<mzgv`yo`z
" Overrides visual-J (join) and visual-K (keywordprg) — use normal mode for those
vnoremap J :m '>+1<CR>gv=gv
vnoremap K :m '<-2<CR>gv=gv
" Pressing ,ss will toggle and untoggle spell checking " Pressing ,ss will toggle and untoggle spell checking
map <leader>ss :setlocal spell!<cr> map <leader>ss :setlocal spell!<cr>
@ -422,9 +389,6 @@ nnoremap <F3> :set invnumber<CR>
" Toggle relative line numbers " Toggle relative line numbers
nnoremap <F4> :set invrelativenumber<CR> nnoremap <F4> :set invrelativenumber<CR>
" Toggle visible whitespace (tabs, trailing spaces, non-breaking spaces)
nnoremap <F6> :set list!<CR>
" Enable folding with the spacebar " Enable folding with the spacebar
nnoremap <space> za nnoremap <space> za
@ -434,9 +398,6 @@ nnoremap Y y$
" Disable accidental Ex mode " Disable accidental Ex mode
nnoremap Q <nop> nnoremap Q <nop>
" Exit insert mode without reaching for Escape (community standard)
inoremap jk <Esc>
" Keep visual selection after indent " Keep visual selection after indent
vnoremap < <gv vnoremap < <gv
vnoremap > >gv vnoremap > >gv
@ -445,25 +406,15 @@ vnoremap > >gv
nnoremap n nzzzv nnoremap n nzzzv
nnoremap N Nzzzv nnoremap N Nzzzv
" Search for visually selected text with // (hit // in visual mode)
vnoremap // y/\V<C-r>=escape(@",'/\')<CR><CR>
" <C-s> to save in normal and insert mode
" (for terminals: add 'stty -ixon' to your shell rc to disable XON/XOFF)
nnoremap <silent> <C-s> :w<CR>
inoremap <silent> <C-s> <Esc>:w<CR>a
" Center cursor after half-page scroll " Center cursor after half-page scroll
nnoremap <C-d> <C-d>zz nnoremap <C-d> <C-d>zz
nnoremap <C-u> <C-u>zz nnoremap <C-u> <C-u>zz
" System clipboard yank/paste (conditional: requires clipboard provider) " System clipboard yank (conditional: requires clipboard provider)
if has('clipboard') if has('clipboard')
nnoremap <leader>y "+y nnoremap <leader>y "+y
vnoremap <leader>y "+y vnoremap <leader>y "+y
nnoremap <leader>Y "+Y nnoremap <leader>Y "+Y
nnoremap <leader>p "+p
nnoremap <leader>P "+P
endif endif
" Quickfix list shortcuts ([q/]q from vim-unimpaired handles navigation) " Quickfix list shortcuts ([q/]q from vim-unimpaired handles navigation)
@ -534,7 +485,6 @@ endfunction
map <C-p> :call <SID>SmartFiles()<CR> map <C-p> :call <SID>SmartFiles()<CR>
map <leader>b :Buffers<CR> map <leader>b :Buffers<CR>
map <leader>rg :Rg<CR> map <leader>rg :Rg<CR>
map <leader>rG :Rg -F <C-r><C-w><CR>
map <leader>rt :Tags<CR> map <leader>rt :Tags<CR>
map <leader>gF :GFiles<CR> map <leader>gF :GFiles<CR>
@ -633,9 +583,9 @@ let g:ale_fixers = {
let g:ale_fix_on_save = !g:use_vimlsp let g:ale_fix_on_save = !g:use_vimlsp
let g:ale_sign_error = 'X' let g:ale_sign_error = 'X'
let g:ale_sign_warning = '!' let g:ale_sign_warning = '!'
let g:ale_lint_on_text_changed = 'normal' let g:ale_lint_on_text_changed = 'never'
let g:ale_lint_on_insert_leave = 1 let g:ale_lint_on_insert_leave = 0
let g:ale_lint_on_enter = 1 let g:ale_lint_on_enter = 0
" --- vim-go: disable built-in LSP/gopls — CoC (coc-go) handles all Go intelligence --- " --- vim-go: disable built-in LSP/gopls — CoC (coc-go) handles all Go intelligence ---
" vim-go's gopls conflicts with coc-go and causes E495 errors on startup " vim-go's gopls conflicts with coc-go and causes E495 errors on startup
@ -652,18 +602,16 @@ let g:go_highlight_fields = 1
let g:go_highlight_functions = 1 let g:go_highlight_functions = 1
let g:go_highlight_function_calls = 1 let g:go_highlight_function_calls = 1
" Navigate between errors: [e/]e (unimpaired convention: [ = prev, ] = next) " Navigate between errors: [e/]e (unimpaired style), <leader>aD for detail
nmap <silent> [e :ALEPrevious<cr> nmap <silent> [e :ALENext<cr>
nmap <silent> ]e :ALENext<cr> nmap <silent> ]e :ALEPrevious<cr>
nmap <silent> <leader>aD :ALEDetail<cr> nmap <silent> <leader>aD :ALEDetail<cr>
" --- Tagbar --- " --- Tagbar ---
nmap <F8> :TagbarToggle<CR> nmap <F8> :TagbarToggle<CR>
nmap <leader>tt :TagbarToggle<CR>
" --- UndoTree --- " --- UndoTree ---
nnoremap <F5> :UndotreeToggle<CR> nnoremap <F5> :UndotreeToggle<CR>
nnoremap <leader>u :UndotreeToggle<CR>
" --- EasyMotion --- " --- EasyMotion ---
let g:EasyMotion_do_mapping = 0 " Disable default mappings let g:EasyMotion_do_mapping = 0 " Disable default mappings
@ -901,19 +849,6 @@ fun! CleanExtraSpaces()
call setreg('/', old_query) call setreg('/', old_query)
endfun endfun
" Disable auto-insertion of comment leaders when pressing Enter or o/O
" Must run at BufEnter because filetype plugins reset formatoptions per buffer
augroup ChopstickFormatOptions
autocmd!
autocmd BufEnter * setlocal formatoptions-=c formatoptions-=r formatoptions-=o
augroup END
" Auto-disable paste mode when leaving insert mode (prevents broken indentation)
augroup ChopstickPaste
autocmd!
autocmd InsertLeave * set nopaste
augroup END
augroup ChopstickCleanup augroup ChopstickCleanup
autocmd! autocmd!
" Run for real files only; skip special buffers (NERDTree, Startify, terminal, etc.) " Run for real files only; skip special buffers (NERDTree, Startify, terminal, etc.)
@ -936,16 +871,13 @@ augroup ChopstickFiletype
autocmd BufNewFile,BufRead *.tsx setlocal filetype=typescript.tsx autocmd BufNewFile,BufRead *.tsx setlocal filetype=typescript.tsx
" Python specific settings " Python specific settings
autocmd FileType python setlocal expandtab shiftwidth=4 tabstop=4 textwidth=88 colorcolumn=+1 autocmd FileType python setlocal expandtab shiftwidth=4 tabstop=4 colorcolumn=88
" JavaScript / TypeScript specific settings " JavaScript specific settings
autocmd FileType javascript,typescript setlocal expandtab shiftwidth=2 tabstop=2 textwidth=100 colorcolumn=+1 autocmd FileType javascript,typescript setlocal expandtab shiftwidth=2 tabstop=2
" Go specific settings (standard: no textwidth limit, but 120 is common) " Go specific settings
autocmd FileType go setlocal noexpandtab shiftwidth=4 tabstop=4 textwidth=120 colorcolumn=+1 autocmd FileType go setlocal noexpandtab shiftwidth=4 tabstop=4
" Rust specific settings
autocmd FileType rust setlocal expandtab shiftwidth=4 tabstop=4 textwidth=100 colorcolumn=+1
" HTML/CSS specific settings " HTML/CSS specific settings
autocmd FileType html,css setlocal expandtab shiftwidth=2 tabstop=2 autocmd FileType html,css setlocal expandtab shiftwidth=2 tabstop=2
@ -953,11 +885,11 @@ augroup ChopstickFiletype
" YAML specific settings " YAML specific settings
autocmd FileType yaml setlocal expandtab shiftwidth=2 tabstop=2 autocmd FileType yaml setlocal expandtab shiftwidth=2 tabstop=2
" Markdown specific settings (no line-length limit; wrap at window edge) " Markdown specific settings
autocmd FileType markdown setlocal wrap linebreak spell textwidth=0 colorcolumn=0 autocmd FileType markdown setlocal wrap linebreak spell tw=0
" Shell script settings " Shell script settings
autocmd FileType sh setlocal expandtab shiftwidth=2 tabstop=2 textwidth=80 colorcolumn=+1 autocmd FileType sh setlocal expandtab shiftwidth=2 tabstop=2
" Makefile settings (must use tabs) " Makefile settings (must use tabs)
autocmd FileType make setlocal noexpandtab shiftwidth=8 tabstop=8 autocmd FileType make setlocal noexpandtab shiftwidth=8 tabstop=8
@ -997,6 +929,9 @@ function! ToggleNumber()
endif endif
endfunc endfunc
" Toggle paste mode
map <leader>pp :setlocal paste!<cr>
" ============================================================================ " ============================================================================
" => Performance Optimization " => Performance Optimization
" ============================================================================ " ============================================================================
@ -1005,9 +940,6 @@ endfunc
set synmaxcol=200 set synmaxcol=200
set ttyfast set ttyfast
" Don't scan included files for completion — makes Ctrl+n/p much faster (vim-sensible)
set complete-=i
" Reduce updatetime for better user experience " Reduce updatetime for better user experience
set updatetime=300 set updatetime=300
@ -1035,10 +967,6 @@ endif
set exrc set exrc
set secure set secure
" Session options: exclude 'options' (stale plugin settings) and 'globals';
" include terminal buffers — compatible with vim-obsession/vim-prosession
set sessionoptions=blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal
" ============================================================================ " ============================================================================
" => Additional Engineering Utilities " => Additional Engineering Utilities
" ============================================================================ " ============================================================================
@ -1067,8 +995,8 @@ nnoremap <leader>so :source %<CR>
" Edit vimrc quickly " Edit vimrc quickly
nnoremap <leader>ev :edit $MYVIMRC<CR> nnoremap <leader>ev :edit $MYVIMRC<CR>
" Reload vimrc with confirmation echo " Reload vimrc
nnoremap <leader>sv :source $MYVIMRC<CR>:echo "vimrc reloaded"<CR> nnoremap <leader>sv :source $MYVIMRC<CR>
" Search and replace word under cursor " Search and replace word under cursor
nnoremap <leader>* :%s/\<<C-r><C-w>\>//g<Left><Left> nnoremap <leader>* :%s/\<<C-r><C-w>\>//g<Left><Left>
@ -1092,85 +1020,6 @@ augroup BWCCreateDir
autocmd BufWritePre * if !empty(expand('<afile>')) | call s:MkNonExDir(expand('<afile>'), +expand('<abuf>')) | endif autocmd BufWritePre * if !empty(expand('<afile>')) | call s:MkNonExDir(expand('<afile>'), +expand('<abuf>')) | endif
augroup END augroup END
" In-Vim quick reference cheat sheet — ,? opens it, q closes it
function! s:CheatSheet() abort
let l:name = '__ChopsticksCheatSheet__'
let l:winnr = bufwinnr(l:name)
if l:winnr > 0
execute l:winnr . 'wincmd w'
return
endif
execute 'botright new ' . l:name
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile
call setline(1, [
\ '=== chopsticks — Quick Reference ===',
\ '',
\ 'MODES (Vim is modal — the most important concept)',
\ ' Normal Default state. Navigate and run commands.',
\ ' Insert Type text. Enter: i/a/o Leave: Esc or jk',
\ ' Visual Select text. Enter: v/V Leave: Esc',
\ '',
\ 'SURVIVAL (learn these 4 first)',
\ ' Esc / jk Exit insert or visual mode — back to Normal',
\ ' :q! + Enter Quit without saving (emergency exit)',
\ ' ,x Save and quit',
\ ' ,w Save file | Ctrl+s Save (normal + insert)',
\ '',
\ 'NAVIGATION',
\ ' h j k l Left / Down / Up / Right',
\ ' Ctrl+p Fuzzy find file | Ctrl+n File tree',
\ ' Ctrl+o/i Jump back / forward in history',
\ ' ,, Switch to last file',
\ '',
\ 'SEARCH',
\ ' /text Search forward | n next | N prev',
\ ' // Search for visually selected text',
\ ' ,rg Search project contents (ripgrep)',
\ ' ,rG Ripgrep word under cursor',
\ ' ,* Replace word under cursor (file-wide)',
\ '',
\ 'CODE INTELLIGENCE',
\ ' gd Go to definition',
\ ' K Hover documentation',
\ ' [g / ]g Prev / next diagnostic (LSP)',
\ ' [e / ]e Prev / next ALE error',
\ ' ,ca Code action / auto-fix',
\ ' ,rn Rename symbol',
\ ' ,f Format selection | ,F Format whole file',
\ '',
\ 'EDITING',
\ ' i / a / o Insert before / after / on new line',
\ ' gc Toggle comment (works in visual too)',
\ ' u / Ctrl+r Undo / Redo',
\ ' ,p / ,P Paste from system clipboard',
\ ' ,y / ,Y Yank / line-yank to system clipboard',
\ ' s + 2 chars EasyMotion jump anywhere on screen',
\ '',
\ 'GIT',
\ ' ,gs Git status | ,gd Diff | ,gb Blame',
\ ' ,gc Commit | ,gp Push | ,gl Pull',
\ '',
\ 'WINDOWS / PANES',
\ ' Ctrl+h/j/k/l Move between Vim windows or tmux panes',
\ ' ,h / ,l Prev / next buffer',
\ ' ,tv / ,th Open terminal (vertical / horizontal)',
\ '',
\ 'TOOLS',
\ ' ,u Undo tree (visual history)',
\ ' ,tt Tagbar (code structure)',
\ ' ,o File outline (LSP symbols)',
\ '',
\ 'TIP: When confused, press Esc. Then press , and wait 500ms',
\ ' for an interactive guide to ALL keybindings.',
\ ' Re-open this sheet with ,?',
\ '',
\ '(press q to close)',
\ ])
setlocal nomodifiable readonly
nnoremap <buffer> <silent> q :bd<CR>
endfunction
nnoremap <silent> <leader>? :call <SID>CheatSheet()<CR>
" ============================================================================ " ============================================================================
" => Debugging Helpers " => Debugging Helpers
" ============================================================================ " ============================================================================
@ -1233,8 +1082,7 @@ function! LargeFileSettings()
setlocal eventignore+=FileType setlocal eventignore+=FileType
setlocal noswapfile setlocal noswapfile
setlocal syntax=OFF setlocal syntax=OFF
let b:ale_enabled = 0 echo "Large file (>10MB): syntax and undo disabled for performance."
echo "Large file (>10MB): syntax, undo, and linting disabled for performance."
endfunction endfunction
" ============================================================================ " ============================================================================
@ -1300,10 +1148,6 @@ if exists('g:plugs["vim-which-key"]')
let g:which_key_map[','] = 'last-file' let g:which_key_map[','] = 'last-file'
let g:which_key_map['y'] = 'clipboard-yank' let g:which_key_map['y'] = 'clipboard-yank'
let g:which_key_map['Y'] = 'clipboard-yank-line' let g:which_key_map['Y'] = 'clipboard-yank-line'
let g:which_key_map['p'] = 'clipboard-paste-after'
let g:which_key_map['P'] = 'clipboard-paste-before'
let g:which_key_map['u'] = 'undotree-toggle'
let g:which_key_map['?'] = 'cheat-sheet'
" [a]LE lint group ([e/]e navigate; <leader>aD for detail; <leader>ad for diagnostics) " [a]LE lint group ([e/]e navigate; <leader>aD for detail; <leader>ad for diagnostics)
let g:which_key_map['a'] = { let g:which_key_map['a'] = {
@ -1325,9 +1169,9 @@ if exists('g:plugs["vim-which-key"]')
\ 'f': 'copy-filename', \ 'f': 'copy-filename',
\ } \ }
" [e]dit / [e]xplore group " [e]dit group
let g:which_key_map['e'] = { let g:which_key_map['e'] = {
\ 'name': '+edit/explore', \ 'name': '+edit',
\ 'v': 'edit-vimrc', \ 'v': 'edit-vimrc',
\ } \ }
@ -1356,7 +1200,6 @@ if exists('g:plugs["vim-which-key"]')
\ 'name': '+search/refactor', \ 'name': '+search/refactor',
\ 'n': 'rename', \ 'n': 'rename',
\ 'g': 'ripgrep', \ 'g': 'ripgrep',
\ 'G': 'ripgrep-word-under-cursor',
\ 't': 'tags-search', \ 't': 'tags-search',
\ } \ }
@ -1375,7 +1218,7 @@ if exists('g:plugs["vim-which-key"]')
" [t]ab / [t]erminal group " [t]ab / [t]erminal group
let g:which_key_map['t'] = { let g:which_key_map['t'] = {
\ 'name': '+tab/terminal/tagbar', \ 'name': '+tab/terminal',
\ 'n': 'new-tab', \ 'n': 'new-tab',
\ 'o': 'tab-only', \ 'o': 'tab-only',
\ 'c': 'close-tab', \ 'c': 'close-tab',
@ -1384,7 +1227,6 @@ if exists('g:plugs["vim-which-key"]')
\ 'e': 'edit-in-tab', \ 'e': 'edit-in-tab',
\ 'v': 'terminal-vertical', \ 'v': 'terminal-vertical',
\ 'h': 'terminal-horizontal', \ 'h': 'terminal-horizontal',
\ 't': 'tagbar-toggle',
\ } \ }
" [w]orkspace / [w]indow / save group (also: <leader>w = fast save) " [w]orkspace / [w]indow / save group (also: <leader>w = fast save)
@ -1392,10 +1234,8 @@ if exists('g:plugs["vim-which-key"]')
\ 'name': '+save/window', \ 'name': '+save/window',
\ 'a': 'save-all', \ 'a': 'save-all',
\ 's': 'workspace-symbols', \ 's': 'workspace-symbols',
\ 'd': 'change-dir',
\ } \ }
" [c]hange-dir (standalone — <leader>cd changes window-local CWD)
let g:which_key_map['cd'] = 'change-local-dir'
endif endif
" ============================================================================ " ============================================================================

View file

@ -4,94 +4,6 @@ All notable changes to chopsticks are documented here.
--- ---
## [1.1.1] - 2026-04-09
Systematic absorption of best practices from amix/vimrc, tpope/vim-sensible,
ThePrimeagen, skwp/YADR, and spf13-vim — settings and mappings that appear
consistently across all top global configs but were missing here.
### Added
- **`set ttimeoutlen=10`** — eliminates the ~500ms ESC lag in terminal Vim; separates
keycode timeout from leader-key timeout (`timeoutlen` unchanged at 500ms)
- **`set display+=lastline`** — shows truncated long lines instead of replacing them with `@@@`
- **`set complete-=i`** — `Ctrl+n/p` no longer scans all included files; completion is instant
- **`set wildignorecase`** — case-insensitive filename completion in wildmenu and `:find`
- **`set path+=**`** — recursive `:find` across the project; wildignore excludes
`node_modules`, `__pycache__`, `dist`, `build`, `.git`
- **`set sessionoptions`** — removes `options` from saved sessions (prevents stale plugin
settings from contaminating restored sessions)
- **`set listchars`** — defines visible whitespace characters; TTY uses ASCII symbols,
modern terminals use Unicode (tab `→`, trail `·`, extends `▸`)
- **`F6`** — toggle visible whitespace on/off
- **`formatoptions-=cro`** on `BufEnter` — disables automatic comment-leader insertion
when pressing Enter or `o/O`; runs on BufEnter to override filetype plugins
- **`InsertLeave * set nopaste`** — auto-disables paste mode on leaving insert, preventing
permanently broken auto-indent
- **`colorcolumn=+1`** for all languages via `textwidth`:
Python 88, Go 120, JS/TS 100, Rust 100, Shell 80 (Markdown disabled)
- **`vnoremap J/K`** with `gv=gv` — move selected lines down/up and re-indent (ThePrimeagen)
- **`gV`** — re-select last pasted text (`\`[v\`]` — spf13, YADR)
- **`cnoremap <C-p>/<C-n>`** — navigate command-line history matching typed prefix (amix, spf13)
- **`<leader>e :Explore`** — open built-in Netrw file browser; works on any Vim without plugins
- **`<leader>cd`** — change window-local CWD to current file's directory (was `<leader>wd`)
- **`<leader>sv`** — reloads vimrc and echoes confirmation
### Changed
- `<leader>wd` renamed to `<leader>cd`; now uses `lcd` (window-local) instead of `cd` (global)
- `wildignore` expanded with `*/node_modules/*`, `*/__pycache__/*`, `*/dist/*`, `*/build/*`
---
## [1.1.0] - 2026-04-09
Ergonomics and automation overhaul: community-standard keybindings, seamless
tmux integration, an in-Vim cheat sheet, a beginner onboarding section, and
several correctness fixes from a systematic review.
### Added
- **`jk``Esc`** in insert mode — ergonomic escape without reaching for the key
- **`Ctrl+s` save** in normal and insert mode (add `stty -ixon` to shell rc to enable
in terminals that use XON/XOFF flow control)
- **`//` visual search** — search for visually selected text using `\V` very-nomagic escaping
- **`<leader>p` / `<leader>P`** — paste from system clipboard after/before cursor
- **`<leader>rG`** — ripgrep word under cursor with `-F` (literal, not regex)
- **`<leader>u`** — leader-key alias for UndoTree (complements `F5`)
- **`<leader>tt`** — leader-key alias for Tagbar (complements `F8`)
- **`,?` in-Vim cheat sheet** — opens a read-only buffer covering modes, survival
commands, search, code intelligence, git, and clipboard; press `q` to close
- **vim-tmux-navigator** plugin — `Ctrl+h/j/k/l` navigates seamlessly across Vim
splits and tmux panes without a prefix key
- **`install.sh` tmux step** — detects tmux and optionally appends the four
navigator `bind-key` lines to `~/.tmux.conf`; warns about `C-l`/screen-clear tradeoff
- **`install.sh` survival guide** — post-install output now shows the 4 essential
commands for first-time Vim users, plus the `stty -ixon` advisory
- **QUICKSTART.md Step 0** — new first section explaining Vim modes (Normal/Insert/Visual)
and 4 survival commands; makes the guide usable by users who have never opened Vim
- **`let b:ale_enabled = 0`** in `LargeFileSettings()` — ALE no longer spawns
linter subprocesses for files over 10 MB
### Changed
- **ALE lint triggers**`ale_lint_on_text_changed` changed from `'never'` to `'normal'`;
`ale_lint_on_insert_leave` and `ale_lint_on_enter` changed from `0` to `1` — diagnostics
now refresh on buffer enter and after edits settle in normal mode
- **`<C-h/j/k/l>` manual maps removed** — vim-tmux-navigator owns these keys at
plugin load time; the previous hand-rolled `<C-W>` maps were unreachable dead code
- **`<leader>pp` paste-mode toggle removed** — functionally identical to the existing
`F2` pastetoggle; its presence caused a 500 ms delay on every `<leader>p` paste
### Fixed
- **ALE navigation direction reversed**`[e` now correctly calls `ALEPrevious`
and `]e` calls `ALENext`, matching the vim-unimpaired `[`/`]` convention
- **`<leader>rG` regex metacharacter bug** — without `-F`, characters like `.` `*`
`(` in the cursor word were treated as regex, producing incorrect matches
---
## [1.0.0] - 2026-03-29 ## [1.0.0] - 2026-03-29
First stable release. Full-stack engineering environment out of the box — automatic First stable release. Full-stack engineering environment out of the box — automatic

View file

@ -2,39 +2,6 @@
Five minutes from zero to a working Vim engineering environment. Five minutes from zero to a working Vim engineering environment.
> **New to Vim?** Read Step 0 first — it takes 2 minutes and prevents the most
> common beginner frustration. Already know how Vim modes work? [Skip to Step 1](#step-1-install).
---
## Step 0: Vim Basics
> **When confused, press `Esc` until things feel normal again — then keep reading.**
Vim is **modal**: the keyboard behaves differently depending on which mode you are in.
Most people get stuck because they try to type text while in Normal mode.
### The Three Modes
| Mode | Purpose | How to enter | How to leave |
|------|---------|--------------|--------------|
| **Normal** | Navigate and run commands | Startup default | — (you're already here) |
| **Insert** | Type text | `i` before cursor, `a` after, `o` new line below | `Esc` or `jk` |
| **Visual** | Select text | `v` char-by-char, `V` whole lines | `Esc` |
### 4 Survival Commands
Learn these before anything else. They will get you out of every stuck situation.
| Command | Action |
|---------|--------|
| `Esc` or `jk` | Exit insert/visual mode — return to Normal |
| `:q!` then `Enter` | Force quit without saving (emergency exit) |
| `,x` | Save and quit |
| `,w` or `Ctrl+s` | Save the file |
Once in Normal mode, press `,?` to open a cheat sheet covering everything else.
--- ---
## Step 1: Install ## Step 1: Install
@ -99,22 +66,19 @@ This auto-detects and installs the correct language server for the current filet
--- ---
## The 12 Keys That Matter ## The 10 Keys That Matter
``` ```
, (pause 500ms) Show all keybindings (which-key) , (pause 500ms) Show all shortcuts
,? Open cheat sheet inside Vim
Esc / jk Exit insert mode → Normal (memorize this)
Ctrl+s Save (works in normal and insert mode)
Ctrl+p Fuzzy find file Ctrl+p Fuzzy find file
Ctrl+n Toggle file tree Ctrl+n Toggle file tree
gd Go to definition gd Go to definition
K Show documentation K Show documentation
[g / ]g Prev / next LSP diagnostic [g / ]g Prev / next diagnostic
,rn Rename symbol ,rn Rename symbol
,rG Search word under cursor (ripgrep) ,rg Search project contents
,gs Git status ,gs Git status
,w / ,x Save / Save+quit ,w / ,q Save / Quit
``` ```
--- ---
@ -233,51 +197,38 @@ colorscheme dracula " or: gruvbox, solarized, onedark
## Quick Reference Card ## Quick Reference Card
``` ```
BASICS (learn these first)
Esc / jk Exit insert mode → Normal
Ctrl+s Save (normal + insert mode)
:q! + Enter Emergency quit without saving
,? Open cheat sheet
FILES FILES
Ctrl+n File tree toggle Ctrl+n File tree toggle
Ctrl+p Fuzzy find file (git-aware) Ctrl+p Fuzzy find file (git-aware)
,b Search open buffers ,b Search open buffers
,rg Search file contents (ripgrep) ,rg Search file contents (ripgrep)
,rG Ripgrep word under cursor ,w Save | ,q Quit | ,x Save+quit
,w Save | ,q Quit | ,x Save+quit ,wa Save all buffers
,wa Save all buffers
,, Switch to last file
CODE CODE
gd Go to definition gd Go to definition
K Show documentation K Show documentation
[g / ]g Prev/next LSP diagnostic [g / ]g Prev/next diagnostic
[e / ]e Prev/next ALE error
,rn Rename symbol ,rn Rename symbol
,ca Code action / auto-fix ,ca Code action
,f Format selection | ,F Format whole file ,f Format selection
,F Format whole file
GIT GIT
,gs Status | ,gd Diff | ,gb Blame ,gs Status | ,gd Diff | ,gb Blame
,gc Commit | ,gp Push | ,gl Pull ,gc Commit | ,gp Push | ,gl Pull
WINDOWS / PANES WINDOWS
Ctrl+h/j/k/l Move between Vim windows or tmux panes Ctrl+h/j/k/l Move between panes
,h / ,l Prev / next buffer
,tv Open terminal (vertical) ,tv Open terminal (vertical)
,th Open terminal (horizontal) ,th Open terminal (horizontal)
Esc Exit terminal mode Esc Exit terminal mode
,u Undo tree | ,tt Tag browser F5 Undo tree | F8 Tag browser
SEARCH & REPLACE SEARCH
/text Search forward | ?text backward /text Search forward
// Search for visually selected text ?text Search backward
,* Replace word under cursor (file-wide) ,* Replace word under cursor (project-wide)
CLIPBOARD
,y / ,Y Yank / yank line to system clipboard
,p / ,P Paste from system clipboard (after / before)
``` ```
--- ---

546
README.md
View file

@ -1,71 +1,41 @@
# chopsticks # chopsticks — Vim Configuration
> A batteries-included Vim configuration for full-stack engineering. A native Vim configuration optimized for full-stack engineering workflows.
> Tiered LSP · 14 languages · TTY-aware · Zero icon fonts · One-command install. Vim 8.0+ · Tiered LSP · TTY-aware · Zero icon fonts · 14 languages.
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](LICENSE) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Vim 8.0+](https://img.shields.io/badge/Vim-8.0%2B-brightgreen?style=flat-square)](https://www.vim.org/) [![Vim 8.0+](https://img.shields.io/badge/Vim-8.0%2B-brightgreen.svg)](https://www.vim.org/)
[![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux-lightgrey?style=flat-square)](#installation)
[![Release](https://img.shields.io/github/v/release/m1ngsama/chopsticks?style=flat-square&label=release&color=orange)](https://github.com/m1ngsama/chopsticks/releases)
[![Last Commit](https://img.shields.io/github/last-commit/m1ngsama/chopsticks?style=flat-square)](https://github.com/m1ngsama/chopsticks/commits/main)
[![Stars](https://img.shields.io/github/stars/m1ngsama/chopsticks?style=flat-square)](https://github.com/m1ngsama/chopsticks/stargazers)
[![Issues](https://img.shields.io/github/issues/m1ngsama/chopsticks?style=flat-square)](https://github.com/m1ngsama/chopsticks/issues)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen?style=flat-square)](https://github.com/m1ngsama/chopsticks/pulls)
[![Plugins](https://img.shields.io/badge/plugins-30%2B-blueviolet?style=flat-square)](#plugins)
[![Languages](https://img.shields.io/badge/languages-14-informational?style=flat-square)](#language-support)
```bash ```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
cd ~/.vim && ./install.sh cd ~/.vim && ./install.sh
``` ```
> **New to Vim?** Read [Step 0 in QUICKSTART.md](QUICKSTART.md#step-0-vim-basics) first — See [QUICKSTART.md](QUICKSTART.md) for the 5-minute guide.
> a 2-minute intro to modes and the 4 commands that get you out of any jam.
---
## Contents
- [Design Principles](#design-principles)
- [Requirements](#requirements)
- [Installation](#installation)
- [LSP: Tiered Backend](#lsp-tiered-backend)
- [Key Mappings](#key-mappings)
- [Features](#features)
- [Language Support](#language-support)
- [Plugins](#plugins)
- [Customization](#customization)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
--- ---
## Design Principles ## Design Principles
| Principle | What it means | - **KISS** — No icon fonts, no unicode glyphs, plain ASCII throughout
|-----------|--------------| - **Tiered LSP** — CoC (full) with vim-lsp fallback; works with or without Node.js
| **KISS** | No icon fonts, no Nerd Font glyphs — plain ASCII everywhere | - **TTY-aware** — Automatic detection and optimization for console/SSH environments
| **Tiered LSP** | CoC (full) when Node.js is available; vim-lsp (pure VimScript) otherwise | - **Engineering-first** — Git workflow, session management, project-local config
| **TTY-aware** | Automatically detects SSH/console environments and degrades gracefully | - **Batteries included**`install.sh` handles all dependencies automatically
| **Engineering-first** | Git workflow, persistent sessions, project-local config, large-file safety |
| **Batteries included** | `install.sh` handles vim-plug, plugins, system tools, and language servers |
--- ---
## Requirements ## Requirements
| Tool | Minimum | Role | | Requirement | Minimum | Notes |
|------|---------|------| |-------------|---------|-------|
| Vim | **8.0+** | Required | | Vim | 8.0+ | vim9script not required |
| git | any | Cloning and vim-fugitive | | git | any | For cloning and fugitive |
| curl | any | vim-plug bootstrap | | curl | any | For vim-plug auto-install |
| Node.js | 14.14+ | Optional — enables CoC LSP (recommended) | | Node.js | 14.14+ | Optional — enables CoC LSP |
| ripgrep | any | Optional — enables `,rg` / `,rG` project search | | ripgrep (rg) | any | Optional — enables `:Rg` search |
| fzf | any | Optional — enables `Ctrl+p` fuzzy finder | | fzf | any | Optional — enables `Ctrl+p` fuzzy search |
| ctags | any | Optional — enables `,tt` tag browser | | ctags | any | Optional — enables `F8` tag browser |
| tmux | 1.8+ | Optional — enables seamless pane navigation |
All optional tools are installed automatically by `install.sh` when prompted.
--- ---
@ -79,20 +49,16 @@ cd ~/.vim
./install.sh ./install.sh
``` ```
The installer handles everything in sequence: The installer:
1. Checks Vim version and detects OS / package managers
1. Verifies Vim 8.0+ and detects OS / package manager 2. Backs up any existing `~/.vimrc` (timestamped)
2. Backs up any existing `~/.vimrc` with a timestamp 3. Creates symlinks: `~/.vimrc -> ~/.vim/.vimrc` and `~/.vim/coc-settings.json`
3. Symlinks `~/.vimrc → ~/.vim/.vimrc` and `~/.vim/coc-settings.json`
4. Installs vim-plug and runs `:PlugInstall` 4. Installs vim-plug and runs `:PlugInstall`
5. Optionally installs system tools (ripgrep, fzf, ctags, shellcheck, hadolint, marksman) 5. Optionally installs system tools, language tools, and CoC extensions
6. Optionally installs language tools (npm, pip, Go)
7. Optionally installs CoC language server extensions
8. Optionally configures tmux for seamless pane navigation
**Supported platforms:** macOS (Homebrew), Debian/Ubuntu (apt), Arch Linux (pacman), Fedora (dnf). Supported platforms: **macOS** (Homebrew), **Debian/Ubuntu** (apt), **Arch Linux** (pacman), **Fedora** (dnf).
Use `--yes` for non-interactive or CI environments: Use `--yes` for non-interactive / CI environments:
```bash ```bash
./install.sh --yes ./install.sh --yes
@ -111,21 +77,21 @@ vim +PlugInstall +qall </dev/null
--- ---
## LSP: Tiered Backend ## LSP: Tiered Backend System
Code intelligence is provided by one of two backends, chosen automatically at startup: Code intelligence is provided by one of two backends, selected automatically:
| Condition | Backend | Capabilities | | Condition | Backend | Features |
|-----------|---------|-------------| |-----------|---------|----------|
| Vim 8.0.1453+ **and** Node.js 14.14+ | **CoC** | Full LSP, snippets, extension ecosystem | | Vim 8.0.1453+ AND Node.js 14.14+ | **CoC** | Full LSP, snippets, extensions ecosystem |
| Vim 8.0+ (no Node.js) | **vim-lsp** | LSP via language server binaries, asyncomplete | | Vim 8.0+ (no Node.js) | **vim-lsp** | LSP via language server binaries, asyncomplete |
| Any Vim | **ALE** | Async linting + auto-fix (always active, both backends) | | Any Vim | **ALE** | Linting and auto-fix (always active) |
Both CoC and vim-lsp expose the same key mappings so switching backends is transparent. Both backends expose identical key mappings: `gd`, `K`, `[g`, `]g`, `<leader>rn`, `<leader>ca`.
### With Node.js (CoC) ### CoC setup (with Node.js)
Install language server extensions from inside Vim — or let `install.sh` do it automatically: Install language server extensions from inside Vim:
```vim ```vim
:CocInstall coc-pyright " Python :CocInstall coc-pyright " Python
@ -133,86 +99,64 @@ Install language server extensions from inside Vim — or let `install.sh` do it
:CocInstall coc-go " Go :CocInstall coc-go " Go
:CocInstall coc-rust-analyzer " Rust :CocInstall coc-rust-analyzer " Rust
:CocInstall coc-json coc-yaml " JSON, YAML :CocInstall coc-json coc-yaml " JSON, YAML
:CocInstall coc-html coc-css " HTML, CSS/SCSS :CocInstall coc-html coc-css " HTML, CSS
:CocInstall coc-sh " Shell :CocInstall coc-sh " Shell
:CocInstall coc-sql " SQL :CocInstall coc-sql " SQL
``` ```
**Markdown LSP** uses `marksman` as an external binary (not a CoC extension): `install.sh` installs all of the above automatically when prompted.
```bash **Markdown LSP** — `marksman` is configured via `coc-settings.json` (not a CoC
brew install marksman # macOS extension — install `marksman` binary via `brew install marksman` or download from
sudo pacman -S marksman # Arch [releases](https://github.com/artempyanykh/marksman/releases)).
# or: ./install.sh handles it automatically
```
### Without Node.js (vim-lsp) ### vim-lsp setup (without Node.js)
Open a source file, then run: Install language server binaries for your languages, then run:
```vim ```vim
:LspInstallServer " detects filetype and installs the correct server :LspInstallServer " auto-installs the right server for the current filetype
:LspStatus " check server status
``` ```
Supported: Python, Go, Rust, TypeScript/JavaScript, Shell, HTML, CSS/SCSS, JSON, YAML, Markdown, SQL. Supported languages: Python, Go, Rust, TypeScript, JavaScript, Shell, HTML,
CSS/SCSS, JSON, YAML, Markdown, SQL — via `vim-lsp-settings`.
--- ---
## Key Mappings ## Key Mappings
**Leader key:** `,` (comma) Leader key: `,` (comma)
Press `,` and wait 500 ms to see an interactive guide to all bindings (vim-which-key). Press `,` and wait 500ms for an interactive guide to all bindings (vim-which-key).
Press `,?` to open the built-in cheat sheet at any time.
### Survival
| Key | Action |
|-----|--------|
| `jk` | Exit insert mode → Normal (ergonomic Escape) |
| `Esc` | Exit insert / visual mode (standard) |
| `Ctrl+s` | Save file (normal and insert mode) |
| `,w` | Save file |
| `,x` | Save and quit |
| `,q` | Quit |
| `,?` | Open cheat sheet |
> **`Ctrl+s` note:** some terminals freeze on `Ctrl+s` (XON/XOFF). Add `stty -ixon`
> to your `~/.bashrc` / `~/.zshrc` to disable this permanently.
### Files and Buffers ### Files and Buffers
| Key | Action | | Key | Action |
|-----|--------| |-----|--------|
| `Ctrl+p` | Fuzzy file search — git-aware (FZF) |
| `Ctrl+n` | Toggle file tree (NERDTree) | | `Ctrl+n` | Toggle file tree (NERDTree) |
| `,n` | Reveal current file in NERDTree | | `,n` | Reveal current file in NERDTree |
| `Ctrl+p` | Fuzzy file search (FZF — git-aware) |
| `,b` | Search open buffers (FZF) | | `,b` | Search open buffers (FZF) |
| `,rg` | Project-wide search (ripgrep + FZF) | | `,rg` | Project-wide search (ripgrep+FZF) |
| `,rG` | Ripgrep for word under cursor (literal match) |
| `,rt` | Search tags (FZF) | | `,rt` | Search tags (FZF) |
| `,gF` | Search git-tracked files (FZF) | | `,gF` | Search git-tracked files (FZF) |
| `,l` | Next buffer | | `,l` | Next buffer |
| `,h` | Previous buffer | | `,h` | Previous buffer |
| `,bd` | Close current buffer (preserves window layout) | | `,bd` | Close current buffer |
| `,,` | Switch to last file | | `,,` | Switch to last file |
### Windows, Tabs, and tmux ### Windows and Tabs
| Key | Action | | Key | Action |
|-----|--------| |-----|--------|
| `Ctrl+h/j/k/l` | Navigate between Vim splits **and** tmux panes | | `Ctrl+h/j/k/l` | Navigate between windows |
| `,=` | Increase window height | | `<Leader>=` | Increase window height |
| `,-` | Decrease window height | | `<Leader>-` | Decrease window height |
| `,+` | Increase window width | | `<Leader>+` | Increase window width |
| `,_` | Decrease window width | | `<Leader>_` | Decrease window width |
| `,tn` | New tab | | `,tn` | New tab |
| `,tc` | Close tab | | `,tc` | Close tab |
| `,tl` | Toggle to last tab | | `,tl` | Toggle to last tab |
| `,tv` | Open terminal (vertical split) |
| `,th` | Open terminal (horizontal split) |
| `Esc Esc` | Exit terminal mode |
### Code Intelligence (CoC / vim-lsp) ### Code Intelligence (CoC / vim-lsp)
@ -221,128 +165,123 @@ Press `,?` to open the built-in cheat sheet at any time.
| `gd` | Go to definition | | `gd` | Go to definition |
| `gy` | Go to type definition | | `gy` | Go to type definition |
| `gi` | Go to implementation | | `gi` | Go to implementation |
| `gr` | Show all references | | `gr` | Show references |
| `K` | Hover documentation | | `K` | Hover documentation |
| `[g` | Previous diagnostic | | `[g` | Previous diagnostic |
| `]g` | Next diagnostic | | `]g` | Next diagnostic |
| `,rn` | Rename symbol | | `,rn` | Rename symbol |
| `,f` | Format selection | | `,f` | Format selection |
| `,F` | Format whole file | | `,ca` | Code action (cursor) |
| `,ca` | Code action (cursor position) | | `,o` | File outline |
| `,o` | File outline (symbols) |
| `,ws` | Workspace symbols | | `,ws` | Workspace symbols |
| `,cD` | Diagnostics list | | `,cD` | Diagnostics list |
| `,cr` | Resume last CoC list |
| `,qf` | Quick-fix current line (CoC) | | `,qf` | Quick-fix current line (CoC) |
| `,cl` | Run code lens (CoC) |
| `Tab` | Next completion item | | `Tab` | Next completion item |
| `Shift+Tab` | Previous completion item | | `Shift+Tab` | Previous completion item |
| `Enter` | Confirm completion | | `Enter` | Confirm completion |
Text objects (CoC): `if`/`af` (function inner/around), `ic`/`ac` (class inner/around). Text objects (CoC only): `if`/`af` (function), `ic`/`ac` (class)
### Linting (ALE — always active) ### Linting (ALE)
| Key | Action | | Key | Action |
|-----|--------| |-----|--------|
| `[e` | Previous error / warning | | `[e` | Next error/warning |
| `]e` | Next error / warning | | `]e` | Previous error/warning |
| `,aD` | Show error detail | | `,aD` | Show error details |
| `,ad` | Full diagnostics list |
Signs in the gutter: `X` = error, `!` = warning. Signs: `X` = error, `!` = warning
### Git (vim-fugitive) ### Git Workflow (fugitive)
| Key | Action | | Key | Action |
|-----|--------| |-----|--------|
| `,gs` | Git status (stage with `s`, commit with `cc`) | | `,gs` | Git status |
| `,gc` | Git commit | | `,gc` | Git commit |
| `,gp` | Git push | | `,gp` | Git push |
| `,gl` | Git pull | | `,gl` | Git pull |
| `,gd` | Git diff | | `,gd` | Git diff |
| `,gb` | Git blame | | `,gb` | Git blame |
| `,gF` | Search git-tracked files (FZF) |
### Search and Replace ### Engineering Utilities
| Key | Action |
|-----|--------|
| `n` / `N` | Next / previous match (cursor centered) |
| `//` | Search for visually selected text |
| `,*` | Search and replace word under cursor (file-wide) |
| `,rG` | Ripgrep word under cursor across project |
| `,<CR>` | Clear search highlight |
### Clipboard
| Key | Action |
|-----|--------|
| `,y` | Yank to system clipboard |
| `,Y` | Yank line to system clipboard |
| `,p` | Paste from system clipboard (after cursor) |
| `,P` | Paste from system clipboard (before cursor) |
### Editing and Navigation
| Key | Action |
|-----|--------|
| `s` + 2 chars | EasyMotion — jump anywhere on screen |
| `Space` | Toggle code fold |
| `Y` | Yank to end of line (consistent with `D`, `C`) |
| `Ctrl+d/u` | Half-page scroll (cursor stays centered) |
| `>` / `<` | Indent / dedent (keeps visual selection) |
| `Alt+j/k` | Move current line down / up |
| `0` | Jump to first non-blank character |
| `[q` / `]q` | Previous / next quickfix entry (vim-unimpaired) |
| `,u` | Toggle undo tree (visual branch history) |
| `,tt` | Toggle tagbar (code structure) |
| `F2` | Toggle paste mode |
| `F3` | Toggle absolute line numbers |
| `F4` | Toggle relative line numbers |
### Config and Utilities
| Key | Action | | Key | Action |
|-----|--------| |-----|--------|
| `,ev` | Edit `~/.vimrc` | | `,ev` | Edit `~/.vimrc` |
| `,sv` | Reload `~/.vimrc` | | `,sv` | Reload `~/.vimrc` |
| `,F` | Format entire file |
| `,W` | Strip trailing whitespace |
| `,wa` | Save all open buffers | | `,wa` | Save all open buffers |
| `,wd` | Change working directory to current file's location | | `,wd` | Change CWD to current buffer's dir |
| `,cp` | Copy absolute file path to clipboard | | `,cp` | Copy file path to clipboard |
| `,cf` | Copy filename to clipboard | | `,cf` | Copy filename to clipboard |
| `,qo` / `,qc` | Open / close quickfix list | | `,y` | Yank to system clipboard |
| `,Y` | Yank line to system clipboard |
| `,*` | Search+replace word under cursor |
| `,qo` | Open quickfix list |
| `,qc` | Close quickfix list |
| `,tv` | Open terminal (vertical split) |
| `,th` | Open terminal (horizontal, 10 rows) |
| `Esc` | Exit terminal mode |
### Navigation and Editing
| Key | Action |
|-----|--------|
| `s`+2ch | EasyMotion jump to any location |
| `Space` | Toggle code fold |
| `Y` | Yank to end of line (like `D`, `C`) |
| `n` / `N` | Search next/prev (cursor centered) |
| `Ctrl+d/u` | Half-page scroll (cursor centered) |
| `>` | Indent (keeps visual selection) |
| `<` | Dedent (keeps visual selection) |
| `[q` / `]q` | Previous/next quickfix (vim-unimpaired) |
| `[e` / `]e` | Previous/next ALE error/warning |
| `F2` | Toggle paste mode |
| `F3` | Toggle line numbers |
| `F4` | Toggle relative line numbers |
| `F5` | Toggle undo history (UndoTree) |
| `F8` | Toggle code tag browser (Tagbar) |
| `0` | Jump to first non-blank character |
| `Alt+j/k` | Move line up/down |
--- ---
## Features ## Features
### Startup Dashboard ### Startup Screen (vim-startify)
Running `vim` (no arguments) opens a full-screen Startify dashboard showing recent Opens when Vim is launched without a file argument. Shows:
files, sessions, and bookmarks. Running `vim .` opens NERDTree on the left with - Session list for current directory
the dashboard on the right. - Recently opened files
- Bookmarks
### Keybinding Guide Session auto-saves on quit. Auto-loads `Session.vim` if found in the current
directory. Auto-changes to git root on file open.
Press `,` and pause for 500 ms. A popup (vim-which-key) lists all leader bindings **`vim .` layout** — NERDTree on the left, Startify on the right.
organized into groups. No need to memorize everything upfront.
### Built-in Cheat Sheet ### Keybinding Guide (vim-which-key)
Press `,?` to open an inline reference covering modes, survival commands, search, Press `,` and pause for 500ms. A popup lists all available leader bindings
code intelligence, git, and clipboard — without leaving Vim. organized by group. Useful for onboarding and discovering shortcuts.
### Session Management ### Session Management
```vim ```vim
:Obsess " start tracking the current session :Obsess " Start tracking session
:Obsess! " stop tracking :Obsess! " Stop tracking
``` ```
Sessions are stored in `~/.vim/sessions/` and automatically restored by vim-prosession Sessions stored in `~/.vim/sessions/` and automatically resumed by vim-prosession
the next time you open Vim in the same directory. on the next Vim launch in the same directory.
### Project-Local Config ### Project-Local Config
Drop a `.vimrc` in any project root to override settings for that project: Place a `.vimrc` in any project root:
```vim ```vim
" project/.vimrc " project/.vimrc
@ -350,244 +289,157 @@ set shiftwidth=2
let g:ale_python_black_options = '--line-length=100' let g:ale_python_black_options = '--line-length=100'
``` ```
Loaded automatically via `set exrc`. Restricted to safe options via `set secure`. Loaded automatically. Security-restricted via `set secure`.
### tmux Integration
`Ctrl+h/j/k/l` navigates seamlessly between Vim splits and tmux panes — no prefix
key, no mode switch.
**Vim side:** handled by vim-tmux-navigator (installed automatically).
**tmux side:** add to `~/.tmux.conf` (or let `install.sh` append it):
```tmux
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\S+\/)?g?(view|n?vim?x?)(diff)?$'"
bind-key -n 'C-h' if-shell "$is_vim" 'send-keys C-h' 'select-pane -L'
bind-key -n 'C-j' if-shell "$is_vim" 'send-keys C-j' 'select-pane -D'
bind-key -n 'C-k' if-shell "$is_vim" 'send-keys C-k' 'select-pane -U'
bind-key -n 'C-l' if-shell "$is_vim" 'send-keys C-l' 'select-pane -R'
```
Then reload: `tmux source-file ~/.tmux.conf`
> **Note:** the `C-l` binding replaces the terminal's screen-clear shortcut inside
> tmux. To restore it, add `bind C-l send-keys 'C-l'` — then use `prefix + C-l`.
### Large File Handling ### Large File Handling
Files over 10 MB automatically disable syntax highlighting, undo history, and Files over 10 MB automatically disable syntax highlighting and undo history
linting to prevent Vim from stalling. to prevent Vim from freezing.
### TTY / Console Support ### TTY / Console Support
Detected automatically when `$TERM` is `linux` or `screen`. In TTY mode: Detected automatically when `$TERM` is `linux` or `screen`. In TTY mode:
- True color and cursorline disabled - True color and cursorline disabled
- Powerline separators replaced with plain ASCII - Powerline separators replaced with plain ASCII
- FZF preview windows disabled - FZF preview windows disabled
- IndentLine guides disabled - NERDTree auto-open skipped
- Syntax column limit reduced to 120 characters - Syntax column limit reduced to 120
- Simpler built-in status line used instead of airline - Simpler status line
--- ---
## Language Support ## Language Support
| Language | Indent | Formatter | Linter | LSP | | Language | Indent | Formatter | Linter | LSP (CoC) |
|----------|--------|-----------|--------|-----| |----------|--------|-----------|--------|-----------|
| Python | 4 sp | black, isort | flake8, pylint | coc-pyright | | Python | 4sp | black + isort | flake8, pylint | coc-pyright |
| JavaScript | 2 sp | prettier | eslint | coc-tsserver | | JavaScript | 2sp | prettier | eslint | coc-tsserver |
| TypeScript | 2 sp | prettier | eslint, tsserver | coc-tsserver | | TypeScript | 2sp | prettier | eslint, tsserver | coc-tsserver |
| Go | tab | gofmt, goimports | staticcheck | coc-go | | Go | tab | gofmt, goimports | staticcheck | coc-go |
| Rust | 4 sp | rustfmt | cargo | coc-rust-analyzer | | Rust | 4sp | rustfmt | cargo | coc-rust-analyzer |
| Shell | 2 sp | — | shellcheck | coc-sh | | Shell | 2sp | — | shellcheck | coc-sh |
| YAML | 2 sp | prettier | yamllint | coc-yaml | | YAML | 2sp | prettier | yamllint | coc-yaml |
| HTML | 2 sp | prettier | — | coc-html | | HTML | 2sp | prettier | — | coc-html |
| CSS / SCSS | 2 sp | prettier | stylelint | coc-css | | CSS / SCSS | 2sp | prettier | stylelint | coc-css |
| Less | 2 sp | prettier | — | — | | Less | 2sp | prettier | — | — |
| JSON | 2 sp | prettier | — | coc-json | | JSON | 2sp | prettier | — | coc-json |
| Markdown | 2 sp | prettier | markdownlint | marksman | | Markdown | 2sp | prettier | markdownlint | marksman (coc-settings.json) |
| SQL | 4 sp | sqlfluff | sqlfluff | — | | SQL | 4sp | sqlfluff | sqlfluff | — |
| Dockerfile | 2 sp | — | hadolint | — | | Dockerfile | 2sp | — | hadolint | — |
`install.sh` installs all formatters and linters automatically. `install.sh` installs all linters and formatters automatically.
ALE runs them asynchronously; format-on-save is active for all supported languages. ALE runs them asynchronously; format-on-save active when using CoC.
--- ---
## Plugins ## Plugin List
### Navigation ### Navigation
- **NERDTree**file tree explorer - **NERDTree**File tree explorer
- **fzf + fzf.vim**fuzzy finder for files, buffers, tags, and ripgrep - **fzf + fzf.vim**Fuzzy finder (file, buffer, tag, ripgrep)
### Git ### Git
- **vim-fugitive**full Git integration inside Vim - **vim-fugitive**Git commands inside Vim
- **vim-gitgutter**diff signs in the sign column - **vim-gitgutter**Diff signs in the sign column
### LSP and Completion ### LSP and Completion
- **coc.nvim**full LSP + completion via Node.js (recommended) - **coc.nvim**Full LSP + completion (requires Node.js 14.14+)
- **vim-lsp**pure VimScript LSP client (Node.js-free fallback) - **vim-lsp**Pure VimScript LSP client (fallback, no Node.js)
- **vim-lsp-settings**auto-configures language servers for vim-lsp - **vim-lsp-settings**Auto-configure language servers for vim-lsp
- **asyncomplete.vim**async completion engine (vim-lsp mode) - **asyncomplete.vim**Async completion (used with vim-lsp)
### Linting ### Linting
- **ALE**asynchronous lint engine, always active regardless of LSP backend - **ALE**Asynchronous Lint Engine (always active)
### UI ### UI
- **vim-airline**status line and tabline - **vim-airline**Status and tabline
- **vim-startify**startup dashboard with session management - **vim-startify**Startup screen with sessions
- **vim-which-key**keybinding hint popup on leader pause - **vim-which-key**Keybinding hint popup
- **indentLine**indent guide lines (non-TTY only) - **indentLine**Indent guide lines (non-TTY)
- **undotree**visual undo branch history - **undotree**Undo history visualizer
- **tagbar**code structure sidebar via ctags - **tagbar**Code structure sidebar
### Editing ### Editing
- **vim-surround**change surrounding quotes, brackets, and tags - **vim-surround**Change surrounding quotes, brackets, tags
- **vim-commentary**`gc` to toggle comments - **vim-commentary**`gc` to toggle comments
- **auto-pairs** — auto-close brackets and quotes - **auto-pairs** — Auto-close brackets and quotes
- **vim-easymotion** — jump anywhere on screen with 2 keystrokes (`s`) - **vim-easymotion** — Jump anywhere with 2 keystrokes
- **vim-unimpaired** — bracket shortcut pairs (`[q`/`]q`, etc.) - **vim-unimpaired** — Bracket shortcut pairs
- **targets.vim** — additional text objects - **targets.vim** — Extra text objects
- **vim-snippets** — snippet library (used with CoC) - **vim-snippets** — Snippet library (used with CoC/UltiSnips)
- **vim-tmux-navigator** — seamless `Ctrl+h/j/k/l` across Vim and tmux
### Language Packs ### Language Packs
- **vim-polyglot**syntax for 100+ languages - **vim-polyglot**Syntax for 100+ languages
- **vim-go** — Go syntax and tooling (LSP handled by coc-go) - **vim-go** — Go development tools (formatting + highlighting; LSP handled by coc-go)
### Session ### Session
- **vim-obsession**continuous session saving - **vim-obsession**Continuous session saving
- **vim-prosession**project-level session management - **vim-prosession**Project-level session management
### Color Schemes ### Color Schemes
- **gruvbox** (default), **dracula**, **solarized**, **onedark** - **gruvbox** (default), **dracula**, **solarized**, **onedark**
--- ---
## Customization ## Color Scheme
### Change the color scheme Change in `.vimrc` (find the `colorscheme` line):
In `~/.vimrc`, find and update the `colorscheme` line:
```vim ```vim
colorscheme dracula " options: gruvbox, solarized, onedark colorscheme dracula " or: gruvbox, solarized, onedark
``` ```
True color is enabled automatically when `$COLORTERM=truecolor`. Falls back to True color is enabled automatically when the terminal supports it
256-color, then 16-color in TTY. (`$COLORTERM=truecolor`). Falls back to 256-color, then 16-color (TTY).
### Per-project overrides
Create `.vimrc` in your project root. Anything placed here overrides the global
config for that directory:
```vim
" my-project/.vimrc
set shiftwidth=2
let g:ale_python_black_options = '--line-length=120'
```
### Modify keybindings
Edit `~/.vimrc` directly (`,ev` opens it from inside Vim). Reload with `,sv`.
--- ---
## Troubleshooting ## Troubleshooting
**Plugins not loading** **Plugins not installed:**
```vim ```vim
:PlugInstall " install any missing plugins :PlugInstall
:PlugUpdate " update all plugins :PlugUpdate
``` ```
**CoC not working** **CoC not working:**
```bash ```bash
node --version # must be 14.14+ node --version # must be >= 14.14
``` ```
Inside Vim: `:CocInfo` for diagnostics, `:CocInstall <extension>` to add a language server. **Markdown LSP not starting:**
```bash
**vim-lsp server not starting** marksman --version # must be installed separately
brew install marksman # macOS
sudo pacman -S marksman # Arch
# or: ./install.sh (installs automatically)
```
**vim-lsp server not starting:**
```vim ```vim
:LspInstallServer " install the correct server for the current filetype :LspInstallServer " install server for current filetype
:LspStatus " check server status :LspStatus " check server status
``` ```
**Markdown LSP not starting** **Colors look wrong:**
`marksman` must be installed as a standalone binary (not a CoC extension):
```bash
brew install marksman # macOS
sudo pacman -S marksman # Arch
# or: ./install.sh handles it automatically
```
**Colors look wrong**
```bash ```bash
export TERM=xterm-256color # add to ~/.bashrc or ~/.zshrc export TERM=xterm-256color # add to ~/.bashrc or ~/.zshrc
``` ```
For true color: `export COLORTERM=truecolor`. **ALE not finding linters:**
**ALE linters not found**
```bash ```bash
which flake8 black prettier eslint # verify tools are on PATH which flake8 black prettier eslint # confirm tools are on PATH
``` ```
If tools were installed with `pip install --user` or `npm install -g`, make sure
the respective bin directories are on `$PATH`.
**`Ctrl+s` freezes the terminal**
Add `stty -ixon` to your `~/.bashrc`, `~/.zshrc`, or `~/.config/fish/config.fish`.
This disables XON/XOFF flow control permanently.
--- ---
## Contributing ## References
Bug reports and pull requests are welcome. Please follow these guidelines: - [vim-plug](https://github.com/junegunn/vim-plug)
- [coc.nvim](https://github.com/neoclide/coc.nvim)
### Reporting a bug - [vim-lsp](https://github.com/prabirshrestha/vim-lsp)
- [vim-lsp-settings](https://github.com/mattn/vim-lsp-settings)
1. Search [existing issues](https://github.com/m1ngsama/chopsticks/issues) before opening a new one. - [amix/vimrc](https://github.com/amix/vimrc)
2. Include your Vim version (`vim --version`), OS, and a minimal reproduction.
3. If the bug is plugin-specific, check whether it reproduces with a minimal config
(`vim -u NONE`) or only with chopsticks loaded.
### Proposing a change
1. Open an issue first to discuss the change, especially for non-trivial additions.
2. Keep the scope focused — one feature or fix per PR.
3. Follow existing conventions: augroups for autocmds, TTY guards for visual features,
conditional plugin loading where appropriate.
4. Update `CHANGELOG.md` with a summary of the change.
### Scope
Chopsticks is an opinionated configuration. Changes should align with the design
principles above — in particular, KISS (no icon fonts, minimal dependencies) and
TTY-compatibility. Neovim-only features and Lua configs are out of scope.
---
## Acknowledgements
Inspired by [amix/vimrc](https://github.com/amix/vimrc).
Built with [vim-plug](https://github.com/junegunn/vim-plug),
[coc.nvim](https://github.com/neoclide/coc.nvim),
[vim-lsp](https://github.com/prabirshrestha/vim-lsp),
and the broader Vim plugin community.
--- ---

67
get.sh
View file

@ -1,67 +0,0 @@
#!/usr/bin/env bash
# get.sh - One-command bootstrap for chopsticks vim config
#
# Usage:
# curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash
# curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --yes
set -eo pipefail
REPO="https://github.com/m1ngsama/chopsticks.git"
DEST="$HOME/.vim"
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; BOLD='\033[1m'; NC='\033[0m'
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
die() { echo -e "${RED}[FATAL]${NC} $1" >&2; exit 1; }
step() { echo -e "\n${BOLD}==> $1${NC}"; }
echo -e "${BOLD}chopsticks — One-command installer${NC}"
echo "----------------------------------"
echo " Repo: $REPO"
echo " Dest: $DEST"
# ── git ───────────────────────────────────────────────────────────────────────
step "Checking for git"
if ! command -v git >/dev/null 2>&1; then
warn "git not found — attempting to install"
if command -v apt-get >/dev/null 2>&1; then sudo apt-get install -y git >/dev/null 2>&1
elif command -v pacman >/dev/null 2>&1; then sudo pacman -S --noconfirm git >/dev/null 2>&1
elif command -v dnf >/dev/null 2>&1; then sudo dnf install -y git >/dev/null 2>&1
elif command -v brew >/dev/null 2>&1; then brew install git >/dev/null 2>&1
else die "git is required. Install it manually then re-run."; fi
command -v git >/dev/null 2>&1 || die "git install failed. Try: sudo apt install git"
fi
ok "git $(git --version | awk '{print $3}')"
# ── Clone or update ───────────────────────────────────────────────────────────
step "Setting up $DEST"
if [[ -d "$DEST/.git" ]]; then
warn "$DEST already exists — pulling latest changes"
git -C "$DEST" pull --ff-only origin main 2>/dev/null || \
warn "Could not pull latest — using existing version (run: git -C ~/.vim pull)"
ok "Repository updated"
elif [[ -d "$DEST" ]]; then
die "$HOME/.vim exists but is not a chopsticks git repo.
Back it up first: mv ~/.vim ~/.vim.bak
Then re-run: curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash"
else
git clone --depth=1 "$REPO" "$DEST" || \
die "Clone failed — check your network connection"
ok "Cloned to $DEST"
fi
# ── Run installer ─────────────────────────────────────────────────────────────
step "Running installer"
cd "$DEST"
# exec replaces this process with install.sh and reconnects stdin to /dev/tty
# so interactive prompts work correctly even when this script was piped from curl
if [[ -e /dev/tty ]]; then
exec bash install.sh "$@" </dev/tty
else
exec bash install.sh "$@"
fi

View file

@ -4,13 +4,12 @@
# #
# --yes non-interactive: install all optional components automatically # --yes non-interactive: install all optional components automatically
set -eo pipefail set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AUTO_YES=0 AUTO_YES=0
[[ "${1:-}" == "--yes" ]] && AUTO_YES=1 [[ "${1:-}" == "--yes" ]] && AUTO_YES=1
# ── Colours ───────────────────────────────────────────────────────────────────
GREEN='\033[0;32m' GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
RED='\033[0;31m' RED='\033[0;31m'
@ -22,12 +21,8 @@ ok() { echo -e "${GREEN}[OK]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; } warn() { echo -e "${YELLOW}[!]${NC} $1"; }
skip() { echo -e "${CYAN}[--]${NC} $1"; } skip() { echo -e "${CYAN}[--]${NC} $1"; }
fail() { echo -e "${RED}[ERR]${NC} $1"; } fail() { echo -e "${RED}[ERR]${NC} $1"; }
die() { echo -e "${RED}[FATAL]${NC} $1" >&2 die() { echo -e "${RED}[ERR]${NC} $1" >&2; exit 1; }
echo " Retry with: ./install.sh 2>&1 | tee /tmp/chopsticks-install.log" >&2
echo " Report issues: https://github.com/m1ngsama/chopsticks/issues" >&2
exit 1; }
step() { echo -e "\n${BOLD}==> $1${NC}"; } step() { echo -e "\n${BOLD}==> $1${NC}"; }
info() { echo " $1"; }
# Track results for summary # Track results for summary
INSTALLED=() INSTALLED=()
@ -35,89 +30,52 @@ SKIPPED=()
FAILED=() FAILED=()
# Ask yes/no; returns 0 for yes # Ask yes/no; returns 0 for yes
# Reads from /dev/tty so interactive prompts work even under: curl | bash
ask() { ask() {
[[ $AUTO_YES -eq 1 ]] && return 0 [[ $AUTO_YES -eq 1 ]] && return 0
if [[ -t 0 ]]; then read -r -p "$1 [y/N] " reply
read -r -p "$1 [y/N] " reply
elif [[ -e /dev/tty ]]; then
read -r -p "$1 [y/N] " reply </dev/tty
else
# No terminal available — default to no (safe)
echo "$1 [y/N] N"
return 1
fi
[[ "$reply" =~ ^[Yy]$ ]] [[ "$reply" =~ ^[Yy]$ ]]
} }
# ── Error trap ──────────────────────────────────────────────────────────────── # Try to install a single binary tool via a given command
on_error() { # Usage: try_install <display_name> <check_cmd> <install_cmd...>
local line="${BASH_LINENO[0]}" try_install() {
echo -e "\n${RED}[FATAL]${NC} Unexpected error at line $line." >&2 local name="$1"; local check="$2"; shift 2
echo " To get a full debug log:" >&2 if command -v "$check" >/dev/null 2>&1; then
echo " ./install.sh 2>&1 | tee /tmp/chopsticks-install.log" >&2 ok "$name (already installed: $(command -v "$check"))"
echo " Report issues: https://github.com/m1ngsama/chopsticks/issues" >&2 return 0
}
trap on_error ERR
# Cleanup temp files on exit
trap 'rm -f /tmp/chopsticks-hadolint /tmp/chopsticks-marksman 2>/dev/null' EXIT
# ── Safe download helper ──────────────────────────────────────────────────────
# safe_download <url> <dest>
# Returns 1 if download fails or file is empty / HTML error page
safe_download() {
local url="$1" dest="$2"
curl -fsSL --connect-timeout 15 --retry 3 "$url" -o "$dest" 2>/dev/null || return 1
# Reject empty files
[[ -s "$dest" ]] || { rm -f "$dest"; return 1; }
# Reject HTML error pages (GitHub 404, rate limits, etc.)
if head -c 200 "$dest" 2>/dev/null | grep -qi "<!DOCTYPE\|<html"; then
rm -f "$dest"; return 1
fi fi
return 0 if "$@" >/dev/null 2>&1; then
} ok "$name"
INSTALLED+=("$name")
# ── Cross-platform package install helper ───────────────────────────────────── else
# pkg_install <brew> <apt> <pacman> <dnf> (pass "" to skip that pkg manager) fail "$name — install failed (run manually: $*)"
pkg_install() { FAILED+=("$name")
local brew_pkg="${1:-}" apt_pkg="${2:-}" pac_pkg="${3:-}" dnf_pkg="${4:-}"
if [[ $HAS_BREW -eq 1 && -n "$brew_pkg" ]]; then brew install "$brew_pkg" >/dev/null 2>&1
elif [[ $HAS_APT -eq 1 && -n "$apt_pkg" && $HAS_SUDO -eq 1 ]]; then sudo apt-get install -y "$apt_pkg" >/dev/null 2>&1
elif [[ $HAS_PACMAN -eq 1 && -n "$pac_pkg" && $HAS_SUDO -eq 1 ]]; then sudo pacman -S --noconfirm "$pac_pkg" >/dev/null 2>&1
elif [[ $HAS_DNF -eq 1 && -n "$dnf_pkg" && $HAS_SUDO -eq 1 ]]; then sudo dnf install -y "$dnf_pkg" >/dev/null 2>&1
else return 1
fi fi
} }
# ── CPU architecture normalizer ───────────────────────────────────────────────
# Normalize uname -m to the naming convention used by GitHub releases
arch_github() {
case "$(uname -m)" in
x86_64) echo "x86_64" ;;
aarch64|arm64) echo "arm64" ;;
armv7l) echo "armv7" ;;
*) echo "$(uname -m)" ;;
esac
}
arch_linux_x64() {
# Returns x64 or arm64 style (used by marksman)
case "$(uname -m)" in
x86_64) echo "x64" ;;
aarch64|arm64) echo "arm64" ;;
*) echo "$(uname -m)" ;;
esac
}
echo -e "${BOLD}chopsticks — Vim Configuration Installer${NC}" echo -e "${BOLD}chopsticks — Vim Configuration Installer${NC}"
echo "----------------------------------------" echo "----------------------------------------"
# ============================================================================ # ============================================================================
# 1. OS + Package Manager Detection # Preflight
# ============================================================================ # ============================================================================
step "Detecting environment" step "Checking environment"
[ -f "$SCRIPT_DIR/.vimrc" ] || die ".vimrc not found in $SCRIPT_DIR"
command -v vim >/dev/null 2>&1 || die "vim not found.
Ubuntu/Debian: sudo apt install vim
Fedora: sudo dnf install vim
macOS: brew install vim"
VIM_VERSION=$(vim --version | head -n1)
ok "Found $VIM_VERSION"
vim --version | grep -q 'Vi IMproved 8\|Vi IMproved 9' || \
warn "Vim 8.0+ recommended for full LSP support."
# Detect OS
OS="unknown" OS="unknown"
if [[ "$OSTYPE" == darwin* ]]; then if [[ "$OSTYPE" == darwin* ]]; then
OS="macos" OS="macos"
@ -130,258 +88,89 @@ elif [[ -f /etc/arch-release ]]; then
fi fi
ok "OS: $OS" ok "OS: $OS"
HAS_BREW=0; command -v brew >/dev/null 2>&1 && HAS_BREW=1 # Detect package managers
HAS_APT=0; command -v apt >/dev/null 2>&1 && HAS_APT=1 HAS_BREW=0; command -v brew >/dev/null 2>&1 && HAS_BREW=1
HAS_DNF=0; command -v dnf >/dev/null 2>&1 && HAS_DNF=1 HAS_APT=0; command -v apt >/dev/null 2>&1 && HAS_APT=1
HAS_DNF=0; command -v dnf >/dev/null 2>&1 && HAS_DNF=1
HAS_PACMAN=0; command -v pacman >/dev/null 2>&1 && HAS_PACMAN=1 HAS_PACMAN=0; command -v pacman >/dev/null 2>&1 && HAS_PACMAN=1
HAS_NODE=0; command -v node >/dev/null 2>&1 && HAS_NODE=1 && ok "Node.js $(node --version) detected"
# ── sudo ─────────────────────────────────────────────────────────────────────
HAS_SUDO=0
if [[ $OS == "macos" ]]; then
# brew handles its own privilege escalation; no sudo needed for system tools
HAS_SUDO=1
elif sudo -n true 2>/dev/null; then
HAS_SUDO=1
ok "sudo: available (passwordless)"
elif [[ $AUTO_YES -eq 1 ]]; then
warn "sudo requires a password but running non-interactively (--yes)"
warn "System package installations will be skipped"
else
# Prompt once for password now so later sudo calls don't interrupt flow
warn "Some steps require sudo. Authenticating now..."
if sudo true; then
HAS_SUDO=1
ok "sudo: authenticated"
else
warn "sudo not available — system package installations will be skipped"
fi
fi
# ── Network ──────────────────────────────────────────────────────────────────
if curl -fsSL --connect-timeout 5 https://github.com -o /dev/null 2>/dev/null; then
ok "Network: github.com reachable"
else
warn "Network: cannot reach github.com — plugin and binary downloads may fail"
warn "Check your internet connection or proxy settings before continuing"
fi
# ── Homebrew (macOS) ─────────────────────────────────────────────────────────
if [[ $OS == "macos" && $HAS_BREW -eq 0 ]]; then
warn "Homebrew not found — it is the recommended package manager for macOS"
if ask "Install Homebrew now? (strongly recommended — required for system tools)"; then
info "This may take a few minutes and will prompt for your password..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || \
die "Homebrew installation failed. Install manually: https://brew.sh"
# Source brew for Apple Silicon and Intel paths
for brew_path in /opt/homebrew/bin/brew /usr/local/bin/brew; do
[[ -x "$brew_path" ]] && eval "$("$brew_path" shellenv)" && break
done
command -v brew >/dev/null 2>&1 && HAS_BREW=1 && ok "Homebrew installed"
else
warn "Homebrew skipped — system tools (ripgrep, fzf, etc.) will be unavailable"
fi
fi
# ── curl ─────────────────────────────────────────────────────────────────────
if ! command -v curl >/dev/null 2>&1; then
warn "curl not found — required to download plugins and tools"
if pkg_install curl curl curl curl 2>/dev/null; then
ok "curl installed"
else
die "curl is required but could not be installed automatically.
Ubuntu/Debian: sudo apt install curl
Arch: sudo pacman -S curl
Fedora: sudo dnf install curl
macOS: brew install curl"
fi
fi
# ── git ──────────────────────────────────────────────────────────────────────
if ! command -v git >/dev/null 2>&1; then
warn "git not found — required for vim-plug to install plugins"
if pkg_install git git git git 2>/dev/null; then
ok "git installed"
else
die "git is required but could not be installed automatically.
Ubuntu/Debian: sudo apt install git
Arch: sudo pacman -S git
Fedora: sudo dnf install git
macOS: brew install git (or: xcode-select --install)"
fi
fi
# ── vim ──────────────────────────────────────────────────────────────────────
[ -f "$SCRIPT_DIR/.vimrc" ] || die ".vimrc not found in $SCRIPT_DIR — is this the chopsticks repo?"
if ! command -v vim >/dev/null 2>&1; then
warn "vim not found — attempting to install"
if pkg_install vim vim vim vim 2>/dev/null; then
ok "vim installed"
else
die "vim not found and could not be installed automatically.
Ubuntu/Debian: sudo apt install vim
Arch: sudo pacman -S vim
Fedora: sudo dnf install vim
macOS: brew install vim"
fi
fi
VIM_VERSION=$(vim --version | head -n1)
ok "Found: $VIM_VERSION"
vim --version | grep -q 'Vi IMproved 8\|Vi IMproved 9' || \
warn "Vim 8.0+ recommended for full async/LSP support — some features may not work"
# ── Node.js ──────────────────────────────────────────────────────────────────
HAS_NODE=0; command -v node >/dev/null 2>&1 && HAS_NODE=1
if [[ $HAS_NODE -eq 0 ]]; then
warn "Node.js not found — CoC LSP and npm-based formatters will be unavailable"
info "Without Node.js, the config falls back to vim-lsp (pure VimScript)."
info ""
info "Install options:"
info " nvm (recommended): curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/HEAD/install.sh | bash"
info " macOS: brew install node"
info " Ubuntu/Debian: curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -"
info " sudo apt-get install -y nodejs"
info " Arch: sudo pacman -S nodejs npm"
info ""
if ask "Install Node.js via nvm now? (recommended — manages multiple Node versions)"; then
info "Fetching latest nvm release..."
NVM_VER=$(curl -fsSL https://api.github.com/repos/nvm-sh/nvm/releases/latest \
| grep '"tag_name"' | cut -d'"' -f4 2>/dev/null) || NVM_VER="v0.40.1"
[[ -z "$NVM_VER" ]] && NVM_VER="v0.40.1"
info "Installing nvm $NVM_VER + Node.js LTS..."
if curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VER}/install.sh" | bash >/dev/null 2>&1; then
export NVM_DIR="$HOME/.nvm"
# shellcheck disable=SC1091
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
if command -v nvm >/dev/null 2>&1; then
nvm install --lts >/dev/null 2>&1 && nvm use --lts >/dev/null 2>&1 || true
command -v node >/dev/null 2>&1 && HAS_NODE=1 && ok "Node.js $(node --version) installed via nvm"
fi
fi
if [[ $HAS_NODE -eq 0 ]]; then
warn "nvm install failed — CoC and npm tools will be skipped"
warn "After manually installing Node.js, re-run: ./install.sh"
fi
else
skip "Node.js — config will use vim-lsp fallback (no Node.js required)"
fi
else
ok "Node.js $(node --version) found"
fi
# ── Python3 ──────────────────────────────────────────────────────────────────
HAS_PYTHON=0; command -v python3 >/dev/null 2>&1 && HAS_PYTHON=1 HAS_PYTHON=0; command -v python3 >/dev/null 2>&1 && HAS_PYTHON=1
HAS_PIP=0; command -v pip3 >/dev/null 2>&1 && HAS_PIP=1 HAS_PIP=0; command -v pip3 >/dev/null 2>&1 && HAS_PIP=1
HAS_GO=0; command -v go >/dev/null 2>&1 && HAS_GO=1 && ok "Go $(go version | awk '{print $3}') detected"
if [[ $HAS_PYTHON -eq 0 ]]; then # Bootstrap pip3 when python3 exists but pip3 is absent (common on Ubuntu minimal images)
warn "python3 not found — Python formatters/linters will be unavailable"
if ask "Install Python 3?"; then
if pkg_install python3 python3 python3 python3 2>/dev/null; then
command -v python3 >/dev/null 2>&1 && HAS_PYTHON=1 && ok "Python3 installed"
else
warn "Python3 install failed — Python tools will be skipped"
fi
else
skip "Python3"
fi
fi
# Bootstrap pip3 when python3 exists but pip3 is absent (common on Ubuntu minimal)
if [[ $HAS_PYTHON -eq 1 && $HAS_PIP -eq 0 ]]; then if [[ $HAS_PYTHON -eq 1 && $HAS_PIP -eq 0 ]]; then
warn "python3 found but pip3 missing — attempting bootstrap" warn "python3 found but pip3 missing — attempting bootstrap"
if python3 -m ensurepip --upgrade >/dev/null 2>&1 || \ if python3 -m ensurepip --upgrade >/dev/null 2>&1 || \
pkg_install python3-pip python3-pip python-pip python3-pip >/dev/null 2>&1; then (command -v apt-get >/dev/null 2>&1 && sudo apt-get install -y python3-pip >/dev/null 2>&1) || \
(command -v pacman >/dev/null 2>&1 && sudo pacman -S --noconfirm python-pip >/dev/null 2>&1) || \
(command -v dnf >/dev/null 2>&1 && sudo dnf install -y python3-pip >/dev/null 2>&1); then
command -v pip3 >/dev/null 2>&1 && HAS_PIP=1 && ok "pip3 bootstrapped" command -v pip3 >/dev/null 2>&1 && HAS_PIP=1 && ok "pip3 bootstrapped"
else else
warn "pip3 bootstrap failed — Python tools will be skipped" warn "pip3 bootstrap failed — Python tools will be skipped"
fi fi
fi fi
[[ $HAS_PIP -eq 1 ]] && ok "Python/pip3 found" [[ $HAS_PIP -eq 1 ]] && ok "Python/pip3 detected"
[[ $HAS_PYTHON -eq 1 && $HAS_PIP -eq 0 ]] && warn "pip3 not available — Python tools will be skipped" [[ $HAS_NODE -eq 0 ]] && warn "Node.js not found — JS/TS/Markdown npm tools will be skipped"
[[ $HAS_PIP -eq 0 ]] && warn "pip3 not found — Python tools will be skipped"
# ── Go ─────────────────────────────────────────────────────────────────────── [[ $HAS_GO -eq 0 ]] && warn "Go not found — Go tools will be skipped"
HAS_GO=0; command -v go >/dev/null 2>&1 && HAS_GO=1
[[ $HAS_GO -eq 1 ]] && ok "Go $(go version | awk '{print $3}') found"
[[ $HAS_GO -eq 0 ]] && warn "Go not found — Go tools will be skipped (see https://go.dev/dl/)"
# ============================================================================ # ============================================================================
# 2. Symlinks # Symlink
# ============================================================================ # ============================================================================
step "Setting up symlinks" step "Setting up ~/.vimrc symlink"
if [ -f "$HOME/.vimrc" ] && [ ! -L "$HOME/.vimrc" ]; then if [ -f "$HOME/.vimrc" ] && [ ! -L "$HOME/.vimrc" ]; then
TS=$(date +%Y%m%d_%H%M%S) TS=$(date +%Y%m%d_%H%M%S)
warn "Backing up existing ~/.vimrc ~/.vimrc.backup.$TS" warn "Backing up existing ~/.vimrc to ~/.vimrc.backup.$TS"
mv "$HOME/.vimrc" "$HOME/.vimrc.backup.$TS" mv "$HOME/.vimrc" "$HOME/.vimrc.backup.$TS"
fi fi
ln -sf "$SCRIPT_DIR/.vimrc" "$HOME/.vimrc"
# Verify symlink
[[ -L "$HOME/.vimrc" ]] && ok "~/.vimrc → $SCRIPT_DIR/.vimrc" || die "Failed to create ~/.vimrc symlink"
ln -sf "$SCRIPT_DIR/.vimrc" "$HOME/.vimrc"
ok "~/.vimrc -> $SCRIPT_DIR/.vimrc"
# CoC settings (marksman markdown LSP + format-on-save config)
mkdir -p "$HOME/.vim" mkdir -p "$HOME/.vim"
COC_CFG="$HOME/.vim/coc-settings.json" COC_CFG="$HOME/.vim/coc-settings.json"
if [ -f "$COC_CFG" ] && [ ! -L "$COC_CFG" ]; then if [ -f "$COC_CFG" ] && [ ! -L "$COC_CFG" ]; then
TS=$(date +%Y%m%d_%H%M%S) TS=$(date +%Y%m%d_%H%M%S)
warn "Backing up existing coc-settings.json ~/.vim/coc-settings.json.backup.$TS" warn "Backing up existing coc-settings.json to ~/.vim/coc-settings.json.backup.$TS"
mv "$COC_CFG" "$COC_CFG.backup.$TS" mv "$COC_CFG" "$COC_CFG.backup.$TS"
fi fi
ln -sf "$SCRIPT_DIR/coc-settings.json" "$COC_CFG" ln -sf "$SCRIPT_DIR/coc-settings.json" "$COC_CFG"
[[ -L "$COC_CFG" ]] && ok "~/.vim/coc-settings.json → $SCRIPT_DIR/coc-settings.json" || warn "coc-settings.json symlink failed (non-fatal)" ok "~/.vim/coc-settings.json -> $SCRIPT_DIR/coc-settings.json"
# ============================================================================ # ============================================================================
# 3. vim-plug + Plugins # vim-plug + plugins
# ============================================================================ # ============================================================================
step "Installing vim-plug" step "Installing vim-plug"
VIM_PLUG="$HOME/.vim/autoload/plug.vim" VIM_PLUG="$HOME/.vim/autoload/plug.vim"
if [ ! -f "$VIM_PLUG" ]; then if [ ! -f "$VIM_PLUG" ]; then
mkdir -p "$HOME/.vim/autoload" curl -fLo "$VIM_PLUG" --create-dirs \
if safe_download "https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim" "$VIM_PLUG"; then https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
ok "vim-plug downloaded" ok "vim-plug installed"
else
# Fallback: git clone
warn "curl download failed — trying git clone fallback"
if git clone --depth=1 https://github.com/junegunn/vim-plug.git /tmp/vim-plug-src 2>/dev/null; then
cp /tmp/vim-plug-src/plug.vim "$VIM_PLUG" && rm -rf /tmp/vim-plug-src
ok "vim-plug installed (via git)"
else
die "vim-plug installation failed. Check your network connection and try again."
fi
fi
[[ -s "$VIM_PLUG" ]] || die "vim-plug file is empty after download — aborting"
else else
ok "vim-plug already present" ok "vim-plug already present"
fi fi
step "Installing Vim plugins" step "Installing Vim plugins"
info "(Vim will open fullscreen to install plugins — screen may go dark for 10-30s, this is normal)"
# </dev/null prevents Vim from reading stdin in non-interactive/piped environments # </dev/null prevents Vim from reading stdin in non-interactive/piped environments
if ! vim +PlugInstall +qall </dev/null; then vim +PlugInstall +qall </dev/null
warn "vim +PlugInstall exited non-zero — plugins may be partially installed" ok "Plugins installed"
warn "Run :PlugInstall manually inside Vim if something looks wrong"
else
ok "Plugins installed"
fi
# ============================================================================ # ============================================================================
# 4. System Tools # System tools (ripgrep, fzf, ctags, shellcheck, marksman)
# ============================================================================ # ============================================================================
step "System tools" step "System tools"
if [[ $OS == "macos" && $HAS_BREW -eq 0 ]]; then if ask "Install system tools (ripgrep, fzf, ctags, shellcheck, hadolint, marksman)?"; then
skip "system tools (Homebrew not available — install brew first, then re-run)"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman")
elif ask "Install system tools (ripgrep, fzf, ctags, shellcheck, hadolint, marksman)?"; then
install_sys() { install_sys() {
local name="$1" check="$2"; shift 2 local name="$1"; local check="$2"; shift 2
if command -v "$check" >/dev/null 2>&1; then if command -v "$check" >/dev/null 2>&1; then
ok "$name (already installed)" ok "$name (already installed)"
return return
@ -391,110 +180,92 @@ elif ask "Install system tools (ripgrep, fzf, ctags, shellcheck, hadolint, marks
if eval "$cmd" >/dev/null 2>&1; then installed=1; break; fi if eval "$cmd" >/dev/null 2>&1; then installed=1; break; fi
done done
if [[ $installed -eq 1 ]]; then if [[ $installed -eq 1 ]]; then
ok "$name"; INSTALLED+=("$name") ok "$name"
INSTALLED+=("$name")
else else
fail "$name — could not install automatically (install manually)" fail "$name — could not install automatically"
FAILED+=("$name") FAILED+=("$name")
fi fi
} }
if [[ $OS == "macos" ]]; then if [[ $OS == macos ]]; then
install_sys "ripgrep" rg "brew install ripgrep" command -v brew >/dev/null 2>&1 || { warn "brew not found — skipping system tools"; }
install_sys "fzf" fzf "brew install fzf" install_sys "ripgrep" rg "brew install ripgrep"
install_sys "universal-ctags" ctags "brew install universal-ctags" install_sys "fzf" fzf "brew install fzf"
install_sys "shellcheck" shellcheck "brew install shellcheck" install_sys "universal-ctags" ctags "brew install universal-ctags"
install_sys "hadolint" hadolint "brew install hadolint" install_sys "shellcheck" shellcheck "brew install shellcheck"
install_sys "marksman" marksman "brew install marksman" install_sys "hadolint" hadolint "brew install hadolint"
install_sys "marksman" marksman "brew install marksman"
elif [[ $HAS_APT -eq 1 ]]; then elif [[ $HAS_APT -eq 1 ]]; then
if [[ $HAS_SUDO -eq 0 ]]; then sudo apt-get update -qq
warn "No sudo — skipping apt system tools" install_sys "ripgrep" rg "sudo apt-get install -y ripgrep"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman") install_sys "fzf" fzf "sudo apt-get install -y fzf"
install_sys "universal-ctags" ctags "sudo apt-get install -y universal-ctags"
install_sys "shellcheck" shellcheck "sudo apt-get install -y shellcheck"
# hadolint: no apt package, download binary
if ! command -v hadolint >/dev/null 2>&1; then
ARCH=$(uname -m)
[[ "$ARCH" == "x86_64" ]] && HARCH="x86_64" || HARCH="arm64"
HVER=$(curl -s https://api.github.com/repos/hadolint/hadolint/releases/latest \
| grep '"tag_name"' | cut -d'"' -f4)
if [[ -n "$HVER" ]]; then
curl -fsSL "https://github.com/hadolint/hadolint/releases/download/${HVER}/hadolint-Linux-${HARCH}" \
-o /tmp/hadolint && chmod +x /tmp/hadolint && sudo mv /tmp/hadolint /usr/local/bin/hadolint
ok "hadolint"
INSTALLED+=("hadolint")
else
warn "hadolint: could not detect latest release, install manually"
SKIPPED+=("hadolint")
fi
else else
sudo apt-get update -qq ok "hadolint (already installed)"
install_sys "ripgrep" rg "sudo apt-get install -y ripgrep" fi
install_sys "fzf" fzf "sudo apt-get install -y fzf" # marksman: no apt package, download binary
install_sys "universal-ctags" ctags "sudo apt-get install -y universal-ctags" if ! command -v marksman >/dev/null 2>&1; then
install_sys "shellcheck" shellcheck "sudo apt-get install -y shellcheck" ARCH=$(uname -m)
[[ "$ARCH" == "x86_64" ]] && MARCH="x64" || MARCH="arm64"
# hadolint: no apt package — download binary from GitHub releases MVER=$(curl -s https://api.github.com/repos/artempyanykh/marksman/releases/latest \
if command -v hadolint >/dev/null 2>&1; then | grep '"tag_name"' | cut -d'"' -f4)
ok "hadolint (already installed)" if [[ -n "$MVER" ]]; then
else curl -fsSL "https://github.com/artempyanykh/marksman/releases/download/${MVER}/marksman-linux-${MARCH}" \
HARCH=$(arch_github) -o /tmp/marksman && chmod +x /tmp/marksman && sudo mv /tmp/marksman /usr/local/bin/marksman
HVER=$(curl -fsSL https://api.github.com/repos/hadolint/hadolint/releases/latest \ ok "marksman"
| grep '"tag_name"' | cut -d'"' -f4 2>/dev/null) || HVER="" INSTALLED+=("marksman")
if [[ -n "$HVER" ]] && safe_download \ else
"https://github.com/hadolint/hadolint/releases/download/${HVER}/hadolint-Linux-${HARCH}" \ warn "marksman: could not detect latest release, install manually"
/tmp/chopsticks-hadolint; then SKIPPED+=("marksman")
chmod +x /tmp/chopsticks-hadolint && sudo mv /tmp/chopsticks-hadolint /usr/local/bin/hadolint fi
ok "hadolint"; INSTALLED+=("hadolint") else
else ok "marksman (already installed)"
fail "hadolint — download failed (install manually: https://github.com/hadolint/hadolint/releases)"
FAILED+=("hadolint")
fi
fi
# marksman: no apt package — download binary from GitHub releases
if command -v marksman >/dev/null 2>&1; then
ok "marksman (already installed)"
else
MARCH=$(arch_linux_x64)
MVER=$(curl -fsSL https://api.github.com/repos/artempyanykh/marksman/releases/latest \
| grep '"tag_name"' | cut -d'"' -f4 2>/dev/null) || MVER=""
if [[ -n "$MVER" ]] && safe_download \
"https://github.com/artempyanykh/marksman/releases/download/${MVER}/marksman-linux-${MARCH}" \
/tmp/chopsticks-marksman; then
chmod +x /tmp/chopsticks-marksman && sudo mv /tmp/chopsticks-marksman /usr/local/bin/marksman
ok "marksman"; INSTALLED+=("marksman")
else
fail "marksman — download failed (install manually: https://github.com/artempyanykh/marksman/releases)"
FAILED+=("marksman")
fi
fi
fi fi
elif [[ $HAS_PACMAN -eq 1 ]]; then elif [[ $HAS_PACMAN -eq 1 ]]; then
if [[ $HAS_SUDO -eq 0 ]]; then install_sys "ripgrep" rg "sudo pacman -S --noconfirm ripgrep"
warn "No sudo — skipping pacman system tools" install_sys "fzf" fzf "sudo pacman -S --noconfirm fzf"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman") install_sys "universal-ctags" ctags "sudo pacman -S --noconfirm ctags"
else install_sys "shellcheck" shellcheck "sudo pacman -S --noconfirm shellcheck"
install_sys "ripgrep" rg "sudo pacman -S --noconfirm ripgrep" install_sys "hadolint" hadolint "sudo pacman -S --noconfirm hadolint"
install_sys "fzf" fzf "sudo pacman -S --noconfirm fzf" install_sys "marksman" marksman "sudo pacman -S --noconfirm marksman"
install_sys "universal-ctags" ctags "sudo pacman -S --noconfirm ctags"
install_sys "shellcheck" shellcheck "sudo pacman -S --noconfirm shellcheck"
install_sys "hadolint" hadolint "sudo pacman -S --noconfirm hadolint"
install_sys "marksman" marksman "sudo pacman -S --noconfirm marksman"
fi
elif [[ $HAS_DNF -eq 1 ]]; then elif [[ $HAS_DNF -eq 1 ]]; then
if [[ $HAS_SUDO -eq 0 ]]; then install_sys "ripgrep" rg "sudo dnf install -y ripgrep"
warn "No sudo — skipping dnf system tools" install_sys "fzf" fzf "sudo dnf install -y fzf"
SKIPPED+=("ripgrep" "fzf" "shellcheck" "ctags" "hadolint" "marksman") install_sys "shellcheck" shellcheck "sudo dnf install -y ShellCheck"
else skip "universal-ctags — install manually: sudo dnf install ctags"
install_sys "ripgrep" rg "sudo dnf install -y ripgrep" SKIPPED+=("ctags")
install_sys "fzf" fzf "sudo dnf install -y fzf" skip "hadolint — install manually from https://github.com/hadolint/hadolint/releases"
install_sys "shellcheck" shellcheck "sudo dnf install -y ShellCheck" SKIPPED+=("hadolint")
skip "universal-ctags — install manually: sudo dnf install ctags" skip "marksman — install manually from https://github.com/artempyanykh/marksman/releases"
SKIPPED+=("ctags") SKIPPED+=("marksman")
skip "hadolint — install manually: https://github.com/hadolint/hadolint/releases"
SKIPPED+=("hadolint")
skip "marksman — install manually: https://github.com/artempyanykh/marksman/releases"
SKIPPED+=("marksman")
fi
else else
warn "Unknown distro — skipping system tools (install manually)" warn "Unknown Linux distro — skipping system tools (install manually)"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman") SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman")
fi fi
else else
skip "system tools" skip "system tools"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman") SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman")
fi fi
# ============================================================================ # ============================================================================
# 5. npm tools # npm tools (prettier, markdownlint-cli, stylelint, eslint, typescript)
# ============================================================================ # ============================================================================
step "npm tools (formatters + linters)" step "npm tools (formatters + linters)"
@ -504,12 +275,15 @@ if [[ $HAS_NODE -eq 1 ]]; then
npm_install() { npm_install() {
local pkg="$1"; local check="${2:-$1}" local pkg="$1"; local check="${2:-$1}"
if command -v "$check" >/dev/null 2>&1; then if command -v "$check" >/dev/null 2>&1; then
ok "$pkg (already installed)"; return ok "$pkg (already installed)"
return
fi fi
if npm install -g "$pkg" >/dev/null 2>&1; then if npm install -g "$pkg" >/dev/null 2>&1; then
ok "$pkg"; INSTALLED+=("$pkg") ok "$pkg"
INSTALLED+=("$pkg")
else else
fail "$pkg"; FAILED+=("$pkg") fail "$pkg"
FAILED+=("$pkg")
fi fi
} }
npm_install prettier npm_install prettier
@ -528,7 +302,7 @@ else
fi fi
# ============================================================================ # ============================================================================
# 6. Python tools # pip tools (black, isort, flake8, pylint, sqlfluff)
# ============================================================================ # ============================================================================
step "Python tools (formatters + linters)" step "Python tools (formatters + linters)"
@ -538,13 +312,16 @@ if [[ $HAS_PIP -eq 1 ]]; then
pip_install() { pip_install() {
local pkg="$1"; local check="${2:-$1}" local pkg="$1"; local check="${2:-$1}"
if command -v "$check" >/dev/null 2>&1; then if command -v "$check" >/dev/null 2>&1; then
ok "$pkg (already installed)"; return ok "$pkg (already installed)"
return
fi fi
if pip3 install --quiet "$pkg" 2>/dev/null || \ if pip3 install --quiet "$pkg" 2>/dev/null || \
pip3 install --quiet --break-system-packages "$pkg" 2>/dev/null; then pip3 install --quiet --break-system-packages "$pkg" 2>/dev/null; then
ok "$pkg"; INSTALLED+=("$pkg") ok "$pkg"
INSTALLED+=("$pkg")
else else
fail "$pkg"; FAILED+=("$pkg") fail "$pkg"
FAILED+=("$pkg")
fi fi
} }
pip_install black pip_install black
@ -563,94 +340,65 @@ else
fi fi
# ============================================================================ # ============================================================================
# 7. Go tools # Go tools (gopls, goimports, staticcheck)
# ============================================================================ # ============================================================================
step "Go tools" step "Go tools"
if [[ $HAS_GO -eq 1 ]]; then if [[ $HAS_GO -eq 1 ]]; then
if ask "Install Go tools (gopls, goimports, staticcheck)?"; then if ask "Install Go tools (gopls, goimports, staticcheck)?"; then
# Go installs binaries to $(go env GOPATH)/bin — add to PATH for this session
GOBIN="$(go env GOPATH)/bin" GOBIN="$(go env GOPATH)/bin"
export PATH="$PATH:$GOBIN" export PATH="$PATH:$GOBIN"
go_install() { go_install() {
local name="$1" pkg="$2" check="$3" local name="$1"; local pkg="$2"; local check="$3"
if command -v "$check" >/dev/null 2>&1 || [[ -x "$GOBIN/$check" ]]; then if command -v "$check" >/dev/null 2>&1 || [[ -x "$GOBIN/$check" ]]; then
ok "$name (already installed)"; return ok "$name (already installed)"
return
fi fi
if go install "$pkg" >/dev/null 2>&1; then if go install "$pkg" >/dev/null 2>&1; then
ok "$name"; INSTALLED+=("$name") ok "$name"
INSTALLED+=("$name")
else else
fail "$name"; FAILED+=("$name") fail "$name"
FAILED+=("$name")
fi fi
} }
go_install gopls "golang.org/x/tools/gopls@latest" gopls go_install gopls "golang.org/x/tools/gopls@latest" gopls
go_install goimports "golang.org/x/tools/cmd/goimports@latest" goimports go_install goimports "golang.org/x/tools/cmd/goimports@latest" goimports
go_install staticcheck "honnef.co/go/tools/cmd/staticcheck@latest" staticcheck go_install staticcheck "honnef.co/go/tools/cmd/staticcheck@latest" staticcheck
echo "$PATH" | grep -q "$GOBIN" || \ # Remind user to add GOPATH/bin to their shell profile
if ! echo "$PATH" | grep -q "$GOBIN"; then
warn "Add Go binaries to PATH: export PATH=\"\$PATH:$GOBIN\"" warn "Add Go binaries to PATH: export PATH=\"\$PATH:$GOBIN\""
fi
else else
skip "Go tools" skip "Go tools"
SKIPPED+=("gopls" "goimports" "staticcheck") SKIPPED+=("gopls" "goimports" "staticcheck")
fi fi
else else
skip "Go tools (go not installed — see https://go.dev/dl/)" skip "Go tools (go not installed)"
SKIPPED+=("gopls" "goimports" "staticcheck") SKIPPED+=("gopls" "goimports" "staticcheck")
fi fi
# ============================================================================ # ============================================================================
# 8. tmux: vim-tmux-navigator integration # CoC language server extensions
# ============================================================================
step "tmux: vim-tmux-navigator integration"
if command -v tmux >/dev/null 2>&1; then
TMUX_CONF="$HOME/.tmux.conf"
if grep -q 'vim-tmux-navigator' "$TMUX_CONF" 2>/dev/null; then
ok "vim-tmux-navigator bindings already present in ~/.tmux.conf"
elif ask "Append vim-tmux-navigator bindings to ~/.tmux.conf (enables seamless Ctrl+h/j/k/l across vim and tmux)?"; then
cat >> "$TMUX_CONF" << 'TMUXEOF'
# vim-tmux-navigator: seamless Ctrl+h/j/k/l navigation between vim and tmux
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\S+\/)?g?(view|n?vim?x?)(diff)?$'"
bind-key -n 'C-h' if-shell "$is_vim" 'send-keys C-h' 'select-pane -L'
bind-key -n 'C-j' if-shell "$is_vim" 'send-keys C-j' 'select-pane -D'
bind-key -n 'C-k' if-shell "$is_vim" 'send-keys C-k' 'select-pane -U'
bind-key -n 'C-l' if-shell "$is_vim" 'send-keys C-l' 'select-pane -R'
TMUXEOF
ok "vim-tmux-navigator bindings appended to ~/.tmux.conf"
warn "Reload tmux config now: tmux source-file ~/.tmux.conf"
warn "Note: C-l now navigates panes instead of clearing the screen."
warn " To restore clear: add 'bind C-l send-keys C-l' to ~/.tmux.conf"
INSTALLED+=("tmux-navigator-config")
else
skip "tmux navigator config"
SKIPPED+=("tmux-navigator-config")
fi
else
skip "tmux not found — skipping navigator config"
SKIPPED+=("tmux-navigator-config")
fi
# ============================================================================
# 9. CoC language server extensions
# ============================================================================ # ============================================================================
step "CoC language server extensions" step "CoC language server extensions"
if [[ $HAS_NODE -eq 1 ]]; then if [[ $HAS_NODE -eq 1 ]]; then
if ask "Install CoC language servers (LSP for all configured languages)?"; then if ask "Install CoC language servers (LSP for all configured languages)?"; then
info "(Downloading CoC extensions via npm — screen may go dark for 1-3 minutes, this is normal)"
# Note: coc-marksman doesn't exist on npm — markdown LSP is handled via coc-settings.json # Note: coc-marksman doesn't exist on npm — markdown LSP is handled via coc-settings.json
vim +'CocInstall -sync coc-json coc-tsserver coc-pyright coc-sh coc-html coc-css coc-yaml coc-go coc-rust-analyzer coc-sql' +qall </dev/null vim +'CocInstall -sync coc-json coc-tsserver coc-pyright coc-sh coc-html coc-css coc-yaml coc-go coc-rust-analyzer coc-sql' +qall </dev/null
ok "CoC language servers installed" ok "CoC language servers installed"
else else
skip "CoC language servers" skip "CoC language servers"
info "Install later with :CocInstall <name> inside Vim" echo " Install later with :CocInstall <name> inside Vim"
fi fi
else else
warn "Node.js not found — using vim-lsp fallback (run :LspInstallServer inside Vim for each language)" warn "Node.js not found — using vim-lsp fallback (run :LspInstallServer inside Vim)"
fi fi
# ============================================================================ # ============================================================================
@ -673,26 +421,8 @@ fi
if [[ ${#FAILED[@]} -gt 0 ]]; then if [[ ${#FAILED[@]} -gt 0 ]]; then
echo -e "\n${RED}Failed (install manually):${NC}" echo -e "\n${RED}Failed (install manually):${NC}"
for t in "${FAILED[@]}"; do echo " ! $t"; done for t in "${FAILED[@]}"; do echo " ! $t"; done
echo ""
echo " To debug failures: ./install.sh 2>&1 | tee /tmp/chopsticks-install.log"
fi fi
echo "" echo ""
echo -e "${BOLD}---------------------------------------${NC}" echo "Run 'vim' and press ',' then wait 500ms for keybinding hints."
echo -e "${BOLD} You're ready. Open Vim with:${NC}"
echo -e "${BOLD}---------------------------------------${NC}"
echo -e " ${CYAN}vim${NC} Launch startup dashboard"
echo -e " ${CYAN}vim .${NC} Open file tree + dashboard"
echo -e " ${CYAN}vim myfile${NC} Edit a specific file"
echo ""
echo -e "${BOLD} Survival Guide (first-time Vim users)${NC}"
echo -e " ${CYAN}Esc${NC} or ${CYAN}jk${NC} Exit insert mode → back to Normal"
echo -e " ${CYAN}:q!${NC} + Enter Emergency quit without saving"
echo -e " ${CYAN},x${NC} Save and quit"
echo -e " ${CYAN},?${NC} Open cheat sheet inside Vim"
echo -e " ${CYAN},${NC} + pause Interactive keybinding guide"
echo ""
echo -e "${YELLOW}[!]${NC} Ctrl+s is mapped to save in Vim."
echo " If it freezes your terminal, add this to ~/.bashrc or ~/.zshrc:"
echo -e " ${CYAN}stty -ixon${NC}"
echo "" echo ""