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
set wildmode=list:longest
" Case-insensitive filename completion in wildmenu (spf13, YADR)
set wildignorecase
" Files and directories to exclude from wildmenu and :find
" There are certain files that we would never want to edit with Vim
" Wildmenu will ignore files with these extensions
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
@ -125,11 +119,6 @@ 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
@ -143,9 +132,6 @@ 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
@ -221,7 +207,6 @@ 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
@ -302,14 +287,6 @@ 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
@ -346,8 +323,11 @@ nmap <leader>x :x<cr>
" Disable highlight when <leader><cr> is pressed
map <silent> <leader><cr> :noh<cr>
" 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)
" 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
" Close the current buffer (Bclose preserves window layout)
map <leader>bd :Bclose<cr>
@ -379,30 +359,17 @@ augroup END
" Opens a new tab with the current buffer's path
map <leader>te :tabedit <C-r>=expand("%:p:h")<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>
" Switch CWD to the directory of the open buffer
map <leader>wd :cd %:p:h<cr>:pwd<cr>
" Remap VIM 0 to first non-blank character
map 0 ^
" 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)
" Move a line of text using ALT+[jk] or Command+[jk] on mac
nmap <M-j> mz:m+<cr>`z
nmap <M-k> mz:m-2<cr>`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
vmap <M-j> :m'>+<cr>`<my`>mzgv`yo`z
vmap <M-k> :m'<-2<cr>`>my`<mzgv`yo`z
" Pressing ,ss will toggle and untoggle spell checking
map <leader>ss :setlocal spell!<cr>
@ -422,9 +389,6 @@ 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
@ -434,9 +398,6 @@ 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
@ -445,25 +406,15 @@ 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/paste (conditional: requires clipboard provider)
" System clipboard yank (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)
@ -534,7 +485,6 @@ 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>
@ -633,9 +583,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 = 'normal'
let g:ale_lint_on_insert_leave = 1
let g:ale_lint_on_enter = 1
let g:ale_lint_on_text_changed = 'never'
let g:ale_lint_on_insert_leave = 0
let g:ale_lint_on_enter = 0
" --- 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
@ -652,18 +602,16 @@ 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 convention: [ = prev, ] = next)
nmap <silent> [e :ALEPrevious<cr>
nmap <silent> ]e :ALENext<cr>
" Navigate between errors: [e/]e (unimpaired style), <leader>aD for detail
nmap <silent> [e :ALENext<cr>
nmap <silent> ]e :ALEPrevious<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
@ -901,19 +849,6 @@ 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.)
@ -936,16 +871,13 @@ augroup ChopstickFiletype
autocmd BufNewFile,BufRead *.tsx setlocal filetype=typescript.tsx
" 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
autocmd FileType javascript,typescript setlocal expandtab shiftwidth=2 tabstop=2 textwidth=100 colorcolumn=+1
" JavaScript specific settings
autocmd FileType javascript,typescript setlocal expandtab shiftwidth=2 tabstop=2
" 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
" Go specific settings
autocmd FileType go setlocal noexpandtab shiftwidth=4 tabstop=4
" HTML/CSS specific settings
autocmd FileType html,css setlocal expandtab shiftwidth=2 tabstop=2
@ -953,11 +885,11 @@ augroup ChopstickFiletype
" YAML specific settings
autocmd FileType yaml setlocal expandtab shiftwidth=2 tabstop=2
" Markdown specific settings (no line-length limit; wrap at window edge)
autocmd FileType markdown setlocal wrap linebreak spell textwidth=0 colorcolumn=0
" Markdown specific settings
autocmd FileType markdown setlocal wrap linebreak spell tw=0
" 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)
autocmd FileType make setlocal noexpandtab shiftwidth=8 tabstop=8
@ -997,6 +929,9 @@ function! ToggleNumber()
endif
endfunc
" Toggle paste mode
map <leader>pp :setlocal paste!<cr>
" ============================================================================
" => Performance Optimization
" ============================================================================
@ -1005,9 +940,6 @@ endfunc
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
@ -1035,10 +967,6 @@ 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
" ============================================================================
@ -1067,8 +995,8 @@ nnoremap <leader>so :source %<CR>
" Edit vimrc quickly
nnoremap <leader>ev :edit $MYVIMRC<CR>
" Reload vimrc with confirmation echo
nnoremap <leader>sv :source $MYVIMRC<CR>:echo "vimrc reloaded"<CR>
" Reload vimrc
nnoremap <leader>sv :source $MYVIMRC<CR>
" Search and replace word under cursor
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
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
" ============================================================================
@ -1233,8 +1082,7 @@ function! LargeFileSettings()
setlocal eventignore+=FileType
setlocal noswapfile
setlocal syntax=OFF
let b:ale_enabled = 0
echo "Large file (>10MB): syntax, undo, and linting disabled for performance."
echo "Large file (>10MB): syntax and undo disabled for performance."
endfunction
" ============================================================================
@ -1300,10 +1148,6 @@ 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'] = {
@ -1325,9 +1169,9 @@ if exists('g:plugs["vim-which-key"]')
\ 'f': 'copy-filename',
\ }
" [e]dit / [e]xplore group
" [e]dit group
let g:which_key_map['e'] = {
\ 'name': '+edit/explore',
\ 'name': '+edit',
\ 'v': 'edit-vimrc',
\ }
@ -1356,7 +1200,6 @@ if exists('g:plugs["vim-which-key"]')
\ 'name': '+search/refactor',
\ 'n': 'rename',
\ 'g': 'ripgrep',
\ 'G': 'ripgrep-word-under-cursor',
\ 't': 'tags-search',
\ }
@ -1375,7 +1218,7 @@ if exists('g:plugs["vim-which-key"]')
" [t]ab / [t]erminal group
let g:which_key_map['t'] = {
\ 'name': '+tab/terminal/tagbar',
\ 'name': '+tab/terminal',
\ 'n': 'new-tab',
\ 'o': 'tab-only',
\ 'c': 'close-tab',
@ -1384,7 +1227,6 @@ 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)
@ -1392,10 +1234,8 @@ 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,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
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.
> **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
@ -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)
,? Open cheat sheet inside Vim
Esc / jk Exit insert mode → Normal (memorize this)
Ctrl+s Save (works in normal and insert mode)
, (pause 500ms) Show all shortcuts
Ctrl+p Fuzzy find file
Ctrl+n Toggle file tree
gd Go to definition
K Show documentation
[g / ]g Prev / next LSP diagnostic
[g / ]g Prev / next diagnostic
,rn Rename symbol
,rG Search word under cursor (ripgrep)
,rg Search project contents
,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
```
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)
,rG Ripgrep word under cursor
,w Save | ,q Quit | ,x Save+quit
,wa Save all buffers
,, Switch to last file
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
CODE
gd Go to definition
K Show documentation
[g / ]g Prev/next LSP diagnostic
[e / ]e Prev/next ALE error
[g / ]g Prev/next diagnostic
,rn Rename symbol
,ca Code action / auto-fix
,f Format selection | ,F Format whole file
,ca Code action
,f Format selection
,F Format whole file
GIT
,gs 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
WINDOWS
Ctrl+h/j/k/l Move between panes
,tv Open terminal (vertical)
,th Open terminal (horizontal)
Esc Exit terminal mode
,u Undo tree | ,tt Tag browser
F5 Undo tree | F8 Tag browser
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)
SEARCH
/text Search forward
?text Search backward
,* Replace word under cursor (project-wide)
```
---

546
README.md
View file

@ -1,71 +1,41 @@
# chopsticks
# chopsticks — Vim Configuration
> A batteries-included Vim configuration for full-stack engineering.
> Tiered LSP · 14 languages · TTY-aware · Zero icon fonts · One-command install.
A native Vim configuration optimized for full-stack engineering workflows.
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)
[![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)
[![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/)
```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
cd ~/.vim && ./install.sh
```
> **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)
See [QUICKSTART.md](QUICKSTART.md) for the 5-minute guide.
---
## Design Principles
| 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 |
- **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
---
## Requirements
| 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.
| 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 |
---
@ -79,20 +49,16 @@ cd ~/.vim
./install.sh
```
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`
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`
4. Installs vim-plug and runs `:PlugInstall`
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
5. Optionally installs system tools, language tools, and CoC extensions
**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
./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 |
|-----------|---------|-------------|
| Vim 8.0.1453+ **and** Node.js 14.14+ | **CoC** | Full LSP, snippets, extension ecosystem |
| Condition | Backend | Features |
|-----------|---------|----------|
| 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 |
| 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
: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-rust-analyzer " Rust
: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-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
brew install marksman # macOS
sudo pacman -S marksman # Arch
# or: ./install.sh handles it automatically
```
**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)).
### 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
:LspInstallServer " detects filetype and installs the correct server
:LspStatus " check server status
:LspInstallServer " auto-installs the right server for the current filetype
```
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
**Leader key:** `,` (comma)
Leader key: `,` (comma)
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.
Press `,` and wait 500ms for an interactive guide to all bindings (vim-which-key).
### 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` | Ripgrep for word under cursor (literal match) |
| `,rg` | Project-wide search (ripgrep+FZF) |
| `,rt` | Search tags (FZF) |
| `,gF` | Search git-tracked files (FZF) |
| `,l` | Next buffer |
| `,h` | Previous buffer |
| `,bd` | Close current buffer (preserves window layout) |
| `,bd` | Close current buffer |
| `,,` | Switch to last file |
### Windows, Tabs, and tmux
### Windows and Tabs
| Key | Action |
|-----|--------|
| `Ctrl+h/j/k/l` | Navigate between Vim splits **and** tmux panes |
| `,=` | Increase window height |
| `,-` | Decrease window height |
| `,+` | Increase window width |
| `,_` | Decrease window width |
| `Ctrl+h/j/k/l` | Navigate between windows |
| `<Leader>=` | Increase window height |
| `<Leader>-` | Decrease window height |
| `<Leader>+` | Increase window width |
| `<Leader>_` | 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)
@ -221,128 +165,123 @@ Press `,?` to open the built-in cheat sheet at any time.
| `gd` | Go to definition |
| `gy` | Go to type definition |
| `gi` | Go to implementation |
| `gr` | Show all references |
| `gr` | Show references |
| `K` | Hover documentation |
| `[g` | Previous diagnostic |
| `]g` | Next diagnostic |
| `,rn` | Rename symbol |
| `,f` | Format selection |
| `,F` | Format whole file |
| `,ca` | Code action (cursor position) |
| `,o` | File outline (symbols) |
| `,ca` | Code action (cursor) |
| `,o` | File outline |
| `,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): `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 |
|-----|--------|
| `[e` | Previous error / warning |
| `]e` | Next error / warning |
| `,aD` | Show error detail |
| `,ad` | Full diagnostics list |
| `[e` | Next error/warning |
| `]e` | Previous error/warning |
| `,aD` | Show error details |
Signs in the gutter: `X` = error, `!` = warning.
Signs: `X` = error, `!` = warning
### Git (vim-fugitive)
### Git Workflow (fugitive)
| Key | Action |
|-----|--------|
| `,gs` | Git status (stage with `s`, commit with `cc`) |
| `,gs` | Git status |
| `,gc` | Git commit |
| `,gp` | Git push |
| `,gl` | Git pull |
| `,gd` | Git diff |
| `,gb` | Git blame |
| `,gF` | Search git-tracked files (FZF) |
### 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
### Engineering Utilities
| Key | Action |
|-----|--------|
| `,ev` | Edit `~/.vimrc` |
| `,sv` | Reload `~/.vimrc` |
| `,F` | Format entire file |
| `,W` | Strip trailing whitespace |
| `,wa` | Save all open buffers |
| `,wd` | Change working directory to current file's location |
| `,cp` | Copy absolute file path to clipboard |
| `,wd` | Change CWD to current buffer's dir |
| `,cp` | Copy file path 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
### Startup Dashboard
### Startup Screen (vim-startify)
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.
Opens when Vim is launched without a file argument. Shows:
- Session list for current directory
- 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
organized into groups. No need to memorize everything upfront.
**`vim .` layout** — NERDTree on the left, Startify on the right.
### Built-in Cheat Sheet
### Keybinding Guide (vim-which-key)
Press `,?` to open an inline reference covering modes, survival commands, search,
code intelligence, git, and clipboard — without leaving Vim.
Press `,` and pause for 500ms. A popup lists all available leader bindings
organized by group. Useful for onboarding and discovering shortcuts.
### Session Management
```vim
:Obsess " start tracking the current session
:Obsess! " stop tracking
:Obsess " Start tracking session
:Obsess! " Stop tracking
```
Sessions are stored in `~/.vim/sessions/` and automatically restored by vim-prosession
the next time you open Vim in the same directory.
Sessions stored in `~/.vim/sessions/` and automatically resumed by vim-prosession
on the next Vim launch in the same directory.
### Project-Local Config
Drop a `.vimrc` in any project root to override settings for that project:
Place a `.vimrc` in any project root:
```vim
" project/.vimrc
@ -350,244 +289,157 @@ set shiftwidth=2
let g:ale_python_black_options = '--line-length=100'
```
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`.
Loaded automatically. Security-restricted via `set secure`.
### Large File Handling
Files over 10 MB automatically disable syntax highlighting, undo history, and
linting to prevent Vim from stalling.
Files over 10 MB automatically disable syntax highlighting and undo history
to prevent Vim from freezing.
### 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
- IndentLine guides disabled
- Syntax column limit reduced to 120 characters
- Simpler built-in status line used instead of airline
- NERDTree auto-open skipped
- Syntax column limit reduced to 120
- Simpler status line
---
## Language Support
| 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 |
| 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 |
| Go | tab | gofmt, goimports | staticcheck | coc-go |
| 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 | — |
| 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 | — |
`install.sh` installs all formatters and linters automatically.
ALE runs them asynchronously; format-on-save is active for all supported languages.
`install.sh` installs all linters and formatters automatically.
ALE runs them asynchronously; format-on-save active when using CoC.
---
## Plugins
## Plugin List
### Navigation
- **NERDTree**file tree explorer
- **fzf + fzf.vim**fuzzy finder for files, buffers, tags, and ripgrep
- **NERDTree**File tree explorer
- **fzf + fzf.vim**Fuzzy finder (file, buffer, tag, ripgrep)
### Git
- **vim-fugitive**full Git integration inside Vim
- **vim-gitgutter**diff signs in the sign column
- **vim-fugitive**Git commands inside Vim
- **vim-gitgutter**Diff signs in the sign column
### LSP and Completion
- **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)
- **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)
### Linting
- **ALE**asynchronous lint engine, always active regardless of LSP backend
- **ALE**Asynchronous Lint Engine (always active)
### UI
- **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
- **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
### Editing
- **vim-surround**change surrounding quotes, brackets, and tags
- **vim-surround**Change surrounding quotes, brackets, tags
- **vim-commentary**`gc` to toggle comments
- **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
- **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)
### Language Packs
- **vim-polyglot**syntax for 100+ languages
- **vim-go** — Go syntax and tooling (LSP handled by coc-go)
- **vim-polyglot**Syntax for 100+ languages
- **vim-go** — Go development tools (formatting + highlighting; 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**
---
## Customization
## Color Scheme
### Change the color scheme
In `~/.vimrc`, find and update the `colorscheme` line:
Change in `.vimrc` (find the `colorscheme` line):
```vim
colorscheme dracula " options: gruvbox, solarized, onedark
colorscheme dracula " or: gruvbox, solarized, onedark
```
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`.
True color is enabled automatically when the terminal supports it
(`$COLORTERM=truecolor`). Falls back to 256-color, then 16-color (TTY).
---
## Troubleshooting
**Plugins not loading**
**Plugins not installed:**
```vim
:PlugInstall " install any missing plugins
:PlugUpdate " update all plugins
:PlugInstall
:PlugUpdate
```
**CoC not working**
**CoC not working:**
```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.
**vim-lsp server not starting**
**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)
```
**vim-lsp server not starting:**
```vim
:LspInstallServer " install the correct server for the current filetype
:LspStatus " check server status
:LspInstallServer " install server for current filetype
:LspStatus " check server status
```
**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**
**Colors look wrong:**
```bash
export TERM=xterm-256color # add to ~/.bashrc or ~/.zshrc
```
For true color: `export COLORTERM=truecolor`.
**ALE linters not found**
**ALE not finding linters:**
```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:
### 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.
- [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)
---

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
set -eo pipefail
set -e
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'
@ -22,12 +21,8 @@ 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}[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; }
die() { echo -e "${RED}[ERR]${NC} $1" >&2; exit 1; }
step() { echo -e "\n${BOLD}==> $1${NC}"; }
info() { echo " $1"; }
# Track results for summary
INSTALLED=()
@ -35,89 +30,52 @@ 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
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
read -r -p "$1 [y/N] " reply
[[ "$reply" =~ ^[Yy]$ ]]
}
# ── 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
# 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
fi
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
if "$@" >/dev/null 2>&1; then
ok "$name"
INSTALLED+=("$name")
else
fail "$name — install failed (run manually: $*)"
FAILED+=("$name")
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 "----------------------------------------"
# ============================================================================
# 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"
if [[ "$OSTYPE" == darwin* ]]; then
OS="macos"
@ -130,258 +88,89 @@ elif [[ -f /etc/arch-release ]]; then
fi
ok "OS: $OS"
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
# 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_PACMAN=0; command -v pacman >/dev/null 2>&1 && HAS_PACMAN=1
# ── 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_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_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
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)
# Bootstrap pip3 when python3 exists but pip3 is absent (common on Ubuntu minimal images)
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 || \
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"
else
warn "pip3 bootstrap failed — Python tools will be skipped"
fi
fi
[[ $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/)"
[[ $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"
# ============================================================================
# 2. Symlinks
# Symlink
# ============================================================================
step "Setting up symlinks"
step "Setting up ~/.vimrc symlink"
if [ -f "$HOME/.vimrc" ] && [ ! -L "$HOME/.vimrc" ]; then
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"
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"
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 ~/.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"
fi
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"
VIM_PLUG="$HOME/.vim/autoload/plug.vim"
if [ ! -f "$VIM_PLUG" ]; then
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"
curl -fLo "$VIM_PLUG" --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
ok "vim-plug installed"
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
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
vim +PlugInstall +qall </dev/null
ok "Plugins installed"
# ============================================================================
# 4. System Tools
# System tools (ripgrep, fzf, ctags, shellcheck, marksman)
# ============================================================================
step "System tools"
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
if ask "Install system tools (ripgrep, fzf, ctags, shellcheck, hadolint, marksman)?"; then
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
ok "$name (already installed)"
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
done
if [[ $installed -eq 1 ]]; then
ok "$name"; INSTALLED+=("$name")
ok "$name"
INSTALLED+=("$name")
else
fail "$name — could not install automatically (install manually)"
fail "$name — could not install automatically"
FAILED+=("$name")
fi
}
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"
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"
elif [[ $HAS_APT -eq 1 ]]; then
if [[ $HAS_SUDO -eq 0 ]]; then
warn "No sudo — skipping apt system tools"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "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
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
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
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
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")
else
warn "marksman: could not detect latest release, install manually"
SKIPPED+=("marksman")
fi
else
ok "marksman (already installed)"
fi
elif [[ $HAS_PACMAN -eq 1 ]]; then
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
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"
elif [[ $HAS_DNF -eq 1 ]]; then
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
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")
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")
fi
else
skip "system tools"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman")
fi
# ============================================================================
# 5. npm tools
# npm tools (prettier, markdownlint-cli, stylelint, eslint, typescript)
# ============================================================================
step "npm tools (formatters + linters)"
@ -504,12 +275,15 @@ 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
@ -528,7 +302,7 @@ else
fi
# ============================================================================
# 6. Python tools
# pip tools (black, isort, flake8, pylint, sqlfluff)
# ============================================================================
step "Python tools (formatters + linters)"
@ -538,13 +312,16 @@ 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
@ -563,94 +340,65 @@ else
fi
# ============================================================================
# 7. Go tools
# Go tools (gopls, goimports, staticcheck)
# ============================================================================
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" 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
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
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\""
fi
else
skip "Go tools"
SKIPPED+=("gopls" "goimports" "staticcheck")
fi
else
skip "Go tools (go not installed — see https://go.dev/dl/)"
skip "Go tools (go not installed)"
SKIPPED+=("gopls" "goimports" "staticcheck")
fi
# ============================================================================
# 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
# 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"
info "Install later with :CocInstall <name> inside Vim"
echo " Install later with :CocInstall <name> inside Vim"
fi
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
# ============================================================================
@ -673,26 +421,8 @@ 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 -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 "Run 'vim' and press ',' then wait 500ms for keybinding hints."
echo ""