Compare commits

...

10 commits

Author SHA1 Message Date
01c67d841b fix: audit fixes — shellcheck, TTY signcolumn, matchdelete race, docs accuracy
Some checks failed
test / startup (macos-latest) (push) Has been cancelled
test / startup (ubuntu-latest) (push) Has been cancelled
test / shellcheck (push) Has been cancelled
- install.sh: ls -A → find (shellcheck SC2012)
- navigation.vim: GFiles -nargs=? to support :GFiles?
- lsp.vim: signcolumn=yes gated behind !g:is_tty
- editing.vim: matchdelete in timer wrapped with silent!
- README: gofmt → goimports, plugin count 24–25
- CHANGELOG: annotate removed features (ChopsticksLearn, vim . layout)
2026-04-22 18:31:08 +08:00
196e88c2c8 docs: demo GIF with live API curl — 40s, 5 clean scenes
Scenes: open → fuzzy find → ripgrep → curl running API (JSON) → cheat sheet.
Server runs in background, curl shows formatted /users response.
Fixed: port conflict, buffer state reset before cheat sheet.
2026-04-22 18:08:05 +08:00
4965b65041 docs: slow down demo GIF — 24s → 35s, 50ms typing, longer scene holds 2026-04-22 17:50:15 +08:00
2c7d190314 docs: re-record demo GIF — clean narrative, zero lint warnings
5-scene story: open → fuzzy find → ripgrep → run file → cheat sheet.
Demo project rewritten with functional dispatch (no class/stdlib method
name lint issues). All frames clean — no ALE warnings, no git markers.
2026-04-22 17:45:52 +08:00
a42c980d3e perf: remove ttyfast, add grepprg/diffopt, DRY FZF commands, conditional tmux-navigator
- Remove deprecated `set ttyfast` (no-op since Vim 8)
- Add grepprg=rg for :grep → quickfix integration
- Add diffopt with histogram algorithm
- Consolidate Rg/RgWord/GFiles (24 lines → 10, lazy preview)
- Load vim-tmux-navigator only inside tmux, Ctrl+hjkl fallback outside
2026-04-22 17:06:33 +08:00
f6631dfdf1 docs: re-record demo GIF — deterministic FZF handling, all 6 scenes clean 2026-04-22 16:51:52 +08:00
56c3917682 perf+feat: fix logiPat guard, skip 2 more built-ins, add git/format bindings
Performance:
- Fix g:loaded_logipat → g:loaded_logiPat (0.478ms → 0.017ms)
- Skip openPlugin + manpager (12 built-in plugins disabled total)

Features:
- ,af toggle format-on-save
- ,gL git log --oneline --graph
- ,gC FZF commits search, ,gB buffer commits
2026-04-22 16:30:28 +08:00
996b428809 docs: re-record demo GIF — clean viminfo, fix ALE warnings 2026-04-22 16:18:21 +08:00
ba39fca13b docs: re-record demo GIF — 7-scene feature showcase 2026-04-22 15:54:33 +08:00
ecf0a1b260 docs: rewrite demo tape — 7-scene "what can I do" narrative
Scenes: vim . dashboard, fuzzy find, ripgrep, run file, sidebar
toggle, git status, cheat sheet. Optional LSP section commented out.
2026-04-22 15:53:30 +08:00
14 changed files with 124 additions and 74 deletions

BIN
.github/demo.gif vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 KiB

After

Width:  |  Height:  |  Size: 990 KiB

72
.github/demo.tape vendored
View file

@ -1,47 +1,63 @@
# chopsticks demo — "what can I do with this?"
# Rule: NEVER use Escape to close FZF — always Enter (deterministic).
# Pacing: hold each scene long enough for the viewer to read.
Output .github/demo.gif Output .github/demo.gif
Set Shell bash Set Shell bash
Set FontSize 14 Set FontSize 14
Set Width 960 Set Width 1080
Set Height 540 Set Height 620
Set Theme "Builtin Solarized Dark" Set Theme "Builtin Solarized Dark"
Set TypingSpeed 50ms Set TypingSpeed 50ms
Set Padding 10 Set Padding 10
Type "vim ~/.vim/modules/core.vim" # ── 0. Start API server in background ──────────────────────────────────────
Type "cd /tmp/demo-project"
Enter
Sleep 0.5s
Type "python3 serve.py > /dev/null 2>&1 &"
Enter
Sleep 1s
# ── 1. Open file — syntax highlighting + statusline ────────────────────────
Type "vim server.py"
Enter
Sleep 3.5s
# ── 2. Fuzzy find files (Ctrl+p → type → select) ──────────────────────────
Ctrl+p
Sleep 1.5s
Type "route"
Sleep 2.5s
Enter
Sleep 3s
# ── 3. Ripgrep project search (,rg → query → select) ──────────────────────
Type ",rg"
Sleep 1.5s
Type "def "
Sleep 3s
Enter
Sleep 3s
# ── 4. Curl the running API from inside Vim ────────────────────────────────
Type ":!curl -s localhost:8080/users | python3 -m json.tool"
Enter
Sleep 4.5s
Enter Enter
Sleep 1.5s Sleep 1.5s
# Show the code with solarized theme and statusline # ── 5. Cheat sheet (,?) ───────────────────────────────────────────────────
Sleep 1s # Reset to server.py so cheat sheet shows code on left, keys on right.
Type ":edit server.py"
# Use Ctrl+p for fuzzy find
Type "\x10"
Sleep 1s
Type "lsp"
Sleep 1s
Escape
Sleep 0.5s
# Go to definition
Type "gg"
Sleep 0.3s
Type "/mapleader"
Enter Enter
Sleep 1s Sleep 1s
# Show cheat sheet
Type ",?" Type ",?"
Sleep 2s Sleep 5.5s
Type "q" Type "q"
Sleep 0.5s Sleep 0.5s
# Open file browser # ── done ───────────────────────────────────────────────────────────────────
Type ",e"
Sleep 1.5s
Escape
Sleep 0.5s
# Quit
Type ":qa!" Type ":qa!"
Enter Enter
Sleep 0.5s Sleep 0.5s

View file

@ -4,6 +4,21 @@
### Added ### Added
- `:ChopsticksStatus` diagnostic command — checks system tools, LSP servers, linters, formatters - `:ChopsticksStatus` diagnostic command — checks system tools, LSP servers, linters, formatters
- `,af` toggle format-on-save (ALE `fix_on_save`)
- `,gL` git log graph (last 20 commits)
- `,gC` FZF git commits search, `,gB` buffer commits
### Fixed
- `g:loaded_logipat` typo → `g:loaded_logiPat` — logiPat was loading fully (0.478ms wasted)
### Changed
- Skip 2 more built-in plugins: openPlugin, manpager (10 → 12 total)
- Remove deprecated `set ttyfast` (no-op since Vim 8)
- Add `grepprg=rg --vimgrep``:grep` now uses ripgrep + quickfix
- Add `diffopt` with histogram algorithm and indent-heuristic
- Consolidate FZF Rg/RgWord/GFiles commands (DRY refactor)
- vim-tmux-navigator: conditional load (only inside tmux), fallback `Ctrl+hjkl` mappings outside
- Add `Ctrl+hjkl` window navigation fallback when tmux-navigator not loaded
## 2.1.0 — 2025-04-22 ## 2.1.0 — 2025-04-22
@ -22,8 +37,8 @@
- Sidebar toggle (`,e` / `,E`) — left-side netrw with `topleft vertical`, winfixwidth, proper toggle - Sidebar toggle (`,e` / `,E`) — left-side netrw with `topleft vertical`, winfixwidth, proper toggle
- Enriched statusline — SLMode, SLGit, SLAle, SLFlags - Enriched statusline — SLMode, SLGit, SLAle, SLFlags
- Toggle feedback — F2/F3/F4/F6/`,ss` echo current state - Toggle feedback — F2/F3/F4/F6/`,ss` echo current state
- `vim .` layout — netrw left + Startify right, proper proportions - `vim .` layout — netrw left + Startify right (removed in later refactor)
- Interactive tutorial (`:ChopsticksLearn`) - Interactive tutorial (`:ChopsticksLearn` — removed in later release)
### Removed (Unix minimalism refactor) ### Removed (Unix minimalism refactor)
- **565 lines** of dead code and bloat - **565 lines** of dead code and bloat

View file

@ -5,7 +5,7 @@
<h1 align="center">chopsticks</h1> <h1 align="center">chopsticks</h1>
<p align="center"> <p align="center">
<strong>Vim for engineers. 25 plugins, works over SSH.</strong> <strong>Vim for engineers. ~25 plugins, works over SSH.</strong>
</p> </p>
<p align="center"> <p align="center">
@ -30,14 +30,14 @@ You SSH into a server. You need to edit code. You want LSP, fuzzy find, git inte
chopsticks gives you a production-ready Vim config in one command. Pure VimScript — no Node.js for the core. Degrades gracefully on TTY. Works the same on your MacBook and your headless Arch box. chopsticks gives you a production-ready Vim config in one command. Pure VimScript — no Node.js for the core. Degrades gracefully on TTY. Works the same on your MacBook and your headless Arch box.
**25 plugins**, LSP, linting, and a hand-built statusline. No bloat, no decorations, just tools. **2425 plugins** (tmux-navigator loads only inside tmux), LSP, linting, and a hand-built statusline. No bloat, no decorations, just tools.
## What's in the box ## What's in the box
| Feature | Description | | Feature | Description |
|---------|-------------| |---------|-------------|
| **LSP** | completion, go-to-def, hover, rename, code actions — pure VimScript ([vim-lsp](https://github.com/prabirshrestha/vim-lsp)) | | **LSP** | completion, go-to-def, hover, rename, code actions — pure VimScript ([vim-lsp](https://github.com/prabirshrestha/vim-lsp)) |
| **Lint + format** | [ALE](https://github.com/dense-analysis/ale) runs black, prettier, gofmt, rustfmt on save | | **Lint + format** | [ALE](https://github.com/dense-analysis/ale) runs black, prettier, goimports, rustfmt on save |
| **Fuzzy find** | files, buffers, grep, tags, marks, commands — [FZF](https://github.com/junegunn/fzf.vim) | | **Fuzzy find** | files, buffers, grep, tags, marks, commands — [FZF](https://github.com/junegunn/fzf.vim) |
| **Git** | status, diff, blame, push, pull, conflict markers — [fugitive](https://github.com/tpope/vim-fugitive) + [gitgutter](https://github.com/airblade/vim-gitgutter) | | **Git** | status, diff, blame, push, pull, conflict markers — [fugitive](https://github.com/tpope/vim-fugitive) + [gitgutter](https://github.com/airblade/vim-gitgutter) |
| **Run file** | `,cr` — auto-detects Python, Go, Rust, JS, C, Shell, and more | | **Run file** | `,cr` — auto-detects Python, Go, Rust, JS, C, Shell, and more |
@ -92,7 +92,7 @@ jk exit insert mode ,? cheat sheet
### Git ### Git
`,gs` status | `,gd` diff | `,gb` blame | `,gc` commit | `,gp` push | `,gl` pull | `]x` `[x` conflict `,gs` status | `,gd` diff | `,gb` blame | `,gc` commit | `,gp` push | `,gl` pull | `,gL` log graph | `,gC` FZF commits | `,gB` buffer commits | `]x` `[x` conflict
### Windows ### Windows
@ -104,7 +104,7 @@ jk exit insert mode ,? cheat sheet
### Toggle ### Toggle
`F2` paste | `F3` line numbers | `F4` relative numbers | `F6` invisible chars | `,ss` spell check `F2` paste | `F3` line numbers | `F4` relative numbers | `F6` invisible chars | `,ss` spell check | `,af` format on save
### Utilities ### Utilities
@ -131,7 +131,7 @@ ALE and vim-lsp coexist cleanly (`ale_disable_lsp=1`). ALE handles linting + for
├── .vimrc thin loader ├── .vimrc thin loader
├── modules/ ├── modules/
│ ├── env.vim TTY detection, truecolor, skip built-in plugins │ ├── env.vim TTY detection, truecolor, skip built-in plugins
│ ├── plugins.vim vim-plug + 25 plugins │ ├── plugins.vim vim-plug + 2425 plugins
│ ├── core.vim settings, keymaps, performance │ ├── core.vim settings, keymaps, performance
│ ├── ui.vim solarized, statusline, startify │ ├── ui.vim solarized, statusline, startify
│ ├── editing.vim easymotion, yank highlight, blank lines │ ├── editing.vim easymotion, yank highlight, blank lines
@ -150,7 +150,7 @@ Each module is self-contained. Comment out one line in `.vimrc` to disable it. A
| Metric | Value | | Metric | Value |
|--------|-------| |--------|-------|
| Lazy-loaded | 7 plugins (on command or filetype) | | Lazy-loaded | 7 plugins (on command or filetype) |
| Built-in plugins skipped | 10 (gzip, tar, zip, vimball, etc.) | | Built-in plugins skipped | 12 (gzip, tar, zip, vimball, logiPat, etc.) |
| Large file threshold | 10MB (auto-disables syntax + undo) | | Large file threshold | 10MB (auto-disables syntax + undo) |
| TTY large file | 500KB (syntax disabled) | | TTY large file | 500KB (syntax disabled) |

View file

@ -423,7 +423,7 @@ _vim_run() {
vim --not-a-term "$@" 2>/dev/null vim --not-a-term "$@" 2>/dev/null
fi fi
} }
if [[ -d "$HOME/.vim/plugged" ]] && [[ -n "$(ls -A "$HOME/.vim/plugged" 2>/dev/null)" ]]; then if [[ -d "$HOME/.vim/plugged" ]] && [[ -n "$(find "$HOME/.vim/plugged" -mindepth 1 -maxdepth 1 2>/dev/null)" ]]; then
warn "PlugClean: removing plugins not listed in .vimrc from ~/.vim/plugged" warn "PlugClean: removing plugins not listed in .vimrc from ~/.vim/plugged"
fi fi
_vim_run +'PlugClean!' +qall || true # remove plugins no longer in vimrc; ignore exit code (none expected) _vim_run +'PlugClean!' +qall || true # remove plugins no longer in vimrc; ignore exit code (none expected)

View file

@ -160,9 +160,13 @@ augroup END
" ── Performance ───────────────────────────────────────────────────────────── " ── Performance ─────────────────────────────────────────────────────────────
set synmaxcol=200 set synmaxcol=200
set ttyfast
set lazyredraw set lazyredraw
set complete-=i set complete-=i
if executable('rg')
set grepprg=rg\ --vimgrep\ --smart-case
set grepformat=%f:%l:%c:%m
endif
set updatetime=300 set updatetime=300
set shortmess+=cI set shortmess+=cI
@ -183,6 +187,10 @@ set exrc
set secure set secure
set sessionoptions=blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal set sessionoptions=blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal
if has("patch-8.1.0360")
set diffopt=filler,internal,context:3,algorithm:histogram,indent-heuristic
endif
" ── Format Options ────────────────────────────────────────────────────────── " ── Format Options ──────────────────────────────────────────────────────────
augroup ChopstickFormatOptions augroup ChopstickFormatOptions

View file

@ -25,7 +25,7 @@ if exists('##TextYankPost') && has('timers')
if v:event.operator !=# 'y' | return | endif if v:event.operator !=# 'y' | return | endif
let l:m = matchadd('IncSearch', let l:m = matchadd('IncSearch',
\ printf('\%%>%dl\%%<%dl', line("'[") - 1, line("']") + 1)) \ printf('\%%>%dl\%%<%dl', line("'[") - 1, line("']") + 1))
call timer_start(150, {-> matchdelete(l:m)}) call timer_start(150, {-> execute('silent! call matchdelete(' . l:m . ')')})
endfunction endfunction
augroup ChopstickYankHL augroup ChopstickYankHL
autocmd! autocmd!

View file

@ -10,10 +10,12 @@ let g:has_true_color = ($COLORTERM ==# 'truecolor' || $COLORTERM ==# '24bit')
let g:loaded_2html_plugin = 1 let g:loaded_2html_plugin = 1
let g:loaded_getscriptPlugin = 1 let g:loaded_getscriptPlugin = 1
let g:loaded_gzip = 1 let g:loaded_gzip = 1
let g:loaded_logipat = 1 let g:loaded_logiPat = 1
let g:loaded_rrhelper = 1 let g:loaded_rrhelper = 1
let g:loaded_tarPlugin = 1 let g:loaded_tarPlugin = 1
let g:loaded_vimballPlugin = 1 let g:loaded_vimballPlugin = 1
let g:loaded_zipPlugin = 1 let g:loaded_zipPlugin = 1
let g:loaded_tutor_mode_plugin = 1 let g:loaded_tutor_mode_plugin = 1
let g:loaded_spellfile_plugin = 1 let g:loaded_spellfile_plugin = 1
let g:loaded_openPlugin = 1
let g:loaded_manpager_plugin = 1

View file

@ -18,6 +18,7 @@ if exists('g:plugs["vim-fugitive"]')
nnoremap <leader>gl :Git pull<CR> nnoremap <leader>gl :Git pull<CR>
nnoremap <leader>gd :Gdiffsplit<CR> nnoremap <leader>gd :Gdiffsplit<CR>
nnoremap <leader>gb :Git blame<CR> nnoremap <leader>gb :Git blame<CR>
nnoremap <leader>gL :Git log --oneline --graph -20<CR>
endif endif
" ── Conflict Navigation ──────────────────────────────────────────────────── " ── Conflict Navigation ────────────────────────────────────────────────────

View file

@ -50,4 +50,6 @@ if exists('g:plugs["ale"]')
nnoremap <silent> [e :ALEPrevious<cr> nnoremap <silent> [e :ALEPrevious<cr>
nnoremap <silent> ]e :ALENext<cr> nnoremap <silent> ]e :ALENext<cr>
nnoremap <silent> <leader>aD :ALEDetail<cr> nnoremap <silent> <leader>aD :ALEDetail<cr>
nnoremap <silent> <leader>af :let g:ale_fix_on_save = !g:ale_fix_on_save
\ <bar> echo 'Format on save: ' . (g:ale_fix_on_save ? 'ON' : 'OFF')<cr>
endif endif

View file

@ -52,7 +52,9 @@ inoremap <expr> <CR> pumvisible() ? asyncomplete#close_popup() : "\<CR>"
function! s:on_lsp_buffer_enabled() abort function! s:on_lsp_buffer_enabled() abort
setlocal omnifunc=lsp#complete setlocal omnifunc=lsp#complete
if !g:is_tty
setlocal signcolumn=yes setlocal signcolumn=yes
endif
nmap <buffer> gd <plug>(lsp-definition) nmap <buffer> gd <plug>(lsp-definition)
nmap <buffer> gy <plug>(lsp-type-definition) nmap <buffer> gy <plug>(lsp-type-definition)

View file

@ -60,6 +60,8 @@ if exists('g:plugs["fzf.vim"]')
nnoremap <leader>fL :Lines<CR> nnoremap <leader>fL :Lines<CR>
nnoremap <leader>f/ :History/<CR> nnoremap <leader>f/ :History/<CR>
nnoremap <leader>f: :History:<CR> nnoremap <leader>f: :History:<CR>
nnoremap <leader>gC :Commits<CR>
nnoremap <leader>gB :BCommits<CR>
endif endif
let g:fzf_layout = { 'down': '40%' } let g:fzf_layout = { 'down': '40%' }
@ -70,30 +72,27 @@ else
let g:fzf_preview_window = ['right:50%', 'ctrl-/'] let g:fzf_preview_window = ['right:50%', 'ctrl-/']
endif endif
if g:is_tty function! s:Preview() abort
command! -bang -nargs=* Rg return g:is_tty ? {} : fzf#vim#with_preview()
\ call fzf#vim#grep( endfunction
\ 'rg --column --line-number --no-heading --color=always --smart-case -- '
\ .shellescape(<q-args>), 1, <bang>0)
command! -bang GFiles call fzf#vim#gitfiles('', <bang>0)
else
command! -bang -nargs=* Rg
\ call fzf#vim#grep(
\ 'rg --column --line-number --no-heading --color=always --smart-case -- '
\ .shellescape(<q-args>), 1, fzf#vim#with_preview(), <bang>0)
command! -bang GFiles call fzf#vim#gitfiles('', fzf#vim#with_preview(), <bang>0)
endif
if g:is_tty command! -bang -nargs=* Rg
\ call fzf#vim#grep(
\ 'rg --column --line-number --no-heading --color=always --smart-case -- '
\ .shellescape(<q-args>), 1, s:Preview(), <bang>0)
command! -bang -nargs=* RgWord command! -bang -nargs=* RgWord
\ call fzf#vim#grep( \ call fzf#vim#grep(
\ 'rg --column --line-number --no-heading --color=always --smart-case -F -- ' \ 'rg --column --line-number --no-heading --color=always --smart-case -F -- '
\ .shellescape(expand('<cword>')), 1, <bang>0) \ .shellescape(expand('<cword>')), 1, s:Preview(), <bang>0)
else command! -bang -nargs=? GFiles call fzf#vim#gitfiles(<q-args>, s:Preview(), <bang>0)
command! -bang -nargs=* RgWord
\ call fzf#vim#grep( " ── Window Navigation ───────────────────────────────────────────────────────
\ 'rg --column --line-number --no-heading --color=always --smart-case -F -- '
\ .shellescape(expand('<cword>')), 1, fzf#vim#with_preview(), <bang>0) if empty($TMUX)
nnoremap <C-h> <C-w>h
nnoremap <C-j> <C-w>j
nnoremap <C-k> <C-w>k
nnoremap <C-l> <C-w>l
endif endif
" ── Window Maximize Toggle ────────────────────────────────────────────────── " ── Window Maximize Toggle ──────────────────────────────────────────────────

View file

@ -49,6 +49,8 @@ Plug 'fatih/vim-go', { 'for': 'go' }
Plug 'mbbill/undotree', { 'on': 'UndotreeToggle' } Plug 'mbbill/undotree', { 'on': 'UndotreeToggle' }
Plug 'mhinz/vim-startify' Plug 'mhinz/vim-startify'
Plug 'lifepillar/vim-solarized8' Plug 'lifepillar/vim-solarized8'
if !empty($TMUX)
Plug 'christoomey/vim-tmux-navigator' Plug 'christoomey/vim-tmux-navigator'
endif
call plug#end() call plug#end()

View file

@ -248,6 +248,7 @@ function! s:CheatSheet() abort
\ ' ,mt table of contents', \ ' ,mt table of contents',
\ ' [g ]g LSP diagnostics', \ ' [g ]g LSP diagnostics',
\ ' [e ]e ALE errors', \ ' [e ]e ALE errors',
\ ' ,af format on save',
\ ' :LspInstallServer setup LSP', \ ' :LspInstallServer setup LSP',
\ '', \ '',
\ ' ── edit ──────────────────', \ ' ── edit ──────────────────',
@ -268,6 +269,8 @@ function! s:CheatSheet() abort
\ ' ,gc commit', \ ' ,gc commit',
\ ' ,gp push', \ ' ,gp push',
\ ' ,gl pull', \ ' ,gl pull',
\ ' ,gL log graph',
\ ' ,gC FZF commits',
\ ' [x ]x conflict markers', \ ' [x ]x conflict markers',
\ '', \ '',
\ ' ── windows ───────────────', \ ' ── windows ───────────────',