Compare commits

...

4 commits

Author SHA1 Message Date
825633d623 feat: robust installer with preflight checks, one-command get.sh
install.sh:
- set -eo pipefail + trap ERR with line number and debug hint
- Network connectivity check before any downloads
- curl and git preflight: auto-install or die with clear instructions
- vim auto-install attempt before dying
- sudo availability check (one-time auth, graceful skip when unavailable)
- macOS: offer to install Homebrew if missing
- Node.js: offer nvm install if missing (with fallback to vim-lsp)
- Python3: offer install if missing
- ask() reads from /dev/tty — interactive prompts work under curl | bash
- safe_download(): verifies file non-empty and not an HTML error page
- pkg_install(): unified cross-platform helper (brew/apt/pacman/dnf)
- arch_github()/arch_linux_x64(): normalize uname -m (handles aarch64)
- Temp files cleaned up via EXIT trap
- Symlink creation verified after ln -sf
- vim-plug: fallback to git clone if curl fails; verify file non-empty
- vim +PlugInstall: warn on failure instead of silent continue
- Binary downloads (hadolint, marksman) use named temp files

get.sh (new):
- One-command bootstrap: curl | bash
- Installs git if missing
- Clones repo to ~/.vim (or git pull if already present)
- exec bash install.sh </dev/tty so interactive prompts work correctly
2026-04-09 13:59:30 +08:00
e3877edaeb docs: add CHANGELOG entry for v1.1.1
Documents the 15 best-practice settings absorbed from top global vim
configs (amix, tpope/vim-sensible, ThePrimeagen, YADR, spf13) that
were committed in the previous release but missing from the changelog.
2026-04-09 13:41:45 +08:00
74071f7464 feat: absorb 15 best practices from top global vim configs (amix, tpope, ThePrimeagen, YADR, spf13)
Settings (vim-sensible / community consensus):
- set ttimeoutlen=10 — eliminates ESC lag in terminal Vim
- set display+=lastline — show truncated last line instead of @@@
- set complete-=i — faster Ctrl+n/p by not scanning included files
- set wildignorecase — case-insensitive filename completion
- set path+=** — recursive :find with wildignore exclusions
- set sessionoptions — clean session saves without stale plugin options
- Expand wildignore: node_modules, __pycache__, dist, build

Visible whitespace:
- set listchars (TTY: ASCII; GUI: Unicode symbols) — F6 to toggle

Line-length guides (textwidth + colorcolumn=+1):
- Python: 88, Go: 120, JS/TS: 100, Rust: 100, Shell: 80
- Markdown: no limit (colorcolumn=0), all others via +1

Autocmds:
- ChopstickFormatOptions: formatoptions-=cro on BufEnter (disables
  auto-comment-continuation — universally desired, overrides filetype plugins)
- ChopstickPaste: InsertLeave → set nopaste (prevents broken indent)

Key mappings:
- vnoremap J/K — move selected lines with =gv re-indent (ThePrimeagen)
- gV — reselect last pasted text (`[v`] — spf13, YADR)
- cnoremap Ctrl+p/n — command history navigation (amix, spf13)
- <leader>e — :Explore (built-in file browser, plugin-free fallback)
- <leader>cd — lcd to current file's directory (was: cd, now window-local)
- F6 — toggle visible whitespace
- <leader>sv — reload vimrc with confirmation echo
2026-04-09 12:43:47 +08:00
23ad2e5b7a feat: ergonomics overhaul, tmux integration, and beginner onboarding (v1.1.0)
Keybindings:
- Add jk → Esc in insert mode (ergonomic escape)
- Add Ctrl+s save in normal and insert mode
- Add // visual search (very-nomagic escaped)
- Add <leader>p/P clipboard paste
- Add <leader>rG ripgrep word under cursor (-F literal)
- Add <leader>u / <leader>tt as leader aliases for F5/F8
- Fix ALE [e/]e navigation direction (was reversed)
- Remove dead <C-h/j/k/l> maps (owned by vim-tmux-navigator)
- Remove <leader>pp (duplicate of F2, caused 500ms delay on <leader>p)

Plugins:
- Add vim-tmux-navigator for seamless Vim/tmux pane navigation
- Fix <leader>rG: pass -F so regex metacharacters don't corrupt matches
- Fix LargeFileSettings: disable ALE for files >10MB

In-Vim UX:
- Add ,? cheat sheet (read-only buffer, q to close)
- ALE lint triggers: normal/enter/insert-leave now active

install.sh:
- Add tmux.conf auto-configuration step with C-l warning
- Add post-install survival guide for first-time Vim users

Docs:
- README: full badge row (release, stars, issues, last-commit, PRs, plugins, languages)
- README: Contributing section with bug/PR guidelines
- QUICKSTART: Step 0 — Vim modes and 4 survival commands for beginners
- CHANGELOG: v1.1.0 entry
2026-04-09 12:20:10 +08:00
6 changed files with 1207 additions and 425 deletions

234
.vimrc
View file

@ -73,9 +73,15 @@ set wildmenu
" Make wildmenu behave like similar to Bash completion
set wildmode=list:longest
" There are certain files that we would never want to edit with Vim
" Wildmenu will ignore files with these extensions
" Case-insensitive filename completion in wildmenu (spf13, YADR)
set wildignorecase
" Files and directories to exclude from wildmenu and :find
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
set mouse=a
@ -119,6 +125,11 @@ set novisualbell
set t_vb=
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
if $COLORTERM == 'gnome-terminal'
set t_Co=256
@ -132,6 +143,9 @@ if has("gui_running")
set guitablabel=%M\ %t
endif
" Show last line partially instead of replacing it with @@@ (vim-sensible)
set display+=lastline
" Use Unix as the standard file type
set ffs=unix,dos,mac
@ -207,6 +221,7 @@ Plug 'dhruvasagar/vim-prosession' " Better session management
Plug 'tpope/vim-unimpaired' " Handy bracket mappings
Plug 'wellle/targets.vim' " Additional text objects
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) =====
" Used as fallback when CoC/Node.js is unavailable
@ -287,6 +302,14 @@ endif
" => 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
set expandtab
@ -323,11 +346,8 @@ nmap <leader>x :x<cr>
" Disable highlight when <leader><cr> is pressed
map <silent> <leader><cr> :noh<cr>
" Smart way to move between windows
map <C-j> <C-W>j
map <C-k> <C-W>k
map <C-h> <C-W>h
map <C-l> <C-W>l
" Window navigation — owned by vim-tmux-navigator plugin (Ctrl+h/j/k/l works
" seamlessly across Vim splits and tmux panes; no manual maps needed here)
" Close the current buffer (Bclose preserves window layout)
map <leader>bd :Bclose<cr>
@ -359,17 +379,30 @@ augroup END
" Opens a new tab with the current buffer's path
map <leader>te :tabedit <C-r>=expand("%:p:h")<cr>/
" Switch CWD to the directory of the open buffer
map <leader>wd :cd %:p:h<cr>:pwd<cr>
" Change window-local CWD to current file's directory (lcd = local, safer than cd)
map <leader>cd :lcd %: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
map 0 ^
" Move a line of text using ALT+[jk] or Command+[jk] on mac
" Reselect last pasted text (gV = visual select last paste) — spf13, YADR
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-k> mz:m-2<cr>`z
vmap <M-j> :m'>+<cr>`<my`>mzgv`yo`z
vmap <M-k> :m'<-2<cr>`>my`<mzgv`yo`z
" Move selected lines up/down and re-indent (visual mode) — ThePrimeagen
" 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
map <leader>ss :setlocal spell!<cr>
@ -389,6 +422,9 @@ nnoremap <F3> :set invnumber<CR>
" Toggle relative line numbers
nnoremap <F4> :set invrelativenumber<CR>
" Toggle visible whitespace (tabs, trailing spaces, non-breaking spaces)
nnoremap <F6> :set list!<CR>
" Enable folding with the spacebar
nnoremap <space> za
@ -398,6 +434,9 @@ nnoremap Y y$
" Disable accidental Ex mode
nnoremap Q <nop>
" Exit insert mode without reaching for Escape (community standard)
inoremap jk <Esc>
" Keep visual selection after indent
vnoremap < <gv
vnoremap > >gv
@ -406,15 +445,25 @@ vnoremap > >gv
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
nnoremap <C-d> <C-d>zz
nnoremap <C-u> <C-u>zz
" System clipboard yank (conditional: requires clipboard provider)
" System clipboard yank/paste (conditional: requires clipboard provider)
if has('clipboard')
nnoremap <leader>y "+y
vnoremap <leader>y "+y
nnoremap <leader>Y "+Y
nnoremap <leader>p "+p
nnoremap <leader>P "+P
endif
" Quickfix list shortcuts ([q/]q from vim-unimpaired handles navigation)
@ -485,6 +534,7 @@ endfunction
map <C-p> :call <SID>SmartFiles()<CR>
map <leader>b :Buffers<CR>
map <leader>rg :Rg<CR>
map <leader>rG :Rg -F <C-r><C-w><CR>
map <leader>rt :Tags<CR>
map <leader>gF :GFiles<CR>
@ -583,9 +633,9 @@ let g:ale_fixers = {
let g:ale_fix_on_save = !g:use_vimlsp
let g:ale_sign_error = 'X'
let g:ale_sign_warning = '!'
let g:ale_lint_on_text_changed = 'never'
let g:ale_lint_on_insert_leave = 0
let g:ale_lint_on_enter = 0
let g:ale_lint_on_text_changed = 'normal'
let g:ale_lint_on_insert_leave = 1
let g:ale_lint_on_enter = 1
" --- 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
@ -602,16 +652,18 @@ let g:go_highlight_fields = 1
let g:go_highlight_functions = 1
let g:go_highlight_function_calls = 1
" Navigate between errors: [e/]e (unimpaired style), <leader>aD for detail
nmap <silent> [e :ALENext<cr>
nmap <silent> ]e :ALEPrevious<cr>
" Navigate between errors: [e/]e (unimpaired convention: [ = prev, ] = next)
nmap <silent> [e :ALEPrevious<cr>
nmap <silent> ]e :ALENext<cr>
nmap <silent> <leader>aD :ALEDetail<cr>
" --- Tagbar ---
nmap <F8> :TagbarToggle<CR>
nmap <leader>tt :TagbarToggle<CR>
" --- UndoTree ---
nnoremap <F5> :UndotreeToggle<CR>
nnoremap <leader>u :UndotreeToggle<CR>
" --- EasyMotion ---
let g:EasyMotion_do_mapping = 0 " Disable default mappings
@ -849,6 +901,19 @@ fun! CleanExtraSpaces()
call setreg('/', old_query)
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
autocmd!
" Run for real files only; skip special buffers (NERDTree, Startify, terminal, etc.)
@ -871,13 +936,16 @@ augroup ChopstickFiletype
autocmd BufNewFile,BufRead *.tsx setlocal filetype=typescript.tsx
" Python specific settings
autocmd FileType python setlocal expandtab shiftwidth=4 tabstop=4 colorcolumn=88
autocmd FileType python setlocal expandtab shiftwidth=4 tabstop=4 textwidth=88 colorcolumn=+1
" JavaScript specific settings
autocmd FileType javascript,typescript setlocal expandtab shiftwidth=2 tabstop=2
" JavaScript / TypeScript specific settings
autocmd FileType javascript,typescript setlocal expandtab shiftwidth=2 tabstop=2 textwidth=100 colorcolumn=+1
" Go specific settings
autocmd FileType go setlocal noexpandtab shiftwidth=4 tabstop=4
" Go specific settings (standard: no textwidth limit, but 120 is common)
autocmd FileType go setlocal noexpandtab shiftwidth=4 tabstop=4 textwidth=120 colorcolumn=+1
" Rust specific settings
autocmd FileType rust setlocal expandtab shiftwidth=4 tabstop=4 textwidth=100 colorcolumn=+1
" HTML/CSS specific settings
autocmd FileType html,css setlocal expandtab shiftwidth=2 tabstop=2
@ -885,11 +953,11 @@ augroup ChopstickFiletype
" YAML specific settings
autocmd FileType yaml setlocal expandtab shiftwidth=2 tabstop=2
" Markdown specific settings
autocmd FileType markdown setlocal wrap linebreak spell tw=0
" Markdown specific settings (no line-length limit; wrap at window edge)
autocmd FileType markdown setlocal wrap linebreak spell textwidth=0 colorcolumn=0
" Shell script settings
autocmd FileType sh setlocal expandtab shiftwidth=2 tabstop=2
autocmd FileType sh setlocal expandtab shiftwidth=2 tabstop=2 textwidth=80 colorcolumn=+1
" Makefile settings (must use tabs)
autocmd FileType make setlocal noexpandtab shiftwidth=8 tabstop=8
@ -929,9 +997,6 @@ function! ToggleNumber()
endif
endfunc
" Toggle paste mode
map <leader>pp :setlocal paste!<cr>
" ============================================================================
" => Performance Optimization
" ============================================================================
@ -940,6 +1005,9 @@ map <leader>pp :setlocal paste!<cr>
set synmaxcol=200
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
set updatetime=300
@ -967,6 +1035,10 @@ endif
set exrc
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
" ============================================================================
@ -995,8 +1067,8 @@ nnoremap <leader>so :source %<CR>
" Edit vimrc quickly
nnoremap <leader>ev :edit $MYVIMRC<CR>
" Reload vimrc
nnoremap <leader>sv :source $MYVIMRC<CR>
" Reload vimrc with confirmation echo
nnoremap <leader>sv :source $MYVIMRC<CR>:echo "vimrc reloaded"<CR>
" Search and replace word under cursor
nnoremap <leader>* :%s/\<<C-r><C-w>\>//g<Left><Left>
@ -1020,6 +1092,85 @@ augroup BWCCreateDir
autocmd BufWritePre * if !empty(expand('<afile>')) | call s:MkNonExDir(expand('<afile>'), +expand('<abuf>')) | endif
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
" ============================================================================
@ -1082,7 +1233,8 @@ function! LargeFileSettings()
setlocal eventignore+=FileType
setlocal noswapfile
setlocal syntax=OFF
echo "Large file (>10MB): syntax and undo disabled for performance."
let b:ale_enabled = 0
echo "Large file (>10MB): syntax, undo, and linting disabled for performance."
endfunction
" ============================================================================
@ -1148,6 +1300,10 @@ if exists('g:plugs["vim-which-key"]')
let g:which_key_map[','] = 'last-file'
let g:which_key_map['y'] = 'clipboard-yank'
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)
let g:which_key_map['a'] = {
@ -1169,9 +1325,9 @@ if exists('g:plugs["vim-which-key"]')
\ 'f': 'copy-filename',
\ }
" [e]dit group
" [e]dit / [e]xplore group
let g:which_key_map['e'] = {
\ 'name': '+edit',
\ 'name': '+edit/explore',
\ 'v': 'edit-vimrc',
\ }
@ -1200,6 +1356,7 @@ if exists('g:plugs["vim-which-key"]')
\ 'name': '+search/refactor',
\ 'n': 'rename',
\ 'g': 'ripgrep',
\ 'G': 'ripgrep-word-under-cursor',
\ 't': 'tags-search',
\ }
@ -1218,7 +1375,7 @@ if exists('g:plugs["vim-which-key"]')
" [t]ab / [t]erminal group
let g:which_key_map['t'] = {
\ 'name': '+tab/terminal',
\ 'name': '+tab/terminal/tagbar',
\ 'n': 'new-tab',
\ 'o': 'tab-only',
\ 'c': 'close-tab',
@ -1227,6 +1384,7 @@ if exists('g:plugs["vim-which-key"]')
\ 'e': 'edit-in-tab',
\ 'v': 'terminal-vertical',
\ 'h': 'terminal-horizontal',
\ 't': 'tagbar-toggle',
\ }
" [w]orkspace / [w]indow / save group (also: <leader>w = fast save)
@ -1234,8 +1392,10 @@ if exists('g:plugs["vim-which-key"]')
\ 'name': '+save/window',
\ 'a': 'save-all',
\ '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
" ============================================================================

View file

@ -4,6 +4,94 @@ 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
First stable release. Full-stack engineering environment out of the box — automatic

View file

@ -2,6 +2,39 @@
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
@ -66,19 +99,22 @@ This auto-detects and installs the correct language server for the current filet
---
## The 10 Keys That Matter
## The 12 Keys That Matter
```
, (pause 500ms) Show all shortcuts
, (pause 500ms) Show all keybindings (which-key)
,? 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+n Toggle file tree
gd Go to definition
K Show documentation
[g / ]g Prev / next diagnostic
[g / ]g Prev / next LSP diagnostic
,rn Rename symbol
,rg Search project contents
,rG Search word under cursor (ripgrep)
,gs Git status
,w / ,q Save / Quit
,w / ,x Save / Save+quit
```
---
@ -197,38 +233,51 @@ colorscheme dracula " or: gruvbox, solarized, onedark
## 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
Ctrl+n File tree toggle
Ctrl+p Fuzzy find file (git-aware)
,b Search open buffers
,rg Search file contents (ripgrep)
,w Save | ,q Quit | ,x Save+quit
,wa Save all buffers
Ctrl+n File tree toggle
Ctrl+p Fuzzy find file (git-aware)
,b Search open buffers
,rg Search file contents (ripgrep)
,rG Ripgrep word under cursor
,w Save | ,q Quit | ,x Save+quit
,wa Save all buffers
,, Switch to last file
CODE
gd Go to definition
K Show documentation
[g / ]g Prev/next diagnostic
[g / ]g Prev/next LSP diagnostic
[e / ]e Prev/next ALE error
,rn Rename symbol
,ca Code action
,f Format selection
,F Format whole file
,ca Code action / auto-fix
,f Format selection | ,F Format whole file
GIT
,gs Status | ,gd Diff | ,gb Blame
,gc Commit | ,gp Push | ,gl Pull
WINDOWS
Ctrl+h/j/k/l Move between panes
WINDOWS / PANES
Ctrl+h/j/k/l Move between Vim windows or tmux panes
,h / ,l Prev / next buffer
,tv Open terminal (vertical)
,th Open terminal (horizontal)
Esc Exit terminal mode
F5 Undo tree | F8 Tag browser
,u Undo tree | ,tt Tag browser
SEARCH
/text Search forward
?text Search backward
,* Replace word under cursor (project-wide)
SEARCH & REPLACE
/text Search forward | ?text backward
// Search for visually selected text
,* Replace word under cursor (file-wide)
CLIPBOARD
,y / ,Y Yank / yank line to system clipboard
,p / ,P Paste from system clipboard (after / before)
```
---

554
README.md
View file

@ -1,41 +1,71 @@
# chopsticks — Vim Configuration
# chopsticks
A native Vim configuration optimized for full-stack engineering workflows.
Vim 8.0+ · Tiered LSP · TTY-aware · Zero icon fonts · 14 languages.
> A batteries-included Vim configuration for full-stack engineering.
> Tiered LSP · 14 languages · TTY-aware · Zero icon fonts · One-command install.
[![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.svg)](https://www.vim.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](LICENSE)
[![Vim 8.0+](https://img.shields.io/badge/Vim-8.0%2B-brightgreen?style=flat-square)](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
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
cd ~/.vim && ./install.sh
```
See [QUICKSTART.md](QUICKSTART.md) for the 5-minute guide.
> **New to Vim?** Read [Step 0 in QUICKSTART.md](QUICKSTART.md#step-0-vim-basics) first —
> 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
- **KISS** — No icon fonts, no unicode glyphs, plain ASCII throughout
- **Tiered LSP** — CoC (full) with vim-lsp fallback; works with or without Node.js
- **TTY-aware** — Automatic detection and optimization for console/SSH environments
- **Engineering-first** — Git workflow, session management, project-local config
- **Batteries included**`install.sh` handles all dependencies automatically
| Principle | What it means |
|-----------|--------------|
| **KISS** | No icon fonts, no Nerd Font glyphs — plain ASCII everywhere |
| **Tiered LSP** | CoC (full) when Node.js is available; vim-lsp (pure VimScript) otherwise |
| **TTY-aware** | Automatically detects SSH/console environments and degrades gracefully |
| **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
| Requirement | Minimum | Notes |
|-------------|---------|-------|
| Vim | 8.0+ | vim9script not required |
| git | any | For cloning and fugitive |
| curl | any | For vim-plug auto-install |
| Node.js | 14.14+ | Optional — enables CoC LSP |
| ripgrep (rg) | any | Optional — enables `:Rg` search |
| fzf | any | Optional — enables `Ctrl+p` fuzzy search |
| ctags | any | Optional — enables `F8` tag browser |
| Tool | Minimum | Role |
|------|---------|------|
| Vim | **8.0+** | Required |
| git | any | Cloning and vim-fugitive |
| curl | any | vim-plug bootstrap |
| Node.js | 14.14+ | Optional — enables CoC LSP (recommended) |
| ripgrep | any | Optional — enables `,rg` / `,rG` project search |
| fzf | any | Optional — enables `Ctrl+p` fuzzy finder |
| ctags | any | Optional — enables `,tt` tag browser |
| tmux | 1.8+ | Optional — enables seamless pane navigation |
All optional tools are installed automatically by `install.sh` when prompted.
---
@ -49,16 +79,20 @@ cd ~/.vim
./install.sh
```
The installer:
1. Checks Vim version and detects OS / package managers
2. Backs up any existing `~/.vimrc` (timestamped)
3. Creates symlinks: `~/.vimrc -> ~/.vim/.vimrc` and `~/.vim/coc-settings.json`
The installer handles everything in sequence:
1. Verifies Vim 8.0+ and detects OS / package manager
2. Backs up any existing `~/.vimrc` with a timestamp
3. Symlinks `~/.vimrc → ~/.vim/.vimrc` and `~/.vim/coc-settings.json`
4. Installs vim-plug and runs `:PlugInstall`
5. Optionally installs system tools, language tools, and CoC extensions
5. Optionally installs system tools (ripgrep, fzf, ctags, shellcheck, hadolint, marksman)
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 / CI environments:
Use `--yes` for non-interactive or CI environments:
```bash
./install.sh --yes
@ -77,21 +111,21 @@ vim +PlugInstall +qall </dev/null
---
## LSP: Tiered Backend System
## LSP: Tiered Backend
Code intelligence is provided by one of two backends, selected automatically:
Code intelligence is provided by one of two backends, chosen automatically at startup:
| Condition | Backend | Features |
|-----------|---------|----------|
| Vim 8.0.1453+ AND Node.js 14.14+ | **CoC** | Full LSP, snippets, extensions ecosystem |
| Condition | Backend | Capabilities |
|-----------|---------|-------------|
| Vim 8.0.1453+ **and** Node.js 14.14+ | **CoC** | Full LSP, snippets, extension ecosystem |
| Vim 8.0+ (no Node.js) | **vim-lsp** | LSP via language server binaries, asyncomplete |
| Any Vim | **ALE** | Linting and auto-fix (always active) |
| Any Vim | **ALE** | Async linting + auto-fix (always active, both backends) |
Both backends expose identical key mappings: `gd`, `K`, `[g`, `]g`, `<leader>rn`, `<leader>ca`.
Both CoC and vim-lsp expose the same key mappings so switching backends is transparent.
### CoC setup (with Node.js)
### With Node.js (CoC)
Install language server extensions from inside Vim:
Install language server extensions from inside Vim — or let `install.sh` do it automatically:
```vim
:CocInstall coc-pyright " Python
@ -99,64 +133,86 @@ Install language server extensions from inside Vim:
:CocInstall coc-go " Go
:CocInstall coc-rust-analyzer " Rust
:CocInstall coc-json coc-yaml " JSON, YAML
:CocInstall coc-html coc-css " HTML, CSS
:CocInstall coc-html coc-css " HTML, CSS/SCSS
:CocInstall coc-sh " Shell
:CocInstall coc-sql " SQL
```
`install.sh` installs all of the above automatically when prompted.
**Markdown LSP** uses `marksman` as an external binary (not a CoC extension):
**Markdown LSP** — `marksman` is configured via `coc-settings.json` (not a CoC
extension — install `marksman` binary via `brew install marksman` or download from
[releases](https://github.com/artempyanykh/marksman/releases)).
### vim-lsp setup (without Node.js)
Install language server binaries for your languages, then run:
```vim
:LspInstallServer " auto-installs the right server for the current filetype
```bash
brew install marksman # macOS
sudo pacman -S marksman # Arch
# or: ./install.sh handles it automatically
```
Supported languages: Python, Go, Rust, TypeScript, JavaScript, Shell, HTML,
CSS/SCSS, JSON, YAML, Markdown, SQL — via `vim-lsp-settings`.
### Without Node.js (vim-lsp)
Open a source file, then run:
```vim
:LspInstallServer " detects filetype and installs the correct server
:LspStatus " check server status
```
Supported: Python, Go, Rust, TypeScript/JavaScript, Shell, HTML, CSS/SCSS, JSON, YAML, Markdown, SQL.
---
## Key Mappings
Leader key: `,` (comma)
**Leader key:** `,` (comma)
Press `,` and wait 500ms for an interactive guide to all bindings (vim-which-key).
Press `,` and wait 500 ms to see 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
| Key | Action |
|-----|--------|
| `Ctrl+p` | Fuzzy file search — git-aware (FZF) |
| `Ctrl+n` | Toggle file tree (NERDTree) |
| `,n` | Reveal current file in NERDTree |
| `Ctrl+p` | Fuzzy file search (FZF — git-aware) |
| `,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) |
| `,gF` | Search git-tracked files (FZF) |
| `,l` | Next buffer |
| `,h` | Previous buffer |
| `,bd` | Close current buffer |
| `,bd` | Close current buffer (preserves window layout) |
| `,,` | Switch to last file |
### Windows and Tabs
### Windows, Tabs, and tmux
| Key | Action |
|-----|--------|
| `Ctrl+h/j/k/l` | Navigate between windows |
| `<Leader>=` | Increase window height |
| `<Leader>-` | Decrease window height |
| `<Leader>+` | Increase window width |
| `<Leader>_` | Decrease window width |
| `Ctrl+h/j/k/l` | Navigate between Vim splits **and** tmux panes |
| `,=` | Increase window height |
| `,-` | Decrease window height |
| `,+` | Increase window width |
| `,_` | Decrease window width |
| `,tn` | New tab |
| `,tc` | Close 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)
@ -165,123 +221,128 @@ Press `,` and wait 500ms for an interactive guide to all bindings (vim-which-key
| `gd` | Go to definition |
| `gy` | Go to type definition |
| `gi` | Go to implementation |
| `gr` | Show references |
| `gr` | Show all references |
| `K` | Hover documentation |
| `[g` | Previous diagnostic |
| `]g` | Next diagnostic |
| `,rn` | Rename symbol |
| `,f` | Format selection |
| `,ca` | Code action (cursor) |
| `,o` | File outline |
| `,F` | Format whole file |
| `,ca` | Code action (cursor position) |
| `,o` | File outline (symbols) |
| `,ws` | Workspace symbols |
| `,cD` | Diagnostics list |
| `,cr` | Resume last CoC list |
| `,qf` | Quick-fix current line (CoC) |
| `,cl` | Run code lens (CoC) |
| `Tab` | Next completion item |
| `Shift+Tab` | Previous completion item |
| `Enter` | Confirm completion |
Text objects (CoC only): `if`/`af` (function), `ic`/`ac` (class)
Text objects (CoC): `if`/`af` (function inner/around), `ic`/`ac` (class inner/around).
### Linting (ALE)
### Linting (ALE — always active)
| Key | Action |
|-----|--------|
| `[e` | Next error/warning |
| `]e` | Previous error/warning |
| `,aD` | Show error details |
| `[e` | Previous error / warning |
| `]e` | Next error / warning |
| `,aD` | Show error detail |
| `,ad` | Full diagnostics list |
Signs: `X` = error, `!` = warning
Signs in the gutter: `X` = error, `!` = warning.
### Git Workflow (fugitive)
### Git (vim-fugitive)
| Key | Action |
|-----|--------|
| `,gs` | Git status |
| `,gs` | Git status (stage with `s`, commit with `cc`) |
| `,gc` | Git commit |
| `,gp` | Git push |
| `,gl` | Git pull |
| `,gd` | Git diff |
| `,gb` | Git blame |
| `,gF` | Search git-tracked files (FZF) |
### Engineering Utilities
### Search and Replace
| 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 |
|-----|--------|
| `,ev` | Edit `~/.vimrc` |
| `,sv` | Reload `~/.vimrc` |
| `,F` | Format entire file |
| `,W` | Strip trailing whitespace |
| `,wa` | Save all open buffers |
| `,wd` | Change CWD to current buffer's dir |
| `,cp` | Copy file path to clipboard |
| `,wd` | Change working directory to current file's location |
| `,cp` | Copy absolute file path to clipboard |
| `,cf` | Copy filename to clipboard |
| `,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 |
| `,qo` / `,qc` | Open / close quickfix list |
---
## Features
### Startup Screen (vim-startify)
### Startup Dashboard
Opens when Vim is launched without a file argument. Shows:
- Session list for current directory
- Recently opened files
- Bookmarks
Running `vim` (no arguments) opens a full-screen Startify dashboard showing recent
files, sessions, and bookmarks. Running `vim .` opens NERDTree on the left with
the dashboard on the right.
Session auto-saves on quit. Auto-loads `Session.vim` if found in the current
directory. Auto-changes to git root on file open.
### Keybinding Guide
**`vim .` layout** — NERDTree on the left, Startify on the right.
Press `,` and pause for 500 ms. A popup (vim-which-key) lists all leader bindings
organized into groups. No need to memorize everything upfront.
### Keybinding Guide (vim-which-key)
### Built-in Cheat Sheet
Press `,` and pause for 500ms. A popup lists all available leader bindings
organized by group. Useful for onboarding and discovering shortcuts.
Press `,?` to open an inline reference covering modes, survival commands, search,
code intelligence, git, and clipboard — without leaving Vim.
### Session Management
```vim
:Obsess " Start tracking session
:Obsess! " Stop tracking
:Obsess " start tracking the current session
:Obsess! " stop tracking
```
Sessions stored in `~/.vim/sessions/` and automatically resumed by vim-prosession
on the next Vim launch in the same directory.
Sessions are stored in `~/.vim/sessions/` and automatically restored by vim-prosession
the next time you open Vim in the same directory.
### Project-Local Config
Place a `.vimrc` in any project root:
Drop a `.vimrc` in any project root to override settings for that project:
```vim
" project/.vimrc
@ -289,157 +350,244 @@ set shiftwidth=2
let g:ale_python_black_options = '--line-length=100'
```
Loaded automatically. Security-restricted via `set secure`.
Loaded automatically via `set exrc`. Restricted to safe options 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
Files over 10 MB automatically disable syntax highlighting and undo history
to prevent Vim from freezing.
Files over 10 MB automatically disable syntax highlighting, undo history, and
linting to prevent Vim from stalling.
### TTY / Console Support
Detected automatically when `$TERM` is `linux` or `screen`. In TTY mode:
- True color and cursorline disabled
- Powerline separators replaced with plain ASCII
- FZF preview windows disabled
- NERDTree auto-open skipped
- Syntax column limit reduced to 120
- Simpler status line
- IndentLine guides disabled
- Syntax column limit reduced to 120 characters
- Simpler built-in status line used instead of airline
---
## Language Support
| Language | Indent | Formatter | Linter | LSP (CoC) |
|----------|--------|-----------|--------|-----------|
| Python | 4sp | black + isort | flake8, pylint | coc-pyright |
| JavaScript | 2sp | prettier | eslint | coc-tsserver |
| TypeScript | 2sp | prettier | eslint, tsserver | coc-tsserver |
| Language | Indent | Formatter | Linter | LSP |
|----------|--------|-----------|--------|-----|
| Python | 4 sp | black, isort | flake8, pylint | coc-pyright |
| JavaScript | 2 sp | prettier | eslint | coc-tsserver |
| TypeScript | 2 sp | prettier | eslint, tsserver | coc-tsserver |
| Go | tab | gofmt, goimports | staticcheck | coc-go |
| Rust | 4sp | rustfmt | cargo | coc-rust-analyzer |
| Shell | 2sp | — | shellcheck | coc-sh |
| YAML | 2sp | prettier | yamllint | coc-yaml |
| HTML | 2sp | prettier | — | coc-html |
| CSS / SCSS | 2sp | prettier | stylelint | coc-css |
| Less | 2sp | prettier | — | — |
| JSON | 2sp | prettier | — | coc-json |
| Markdown | 2sp | prettier | markdownlint | marksman (coc-settings.json) |
| SQL | 4sp | sqlfluff | sqlfluff | — |
| Dockerfile | 2sp | — | hadolint | — |
| Rust | 4 sp | rustfmt | cargo | coc-rust-analyzer |
| Shell | 2 sp | — | shellcheck | coc-sh |
| YAML | 2 sp | prettier | yamllint | coc-yaml |
| HTML | 2 sp | prettier | — | coc-html |
| CSS / SCSS | 2 sp | prettier | stylelint | coc-css |
| Less | 2 sp | prettier | — | — |
| JSON | 2 sp | prettier | — | coc-json |
| Markdown | 2 sp | prettier | markdownlint | marksman |
| SQL | 4 sp | sqlfluff | sqlfluff | — |
| Dockerfile | 2 sp | — | hadolint | — |
`install.sh` installs all linters and formatters automatically.
ALE runs them asynchronously; format-on-save active when using CoC.
`install.sh` installs all formatters and linters automatically.
ALE runs them asynchronously; format-on-save is active for all supported languages.
---
## Plugin List
## Plugins
### Navigation
- **NERDTree**File tree explorer
- **fzf + fzf.vim**Fuzzy finder (file, buffer, tag, ripgrep)
- **NERDTree**file tree explorer
- **fzf + fzf.vim**fuzzy finder for files, buffers, tags, and ripgrep
### Git
- **vim-fugitive**Git commands inside Vim
- **vim-gitgutter**Diff signs in the sign column
- **vim-fugitive**full Git integration inside Vim
- **vim-gitgutter**diff signs in the sign column
### LSP and Completion
- **coc.nvim**Full LSP + completion (requires Node.js 14.14+)
- **vim-lsp**Pure VimScript LSP client (fallback, no Node.js)
- **vim-lsp-settings**Auto-configure language servers for vim-lsp
- **asyncomplete.vim**Async completion (used with vim-lsp)
- **coc.nvim**full LSP + completion via Node.js (recommended)
- **vim-lsp**pure VimScript LSP client (Node.js-free fallback)
- **vim-lsp-settings**auto-configures language servers for vim-lsp
- **asyncomplete.vim**async completion engine (vim-lsp mode)
### Linting
- **ALE**Asynchronous Lint Engine (always active)
- **ALE**asynchronous lint engine, always active regardless of LSP backend
### UI
- **vim-airline**Status and tabline
- **vim-startify**Startup screen with sessions
- **vim-which-key**Keybinding hint popup
- **indentLine**Indent guide lines (non-TTY)
- **undotree**Undo history visualizer
- **tagbar**Code structure sidebar
- **vim-airline**status line and tabline
- **vim-startify**startup dashboard with session management
- **vim-which-key**keybinding hint popup on leader pause
- **indentLine**indent guide lines (non-TTY only)
- **undotree**visual undo branch history
- **tagbar**code structure sidebar via ctags
### Editing
- **vim-surround**Change surrounding quotes, brackets, tags
- **vim-surround**change surrounding quotes, brackets, and tags
- **vim-commentary**`gc` to toggle comments
- **auto-pairs** — Auto-close brackets and quotes
- **vim-easymotion** — Jump anywhere with 2 keystrokes
- **vim-unimpaired** — Bracket shortcut pairs
- **targets.vim** — Extra text objects
- **vim-snippets** — Snippet library (used with CoC/UltiSnips)
- **auto-pairs** — auto-close brackets and quotes
- **vim-easymotion** — jump anywhere on screen with 2 keystrokes (`s`)
- **vim-unimpaired** — bracket shortcut pairs (`[q`/`]q`, etc.)
- **targets.vim** — additional text objects
- **vim-snippets** — snippet library (used with CoC)
- **vim-tmux-navigator** — seamless `Ctrl+h/j/k/l` across Vim and tmux
### Language Packs
- **vim-polyglot**Syntax for 100+ languages
- **vim-go** — Go development tools (formatting + highlighting; LSP handled by coc-go)
- **vim-polyglot**syntax for 100+ languages
- **vim-go** — Go syntax and tooling (LSP handled by coc-go)
### Session
- **vim-obsession**Continuous session saving
- **vim-prosession**Project-level session management
- **vim-obsession**continuous session saving
- **vim-prosession**project-level session management
### Color Schemes
- **gruvbox** (default), **dracula**, **solarized**, **onedark**
---
## Color Scheme
## Customization
Change in `.vimrc` (find the `colorscheme` line):
### Change the color scheme
In `~/.vimrc`, find and update the `colorscheme` line:
```vim
colorscheme dracula " or: gruvbox, solarized, onedark
colorscheme dracula " options: gruvbox, solarized, onedark
```
True color is enabled automatically when the terminal supports it
(`$COLORTERM=truecolor`). Falls back to 256-color, then 16-color (TTY).
True color is enabled automatically when `$COLORTERM=truecolor`. Falls back to
256-color, then 16-color in 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
**Plugins not installed:**
**Plugins not loading**
```vim
:PlugInstall
:PlugUpdate
:PlugInstall " install any missing plugins
:PlugUpdate " update all plugins
```
**CoC not working:**
**CoC not working**
```bash
node --version # must be >= 14.14
node --version # must be 14.14+
```
**Markdown LSP not starting:**
```bash
marksman --version # must be installed separately
brew install marksman # macOS
sudo pacman -S marksman # Arch
# or: ./install.sh (installs automatically)
```
Inside Vim: `:CocInfo` for diagnostics, `:CocInstall <extension>` to add a language server.
**vim-lsp server not starting**
**vim-lsp server not starting:**
```vim
:LspInstallServer " install server for current filetype
:LspStatus " check server status
:LspInstallServer " install the correct server for the current filetype
:LspStatus " check server status
```
**Colors look wrong:**
**Markdown LSP not starting**
`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
export TERM=xterm-256color # add to ~/.bashrc or ~/.zshrc
```
**ALE not finding linters:**
For true color: `export COLORTERM=truecolor`.
**ALE linters not found**
```bash
which flake8 black prettier eslint # confirm tools are on PATH
which flake8 black prettier eslint # verify 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.
---
## References
## Contributing
- [vim-plug](https://github.com/junegunn/vim-plug)
- [coc.nvim](https://github.com/neoclide/coc.nvim)
- [vim-lsp](https://github.com/prabirshrestha/vim-lsp)
- [vim-lsp-settings](https://github.com/mattn/vim-lsp-settings)
- [amix/vimrc](https://github.com/amix/vimrc)
Bug reports and pull requests are welcome. Please follow these guidelines:
### Reporting a bug
1. Search [existing issues](https://github.com/m1ngsama/chopsticks/issues) before opening a new one.
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 Normal file
View file

@ -0,0 +1,67 @@
#!/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,12 +4,13 @@
#
# --yes non-interactive: install all optional components automatically
set -e
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AUTO_YES=0
[[ "${1:-}" == "--yes" ]] && AUTO_YES=1
# ── Colours ───────────────────────────────────────────────────────────────────
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
@ -21,8 +22,12 @@ ok() { echo -e "${GREEN}[OK]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
skip() { echo -e "${CYAN}[--]${NC} $1"; }
fail() { echo -e "${RED}[ERR]${NC} $1"; }
die() { echo -e "${RED}[ERR]${NC} $1" >&2; exit 1; }
die() { echo -e "${RED}[FATAL]${NC} $1" >&2
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}"; }
info() { echo " $1"; }
# Track results for summary
INSTALLED=()
@ -30,52 +35,89 @@ SKIPPED=()
FAILED=()
# Ask yes/no; returns 0 for yes
# Reads from /dev/tty so interactive prompts work even under: curl | bash
ask() {
[[ $AUTO_YES -eq 1 ]] && return 0
read -r -p "$1 [y/N] " reply
if [[ -t 0 ]]; then
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]$ ]]
}
# Try to install a single binary tool via a given command
# Usage: try_install <display_name> <check_cmd> <install_cmd...>
try_install() {
local name="$1"; local check="$2"; shift 2
if command -v "$check" >/dev/null 2>&1; then
ok "$name (already installed: $(command -v "$check"))"
return 0
# ── Error trap ────────────────────────────────────────────────────────────────
on_error() {
local line="${BASH_LINENO[0]}"
echo -e "\n${RED}[FATAL]${NC} Unexpected error at line $line." >&2
echo " To get a full debug log:" >&2
echo " ./install.sh 2>&1 | tee /tmp/chopsticks-install.log" >&2
echo " Report issues: https://github.com/m1ngsama/chopsticks/issues" >&2
}
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
if "$@" >/dev/null 2>&1; then
ok "$name"
INSTALLED+=("$name")
else
fail "$name — install failed (run manually: $*)"
FAILED+=("$name")
return 0
}
# ── Cross-platform package install helper ─────────────────────────────────────
# pkg_install <brew> <apt> <pacman> <dnf> (pass "" to skip that pkg manager)
pkg_install() {
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
}
# ── 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 "----------------------------------------"
# ============================================================================
# Preflight
# 1. OS + Package Manager Detection
# ============================================================================
step "Checking environment"
step "Detecting 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"
if [[ "$OSTYPE" == darwin* ]]; then
OS="macos"
@ -88,89 +130,258 @@ elif [[ -f /etc/arch-release ]]; then
fi
ok "OS: $OS"
# Detect package managers
HAS_BREW=0; command -v brew >/dev/null 2>&1 && HAS_BREW=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_BREW=0; command -v brew >/dev/null 2>&1 && HAS_BREW=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_NODE=0; command -v node >/dev/null 2>&1 && HAS_NODE=1 && ok "Node.js $(node --version) detected"
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_GO=0; command -v go >/dev/null 2>&1 && HAS_GO=1 && ok "Go $(go version | awk '{print $3}') detected"
# Bootstrap pip3 when python3 exists but pip3 is absent (common on Ubuntu minimal images)
# ── 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_PIP=0; command -v pip3 >/dev/null 2>&1 && HAS_PIP=1
if [[ $HAS_PYTHON -eq 0 ]]; then
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
warn "python3 found but pip3 missing — attempting bootstrap"
if python3 -m ensurepip --upgrade >/dev/null 2>&1 || \
(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
pkg_install python3-pip python3-pip python-pip python3-pip >/dev/null 2>&1; then
command -v pip3 >/dev/null 2>&1 && HAS_PIP=1 && ok "pip3 bootstrapped"
else
warn "pip3 bootstrap failed — Python tools will be skipped"
fi
fi
[[ $HAS_PIP -eq 1 ]] && ok "Python/pip3 detected"
[[ $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"
[[ $HAS_GO -eq 0 ]] && warn "Go not found — Go tools will be skipped"
[[ $HAS_PIP -eq 1 ]] && ok "Python/pip3 found"
[[ $HAS_PYTHON -eq 1 && $HAS_PIP -eq 0 ]] && warn "pip3 not available — Python tools will be skipped"
# ── Go ───────────────────────────────────────────────────────────────────────
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/)"
# ============================================================================
# Symlink
# 2. Symlinks
# ============================================================================
step "Setting up ~/.vimrc symlink"
step "Setting up symlinks"
if [ -f "$HOME/.vimrc" ] && [ ! -L "$HOME/.vimrc" ]; then
TS=$(date +%Y%m%d_%H%M%S)
warn "Backing up existing ~/.vimrc to ~/.vimrc.backup.$TS"
warn "Backing up existing ~/.vimrc ~/.vimrc.backup.$TS"
mv "$HOME/.vimrc" "$HOME/.vimrc.backup.$TS"
fi
ln -sf "$SCRIPT_DIR/.vimrc" "$HOME/.vimrc"
ok "~/.vimrc -> $SCRIPT_DIR/.vimrc"
# Verify symlink
[[ -L "$HOME/.vimrc" ]] && ok "~/.vimrc → $SCRIPT_DIR/.vimrc" || die "Failed to create ~/.vimrc symlink"
# CoC settings (marksman markdown LSP + format-on-save config)
mkdir -p "$HOME/.vim"
COC_CFG="$HOME/.vim/coc-settings.json"
if [ -f "$COC_CFG" ] && [ ! -L "$COC_CFG" ]; then
TS=$(date +%Y%m%d_%H%M%S)
warn "Backing up existing coc-settings.json to ~/.vim/coc-settings.json.backup.$TS"
warn "Backing up existing coc-settings.json ~/.vim/coc-settings.json.backup.$TS"
mv "$COC_CFG" "$COC_CFG.backup.$TS"
fi
ln -sf "$SCRIPT_DIR/coc-settings.json" "$COC_CFG"
ok "~/.vim/coc-settings.json -> $SCRIPT_DIR/coc-settings.json"
[[ -L "$COC_CFG" ]] && ok "~/.vim/coc-settings.json → $SCRIPT_DIR/coc-settings.json" || warn "coc-settings.json symlink failed (non-fatal)"
# ============================================================================
# vim-plug + plugins
# 3. vim-plug + Plugins
# ============================================================================
step "Installing vim-plug"
VIM_PLUG="$HOME/.vim/autoload/plug.vim"
if [ ! -f "$VIM_PLUG" ]; then
curl -fLo "$VIM_PLUG" --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
ok "vim-plug installed"
mkdir -p "$HOME/.vim/autoload"
if safe_download "https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim" "$VIM_PLUG"; then
ok "vim-plug downloaded"
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
ok "vim-plug already present"
fi
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
vim +PlugInstall +qall </dev/null
ok "Plugins installed"
if ! vim +PlugInstall +qall </dev/null; then
warn "vim +PlugInstall exited non-zero — plugins may be partially installed"
warn "Run :PlugInstall manually inside Vim if something looks wrong"
else
ok "Plugins installed"
fi
# ============================================================================
# System tools (ripgrep, fzf, ctags, shellcheck, marksman)
# 4. System Tools
# ============================================================================
step "System tools"
if ask "Install system tools (ripgrep, fzf, ctags, shellcheck, hadolint, marksman)?"; then
if [[ $OS == "macos" && $HAS_BREW -eq 0 ]]; 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() {
local name="$1"; local check="$2"; shift 2
local name="$1" check="$2"; shift 2
if command -v "$check" >/dev/null 2>&1; then
ok "$name (already installed)"
return
@ -180,92 +391,110 @@ if ask "Install system tools (ripgrep, fzf, ctags, shellcheck, hadolint, marksma
if eval "$cmd" >/dev/null 2>&1; then installed=1; break; fi
done
if [[ $installed -eq 1 ]]; then
ok "$name"
INSTALLED+=("$name")
ok "$name"; INSTALLED+=("$name")
else
fail "$name — could not install automatically"
fail "$name — could not install automatically (install manually)"
FAILED+=("$name")
fi
}
if [[ $OS == macos ]]; then
command -v brew >/dev/null 2>&1 || { warn "brew not found — skipping system tools"; }
install_sys "ripgrep" rg "brew install ripgrep"
install_sys "fzf" fzf "brew install fzf"
install_sys "universal-ctags" ctags "brew install universal-ctags"
install_sys "shellcheck" shellcheck "brew install shellcheck"
install_sys "hadolint" hadolint "brew install hadolint"
install_sys "marksman" marksman "brew install marksman"
if [[ $OS == "macos" ]]; then
install_sys "ripgrep" rg "brew install ripgrep"
install_sys "fzf" fzf "brew install fzf"
install_sys "universal-ctags" ctags "brew install universal-ctags"
install_sys "shellcheck" shellcheck "brew install shellcheck"
install_sys "hadolint" hadolint "brew install hadolint"
install_sys "marksman" marksman "brew install marksman"
elif [[ $HAS_APT -eq 1 ]]; then
sudo apt-get update -qq
install_sys "ripgrep" rg "sudo apt-get install -y ripgrep"
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
if [[ $HAS_SUDO -eq 0 ]]; then
warn "No sudo — skipping apt system tools"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman")
else
ok "hadolint (already installed)"
fi
# marksman: no apt package, download binary
if ! command -v marksman >/dev/null 2>&1; then
ARCH=$(uname -m)
[[ "$ARCH" == "x86_64" ]] && MARCH="x64" || MARCH="arm64"
MVER=$(curl -s https://api.github.com/repos/artempyanykh/marksman/releases/latest \
| grep '"tag_name"' | cut -d'"' -f4)
if [[ -n "$MVER" ]]; then
curl -fsSL "https://github.com/artempyanykh/marksman/releases/download/${MVER}/marksman-linux-${MARCH}" \
-o /tmp/marksman && chmod +x /tmp/marksman && sudo mv /tmp/marksman /usr/local/bin/marksman
ok "marksman"
INSTALLED+=("marksman")
sudo apt-get update -qq
install_sys "ripgrep" rg "sudo apt-get install -y ripgrep"
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 from GitHub releases
if command -v hadolint >/dev/null 2>&1; then
ok "hadolint (already installed)"
else
warn "marksman: could not detect latest release, install manually"
SKIPPED+=("marksman")
HARCH=$(arch_github)
HVER=$(curl -fsSL https://api.github.com/repos/hadolint/hadolint/releases/latest \
| grep '"tag_name"' | cut -d'"' -f4 2>/dev/null) || HVER=""
if [[ -n "$HVER" ]] && safe_download \
"https://github.com/hadolint/hadolint/releases/download/${HVER}/hadolint-Linux-${HARCH}" \
/tmp/chopsticks-hadolint; then
chmod +x /tmp/chopsticks-hadolint && sudo mv /tmp/chopsticks-hadolint /usr/local/bin/hadolint
ok "hadolint"; INSTALLED+=("hadolint")
else
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
else
ok "marksman (already installed)"
fi
elif [[ $HAS_PACMAN -eq 1 ]]; then
install_sys "ripgrep" rg "sudo pacman -S --noconfirm ripgrep"
install_sys "fzf" fzf "sudo pacman -S --noconfirm fzf"
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"
if [[ $HAS_SUDO -eq 0 ]]; then
warn "No sudo — skipping pacman system tools"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman")
else
install_sys "ripgrep" rg "sudo pacman -S --noconfirm ripgrep"
install_sys "fzf" fzf "sudo pacman -S --noconfirm fzf"
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
install_sys "ripgrep" rg "sudo dnf install -y ripgrep"
install_sys "fzf" fzf "sudo dnf install -y fzf"
install_sys "shellcheck" shellcheck "sudo dnf install -y ShellCheck"
skip "universal-ctags — install manually: sudo dnf install ctags"
SKIPPED+=("ctags")
skip "hadolint — install manually from https://github.com/hadolint/hadolint/releases"
SKIPPED+=("hadolint")
skip "marksman — install manually from https://github.com/artempyanykh/marksman/releases"
SKIPPED+=("marksman")
if [[ $HAS_SUDO -eq 0 ]]; then
warn "No sudo — skipping dnf system tools"
SKIPPED+=("ripgrep" "fzf" "shellcheck" "ctags" "hadolint" "marksman")
else
install_sys "ripgrep" rg "sudo dnf install -y ripgrep"
install_sys "fzf" fzf "sudo dnf install -y fzf"
install_sys "shellcheck" shellcheck "sudo dnf install -y ShellCheck"
skip "universal-ctags — install manually: sudo dnf install ctags"
SKIPPED+=("ctags")
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
warn "Unknown Linux distro — skipping system tools (install manually)"
warn "Unknown distro — skipping system tools (install manually)"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman")
fi
else
skip "system tools"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman")
fi
# ============================================================================
# npm tools (prettier, markdownlint-cli, stylelint, eslint, typescript)
# 5. npm tools
# ============================================================================
step "npm tools (formatters + linters)"
@ -275,15 +504,12 @@ if [[ $HAS_NODE -eq 1 ]]; then
npm_install() {
local pkg="$1"; local check="${2:-$1}"
if command -v "$check" >/dev/null 2>&1; then
ok "$pkg (already installed)"
return
ok "$pkg (already installed)"; return
fi
if npm install -g "$pkg" >/dev/null 2>&1; then
ok "$pkg"
INSTALLED+=("$pkg")
ok "$pkg"; INSTALLED+=("$pkg")
else
fail "$pkg"
FAILED+=("$pkg")
fail "$pkg"; FAILED+=("$pkg")
fi
}
npm_install prettier
@ -302,7 +528,7 @@ else
fi
# ============================================================================
# pip tools (black, isort, flake8, pylint, sqlfluff)
# 6. Python tools
# ============================================================================
step "Python tools (formatters + linters)"
@ -312,16 +538,13 @@ if [[ $HAS_PIP -eq 1 ]]; then
pip_install() {
local pkg="$1"; local check="${2:-$1}"
if command -v "$check" >/dev/null 2>&1; then
ok "$pkg (already installed)"
return
ok "$pkg (already installed)"; return
fi
if pip3 install --quiet "$pkg" 2>/dev/null || \
pip3 install --quiet --break-system-packages "$pkg" 2>/dev/null; then
ok "$pkg"
INSTALLED+=("$pkg")
ok "$pkg"; INSTALLED+=("$pkg")
else
fail "$pkg"
FAILED+=("$pkg")
fail "$pkg"; FAILED+=("$pkg")
fi
}
pip_install black
@ -340,65 +563,94 @@ else
fi
# ============================================================================
# Go tools (gopls, goimports, staticcheck)
# 7. Go tools
# ============================================================================
step "Go tools"
if [[ $HAS_GO -eq 1 ]]; 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"
export PATH="$PATH:$GOBIN"
go_install() {
local name="$1"; local pkg="$2"; local check="$3"
local name="$1" pkg="$2" check="$3"
if command -v "$check" >/dev/null 2>&1 || [[ -x "$GOBIN/$check" ]]; then
ok "$name (already installed)"
return
ok "$name (already installed)"; return
fi
if go install "$pkg" >/dev/null 2>&1; then
ok "$name"
INSTALLED+=("$name")
ok "$name"; INSTALLED+=("$name")
else
fail "$name"
FAILED+=("$name")
fail "$name"; FAILED+=("$name")
fi
}
go_install gopls "golang.org/x/tools/gopls@latest" gopls
go_install goimports "golang.org/x/tools/cmd/goimports@latest" goimports
go_install staticcheck "honnef.co/go/tools/cmd/staticcheck@latest" staticcheck
go_install gopls "golang.org/x/tools/gopls@latest" gopls
go_install goimports "golang.org/x/tools/cmd/goimports@latest" goimports
go_install staticcheck "honnef.co/go/tools/cmd/staticcheck@latest" staticcheck
# Remind user to add GOPATH/bin to their shell profile
if ! echo "$PATH" | grep -q "$GOBIN"; then
echo "$PATH" | grep -q "$GOBIN" || \
warn "Add Go binaries to PATH: export PATH=\"\$PATH:$GOBIN\""
fi
else
skip "Go tools"
SKIPPED+=("gopls" "goimports" "staticcheck")
fi
else
skip "Go tools (go not installed)"
skip "Go tools (go not installed — see https://go.dev/dl/)"
SKIPPED+=("gopls" "goimports" "staticcheck")
fi
# ============================================================================
# CoC language server extensions
# 8. tmux: vim-tmux-navigator integration
# ============================================================================
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"
if [[ $HAS_NODE -eq 1 ]]; 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
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"
else
skip "CoC language servers"
echo " Install later with :CocInstall <name> inside Vim"
info "Install later with :CocInstall <name> inside Vim"
fi
else
warn "Node.js not found — using vim-lsp fallback (run :LspInstallServer inside Vim)"
warn "Node.js not found — using vim-lsp fallback (run :LspInstallServer inside Vim for each language)"
fi
# ============================================================================
@ -421,8 +673,26 @@ fi
if [[ ${#FAILED[@]} -gt 0 ]]; then
echo -e "\n${RED}Failed (install manually):${NC}"
for t in "${FAILED[@]}"; do echo " ! $t"; done
echo ""
echo " To debug failures: ./install.sh 2>&1 | tee /tmp/chopsticks-install.log"
fi
echo ""
echo "Run 'vim' and press ',' then wait 500ms for keybinding hints."
echo -e "${BOLD}---------------------------------------${NC}"
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 ""