Compare commits

...

35 commits
v2.0.0 ... main

Author SHA1 Message Date
259744b5e3 Improve test runner discoverability
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
test / docs (push) Has been cancelled
2026-05-05 01:17:30 +08:00
67ffdfe99f Add reusable project test runner 2026-05-05 00:31:12 +08:00
1b9e8b3999 Add bootstrap dry-run safety checks
Some checks are pending
test / startup (macos-latest) (push) Waiting to run
test / startup (ubuntu-latest) (push) Waiting to run
test / shellcheck (push) Waiting to run
test / docs (push) Waiting to run
2026-05-04 11:49:20 +08:00
ddb4c691b9 Add installer dry-run and configure-only modes
Some checks are pending
test / startup (macos-latest) (push) Waiting to run
test / startup (ubuntu-latest) (push) Waiting to run
test / shellcheck (push) Waiting to run
test / docs (push) Waiting to run
2026-05-03 23:40:01 +08:00
d253583d0b Improve installer profile handling and validation 2026-05-03 23:23:21 +08:00
7ff5326932 Make Vim help profile aware 2026-05-03 23:23:21 +08:00
a9e16d22d3 Respect profiles in local config and status 2026-05-03 23:23:21 +08:00
db9b96f577 Refine Vim defaults and add profiles 2026-05-03 23:23:21 +08:00
cddb5fa725 feat(ui): native tabline + fixed-width signcolumn
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
Show all listed buffers in a tabline (current bufnr highlighted, modified
marker '+'), and reserve a permanent signcolumn so width doesn't jitter when
gitgutter/ALE/LSP signs come and go.

- ui.vim: TLBuild() + TabLine{,Sel,Fill} hl groups via ColorScheme autocmd;
  set signcolumn=yes for non-TTY (overrides core.vim's previous logic)
- core.vim: drop non-TTY signcolumn branch (centralized in ui.vim)
2026-04-27 18:08:40 +08:00
28e4ed3794 fix(ui): drop noisy colorcolumn, polish highlights, warn on missing colorscheme
The full-column ColorColumn was visually overwhelming on every line, especially
with default vim's LightRed (kicked in silently when solarized8 wasn't installed).
Replace it with an OverLength match that only highlights characters past the
buffer's textwidth.

- editing.vim: OverLength matchadd, refreshed on FileType/BufWinEnter/OptionSet
- languages.vim: drop colorcolumn from per-filetype autocmds (textwidth still set)
- ui.vim: VertSplit/CursorLine/CursorLineNr/SignColumn override via ColorScheme
  autocmd; fillchars for vert and eob; timer-based warning if solarized8 missing
2026-04-27 18:08:28 +08:00
4fd9a09948 fix: add missing visual mode mappings for clipboard, movement, and editing
Some checks are pending
test / startup (macos-latest) (push) Waiting to run
test / startup (ubuntu-latest) (push) Waiting to run
test / shellcheck (push) Waiting to run
,p ,P ,F ,W ,* now work in visual mode. 0→^ and C-d/C-u centering
carry into visual selections. Cheat sheet updated with (v) markers.
2026-04-26 15:32:55 +08:00
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
565f2fc80b docs: full README update — accurate keybindings, :ChopsticksStatus, lazy count fix
Some checks are pending
test / startup (macos-latest) (push) Waiting to run
test / startup (ubuntu-latest) (push) Waiting to run
test / shellcheck (push) Waiting to run
- Add :ChopsticksStatus to features table, LSP section, troubleshooting
- Add ,? cheat sheet, ,mp preview, toggle keys to keybinding reference
- Add missing bindings: [<Space>], ,E, ,gl, ,bd, ,cp, ,cf, ,wa, ,F, ,W
- Fix lazy-loaded count: 7 (not 6)
- Update architecture descriptions to match current modules
- Add health check section to QUICKSTART
- Add :ChopsticksStatus to cheat sheet sidebar
2026-04-22 15:25:40 +08:00
0686f9c6be feat: add :ChopsticksStatus diagnostic command and CHANGELOG
:ChopsticksStatus shows system tools, LSP servers, linters, and
formatters with OK/missing status. Helps new users discover what
to install without leaving Vim.
2026-04-22 15:21:35 +08:00
73a55e18e6 docs: fix plugin count (25), improve first-launch guidance, add :LspInstallServer to cheat sheet 2026-04-22 15:09:27 +08:00
0e92e6c1a8 feat: restore previm markdown preview (lazy-loaded, ,mp) 2026-04-22 14:57:55 +08:00
f8d45472dc docs: remove stale references to deleted features in QUICKSTART 2026-04-22 14:53:53 +08:00
fbb692846b ui: redesign cheat sheet — vertical sidebar, one key per line, aligned 2026-04-22 14:41:09 +08:00
51b10aa16d feat: restore ,? cheat sheet — lean version matching current keybindings 2026-04-22 14:35:45 +08:00
f6b6976bc0 fix(ci): lower plugin count threshold to match 24-plugin config 2026-04-22 14:33:07 +08:00
f43c8e5f0f Merge refactor/unix-minimalism: drop 565 lines, 5 plugins 2026-04-22 14:27:21 +08:00
5371521360 refactor: Unix minimalism — drop 565 lines, 5 plugins, 0 features that matter
Remove: Goyo, Limelight, previm, vim-obsession, indentLine (5 plugins).
Remove: cheat sheet function (90 lines), tutorial system (269 lines),
  ASCII art banner, tab management (8 keybindings), dead code (HasPaste,
  CleanExtraSpaces, ToggleNumber), spell nav bindings, scratch buffer,
  syntax stack debug, TTY startup message.
Fold: writing.vim → languages.vim (vim-markdown settings only).
Keep: everything that edits code — LSP, FZF, Git, ALE, netrw sidebar.
Result: 24 plugins, 949 lines, 19ms startup. No decorations, just tools.
2026-04-22 14:27:15 +08:00
b1746c72b7 Merge feature/ui-visibility: sidebar, statusline, toggle feedback 2026-04-22 12:52:34 +08:00
261b6cdce6 feat: UI visibility — sidebar toggle, enriched statusline, toggle feedback
- Netrw sidebar: ,e/,E toggles left-side tree (topleft + winfixwidth)
- vim . opens netrw sidebar (30 cols) + Startify dashboard
- Statusline: buffer count, PASTE/SPELL/MAX flags, LSP server, encoding
- Toggle echo: F2/F3/F4/F6/,ss/,z show new state
- README: fix empty table header in "What's in the box"
2026-04-22 12:52:31 +08:00
2e37efe644 feat: improve UI visibility and user interaction
Statusline: add buffer count, PASTE/SPELL/MAX flags, LSP server name,
encoding (shown only when non-utf-8/unix).

File tree sidebar: ,e toggles netrw sidebar (cwd), ,E toggles sidebar
(current file dir). Tracks t:netrw_sidebar_buf per-tab for reliable
toggle. browse_split=4 opens files in editor window.

vim . integration: opens tree sidebar (30 cols) + Startify dashboard.
Files opened from tree replace the dashboard.

Toggle feedback: F2/F3/F4/F6/,ss/,z now echo their new state.
2026-04-22 12:13:37 +08:00
97ca2be139 Fix shellcheck SC2012: use find instead of ls for plugin count
Some checks are pending
test / startup (macos-latest) (push) Waiting to run
test / startup (ubuntu-latest) (push) Waiting to run
test / shellcheck (push) Waiting to run
2026-04-22 01:02:18 +08:00
26 changed files with 1681 additions and 794 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

@ -36,31 +36,40 @@ jobs:
cp modules/*.vim ~/.vim/modules/ cp modules/*.vim ~/.vim/modules/
- name: Install plugins - name: Install plugins
run: vim -es -u .vimrc -c 'PlugInstall --sync' -c 'qa!' 2>&1 || true
- name: Test startup
run: | run: |
vim -u .vimrc -es -N -c 'qa!' 2>&1 set +e
echo "Vim exited cleanly" vim -i NONE -es -u .vimrc -N -c 'PlugInstall --sync' -c 'qa!' 2>&1
plug_status=$?
- name: Verify modules load set -e
run: | if [ "$plug_status" -ne 0 ]; then
vim -u .vimrc -N -c 'redir! > /tmp/test.txt | echo len(g:plugs) | redir END | qa!' 2>/dev/null echo "PlugInstall exited with $plug_status; validating plugin directories"
PLUGS=$(cat /tmp/test.txt | tr -d '[:space:]')
echo "Plugins registered: $PLUGS"
if [ "$PLUGS" -lt 25 ]; then
echo "FAIL: expected 25+ plugins, got $PLUGS"
exit 1
fi fi
for plugin in fzf fzf.vim vim-fugitive vim-gitgutter ale vim-lsp vim-lsp-settings asyncomplete.vim asyncomplete-lsp.vim vim-markdown; do
test -d "$HOME/.vim/plugged/$plugin" || {
echo "FAIL: missing plugin $plugin"
exit 1
}
done
- name: Measure startup time - name: Run Vim smoke tests
run: | run: scripts/test.sh vim
vim -u .vimrc --startuptime /tmp/startup.log -c 'qa!' 2>/dev/null
tail -1 /tmp/startup.log
shellcheck: shellcheck:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Shellcheck install.sh - name: Check test runner CLI
run: shellcheck install.sh get.sh run: |
scripts/test.sh --help
scripts/test.sh list
- name: Run shell and installer tests
run: scripts/test.sh shell installer bootstrap
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install markdownlint
run: npm install -g markdownlint-cli
- name: Lint Markdown
run: scripts/test.sh docs

4
.gitignore vendored
View file

@ -2,3 +2,7 @@
*.swo *.swo
.DS_Store .DS_Store
Session.vim Session.vim
autoload/
plugged/
.swap/
.undo/

13
.markdownlint.json Normal file
View file

@ -0,0 +1,13 @@
{
"default": true,
"MD013": false,
"MD022": false,
"MD024": {
"siblings_only": true
},
"MD032": false,
"MD033": false,
"MD040": false,
"MD041": false,
"MD060": false
}

15
.vimrc
View file

@ -1,10 +1,18 @@
let g:chopsticks_dir = fnamemodify(resolve(expand('<sfile>')), ':h')
let s:xdg_config_home = !empty($XDG_CONFIG_HOME) && $XDG_CONFIG_HOME =~# '^/'
\ ? $XDG_CONFIG_HOME
\ : '~/.config'
let s:local_config = expand(get(g:, 'chopsticks_local_config',
\ s:xdg_config_home . '/chopsticks.vim'))
if filereadable(s:local_config)
execute 'source ' . fnameescape(s:local_config)
endif
if exists('g:chopsticks_loaded') | finish | endif if exists('g:chopsticks_loaded') | finish | endif
let g:chopsticks_loaded = 1 let g:chopsticks_loaded = 1
let g:chopsticks_dir = fnamemodify(resolve(expand('<sfile>')), ':h')
function! s:load(mod) abort function! s:load(mod) abort
execute 'source ' . g:chopsticks_dir . '/modules/' . a:mod . '.vim' execute 'source ' . fnameescape(g:chopsticks_dir . '/modules/' . a:mod . '.vim')
endfunction endfunction
call s:load('env') call s:load('env')
@ -16,6 +24,5 @@ call s:load('navigation')
call s:load('lsp') call s:load('lsp')
call s:load('lint') call s:load('lint')
call s:load('git') call s:load('git')
call s:load('writing')
call s:load('languages') call s:load('languages')
call s:load('tools') call s:load('tools')

159
CHANGELOG.md Normal file
View file

@ -0,0 +1,159 @@
# Changelog
## Unreleased
### Added
- `~/.config/chopsticks.vim` local pre-load config for profile and user choices
- `g:chopsticks_enable_markdown_preview` to control Previm independently
- `g:chopsticks_profile` with `minimal`, `engineer`, and `full` profiles
- `.markdownlint.json` aligned with the project's README/changelog style
- `: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
- Interactive installer profile selection for `minimal`, `engineer`, and `full`
- `install.sh --profile=minimal|engineer|full` for scripted profile selection
- `install.sh --dry-run` to show the resolved profile/config path without writes
- `install.sh --configure-only` to update local profile config without reinstalling
- `get.sh --dry-run` for safe bootstrap previews before clone/update/install
- `CHOPSTICKS_DEST=/absolute/path` to test or install the bootstrap target elsewhere
- `scripts/test.sh` local test runner reused by GitHub Actions
- `scripts/test.sh quick`, `--help`, and `list` for easier local test discovery
### Fixed
- `g:loaded_logipat` typo → `g:loaded_logiPat` — logiPat was loading fully (0.478ms wasted)
- `get.sh` now refuses to update an existing `~/.vim` git repo unless its
origin is chopsticks
- Large file protection now stays active after filetype and syntax autocommands
- `g:ale_fix_on_save = 0` in local config is now respected
- Local config now respects absolute `XDG_CONFIG_HOME` instead of hardcoding
`~/.config`
### Changed
- `,?` cheat sheet is now profile-aware and hides LSP/ALE/preview/UndoTree keys
when those features are disabled
- Module reload/source paths now use `fnameescape()` so installs in paths with
spaces are handled correctly
- CI now verifies path-safe module loading, the local config hook, and
minimal-profile cheat sheet output
- Markdown now opens in quiet writing mode by default: no real-time markdownlint,
no Marksman LSP, no spell noise, no conceal, no sign column, and no realtime preview
- Native `s` is no longer shadowed by EasyMotion; use `,S` for the two-character jump
- `,w` now uses a normal `:write` instead of forced `:write!`
- Swap files are enabled again and stored under `~/.vim/.swap` for crash recovery
- Installer defaults are slimmer: only core search tools stay selected by default;
language and lint suites are opt-in
- `:ChopsticksStatus` now respects disabled LSP/lint profiles instead of reporting
intentionally disabled tools as missing
- `,sv` now clears the load guard before sourcing `$MYVIMRC`
- CI now verifies key plugin directories, Markdown quiet defaults, markdownlint,
and an explicit startup-time threshold
- Installer plugin validation now checks every plugin required by the active profile
- The optional tool menu now hides LSP/lint suites in `minimal` and selects
Marksman by default in `full`
- tmux integration is written as a managed block so future installer runs can
update it without appending duplicate bindings
- Installer cleanup now restores the cursor after interrupted checkbox menus
- Bootstrap dry-run now refuses unrelated existing git repos before any writes
- CI now shares shell, installer, bootstrap, docs, and Vim smoke checks with
the local test runner
- CI now checks the test runner help and group-list commands
- 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
### Added
- Cheat sheet (`,?`) — vertical sidebar, one key per line, section headers
- Previm markdown preview restored (lazy-loaded, `,mp`)
- `:LspInstallServer` added to cheat sheet
### Changed
- Plugin count: 25 (restored previm, dropped 5 bloat plugins)
- QUICKSTART updated — removed stale references, improved first-launch guidance
## 2.0.0 — 2025-04-21
### Added
- Sidebar toggle (`,e` / `,E`) — left-side netrw with `topleft vertical`, winfixwidth, proper toggle
- Enriched statusline — SLMode, SLGit, SLAle, SLFlags
- Toggle feedback — F2/F3/F4/F6/`,ss` echo current state
- `vim .` layout — netrw left + Startify right (removed in later refactor)
- Interactive tutorial (`:ChopsticksLearn` — removed in later release)
### Removed (Unix minimalism refactor)
- **565 lines** of dead code and bloat
- 5 plugins: Goyo, Limelight, vim-obsession, indentLine, vim-unimpaired
- `modules/writing.vim` — folded into `languages.vim`
- `tutor/chopsticks.tutor` — removed (269 lines)
- Tab management keybindings (8 mappings), spell nav bindings (4 mappings)
- Dead functions: HasPaste(), CleanExtraSpaces(), ToggleNumber(), SynStack
- TTY welcome message, `,so`, `,ms`, `,sh` mappings
### Changed
- CI plugin threshold lowered to 20
- README: hero layout with demo GIF, badges, architecture diagram
- vim-markdown settings absorbed from writing.vim into languages.vim
## 1.3.0 — 2025-04-20
### Changed
- Startup: 39ms → 19ms (51% faster)
- Dropped vim-unimpaired for performance
- Runtime tuning across modules
## 1.2.0 — 2025-04-19
### Added
- Hero README with demo GIF, CI badges
- GitHub Actions CI (startup test on macOS + Ubuntu, shellcheck)
- Issue/PR templates
### Changed
- Documentation rewrite — clean, short, for engineers
## 1.1.0 — 2025-04-18
### Added
- 12-module architecture (env → plugins → core → ui → editing → navigation → lsp → lint → git → writing → languages → tools)
- Zen mode (Goyo + Limelight)
- Run file (`,cr`) with auto filetype detection
- Smart search (SmartFiles, Rg, RgWord)
- EasyMotion, yank highlight, undo tree
- Robust installer (`get.sh`) with preflight checks
### Changed
- `.vimrc` split into 12 self-contained modules
- Comprehensive bug audit (14 fixes)
## 1.0.0 — 2025-04-16
### Added
- Initial Vim configuration — migrated from Neovim
- vim-plug plugin manager
- vim-lsp + asyncomplete (pure VimScript LSP)
- ALE linting + format-on-save
- FZF fuzzy finder
- Fugitive + GitGutter
- Solarized colorscheme
- TTY detection and graceful degradation
- Platform installer (macOS, Debian, Arch, Fedora)

View file

@ -3,7 +3,7 @@
## Rules ## Rules
1. **No Node.js dependencies.** The LSP engine is pure VimScript. Some language servers need Node — that's fine. The config itself must not. 1. **No Node.js dependencies.** The LSP engine is pure VimScript. Some language servers need Node — that's fine. The config itself must not.
2. **Startup matters.** Run `vim --startuptime /tmp/s.log -c qa!` before and after. If your change adds >1ms, it needs a good reason. 2. **Startup matters.** Run `vim -u .vimrc -i NONE --startuptime /tmp/s.log -es -N -c qa!` before and after. If your change adds >1ms, it needs a good reason.
3. **Works on TTY.** Test over SSH. If it breaks in a terminal without true color, fix it or gate it behind `g:is_tty`. 3. **Works on TTY.** Test over SSH. If it breaks in a terminal without true color, fix it or gate it behind `g:is_tty`.
4. **One module, one concern.** Don't put git config in lsp.vim. 4. **One module, one concern.** Don't put git config in lsp.vim.
@ -13,11 +13,25 @@
2. If it's not needed at startup, lazy-load it: `Plug 'foo/bar', { 'on': 'FooCommand' }` 2. If it's not needed at startup, lazy-load it: `Plug 'foo/bar', { 'on': 'FooCommand' }`
3. Put config in the appropriate module 3. Put config in the appropriate module
4. Update the cheat sheet in `modules/tools.vim` if you add keybindings 4. Update the cheat sheet in `modules/tools.vim` if you add keybindings
5. Test on both macOS and Linux 5. Run `scripts/test.sh vim` locally after installing plugins
6. Test on both macOS and Linux when changing terminal or package-manager behavior
## Local tests
```bash
scripts/test.sh --help
scripts/test.sh quick
scripts/test.sh vim
```
`scripts/test.sh quick` runs shell, docs, installer, and bootstrap checks without requiring Vim plugins.
`scripts/test.sh vim` expects plugins to be installed under `~/.vim/plugged`.
Use `STARTUP_LIMIT_MS=150 scripts/test.sh vim` to match CI's startup threshold.
## Reporting bugs ## Reporting bugs
Open an issue. Include: Open an issue. Include:
- OS and Vim version - OS and Vim version
- Whether you're on SSH/TTY - Whether you're on SSH/TTY
- Steps to reproduce - Steps to reproduce
@ -25,6 +39,6 @@ Open an issue. Include:
## Code style ## Code style
- Named augroups with `autocmd!` - Named augroups with `autocmd!`
- No comments explaining *what* — only *why* - No comments explaining _what_ — only _why_
- `exists('g:plugs["..."]')` guards for plugin-dependent config - `exists('g:plugs["..."]')` guards for plugin-dependent config
- Test with `vim -u .vimrc --startuptime /tmp/s.log -c qa!` - Test with `scripts/test.sh vim`

View file

@ -6,14 +6,28 @@ Five minutes from zero to a working Vim setup.
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash 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 -- --profile=minimal
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --dry-run --profile=full
``` ```
Open vim. Plugins install automatically on first launch (30-60s). Restart vim. Open vim. First launch auto-installs plugins — **wait 30-60s, don't close vim**. Restart when done.
Default profile is `engineer`. Interactive installs ask for a profile first;
`--profile=minimal`, `--profile=engineer`, or `--profile=full` selects it
without prompting. You can later put `let g:chopsticks_profile = 'minimal'` in
`${XDG_CONFIG_HOME:-~/.config}/chopsticks.vim` for a smaller core-only setup,
or use `full` for the heavier Markdown/LSP feedback.
To switch later without reinstalling anything:
```bash
cd ~/.vim && ./install.sh --configure-only --profile=full
```
## Modes ## Modes
| Mode | Enter | Leave | | Mode | Enter | Leave |
|------|-------|-------| | ------ | --------------- | ------------- |
| Normal | startup default | — | | Normal | startup default | — |
| Insert | `i` / `a` / `o` | `Esc` or `jk` | | Insert | `i` / `a` / `o` | `Esc` or `jk` |
| Visual | `v` / `V` | `Esc` | | Visual | `v` / `V` | `Esc` |
@ -26,8 +40,7 @@ Esc / jk back to Normal
,x save + quit ,x save + quit
:q! force quit :q! force quit
Ctrl+s save from any mode Ctrl+s save from any mode
,? cheat sheet ,? cheat sheet (toggle sidebar)
:ChopsticksLearn interactive tutorial
``` ```
## Find things ## Find things
@ -53,7 +66,7 @@ K hover docs
Tab / S-Tab cycle completions Tab / S-Tab cycle completions
``` ```
Install language servers with `:LspInstallServer` (auto-detects filetype). **First time in a new language?** Run `:LspInstallServer` — it auto-detects filetype and installs the right server. Do this once per language.
## Git ## Git
@ -68,7 +81,7 @@ Install language servers with `:LspInstallServer` (auto-detects filetype).
## Edit ## Edit
``` ```
s + 2 chars EasyMotion jump ,S + 2 chars EasyMotion jump
gc toggle comment gc toggle comment
cs"' change surrounding " to ' cs"' change surrounding " to '
Alt+j / Alt+k move line Alt+j / Alt+k move line
@ -85,12 +98,23 @@ Ctrl+h/j/k/l splits + tmux panes
,tv / ,th terminal ,tv / ,th terminal
``` ```
## Write prose ## Markdown
``` ```
,zen zen mode (Goyo + Limelight) ,mp preview in browser
,mp markdown preview in browser
,mt table of contents ,mt table of contents
``` ```
Markdown is quiet by default: no real-time lint, no spell noise, no concealed
syntax. Enable the heavier Markdown tools only when you want them.
## Health check
```
:ChopsticksStatus see what's installed and what's missing
```
The `,?` cheat sheet follows your active profile, so `minimal` users only see
keys for features that are actually loaded.
See [README](README.md) for the full reference. See the [wiki](https://github.com/m1ngsama/chopsticks/wiki) for deep dives. See [README](README.md) for the full reference. See the [wiki](https://github.com/m1ngsama/chopsticks/wiki) for deep dives.

141
README.md
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. 29 plugins, 19ms startup, works over SSH.</strong> <strong>Vim for engineers. ~25 plugins, works over SSH.</strong>
</p> </p>
<p align="center"> <p align="center">
@ -30,47 +30,76 @@ 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.
**19ms startup** with 29 plugins, LSP, linting, and a hand-built statusline. Faster than most people's empty vimrc. **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 |
|-|-| | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **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) |
| **Zen mode** | `,zen` — [Goyo](https://github.com/junegunn/goyo.vim) + [Limelight](https://github.com/junegunn/limelight.vim) |
| **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 |
| **Markdown** | quiet writing defaults, browser preview (`,mp`), table of contents (`,mt`) |
| **Diagnostics** | `:ChopsticksStatus` — see what's installed, what's missing, how to fix it |
| **TTY-aware** | degrades gracefully on SSH, console, slow links — never breaks | | **TTY-aware** | degrades gracefully on SSH, console, slow links — never breaks |
| **19ms startup** | lazy-loaded plugins, deferred LSP init, zero redundant work |
## Install ## Install
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash 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 -- --profile=minimal
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --dry-run --profile=full
``` ```
Or manually: Or manually:
```bash ```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
cd ~/.vim && ./install.sh cd ~/.vim && ./install.sh --profile=engineer
``` ```
Supports macOS (brew), Debian/Ubuntu (apt), Arch (pacman), Fedora (dnf). Supports macOS (brew), Debian/Ubuntu (apt), Arch (pacman), Fedora (dnf).
Set `CHOPSTICKS_DEST=/absolute/path` before running `get.sh` to install
somewhere other than `~/.vim`.
First launch installs plugins automatically (30-60s). Restart vim when done. First launch installs plugins automatically (30-60s). Restart vim when done.
Use `./install.sh --dry-run --profile=full` to inspect the resolved profile and
config path without changing files. Use `./install.sh --configure-only
--profile=minimal` to switch profiles without reinstalling plugins or tools.
## Profiles
Default profile: `engineer`. Interactive installs ask for this profile before
plugins are installed; `--profile=minimal`, `--profile=engineer`, or
`--profile=full` selects it without prompting. `--yes` keeps the existing local
profile or uses `engineer`.
```vim
" Put this in ${XDG_CONFIG_HOME:-~/.config}/chopsticks.vim.
let g:chopsticks_profile = 'minimal' " core navigation/editing/git/markdown
let g:chopsticks_profile = 'engineer' " default: LSP, ALE, syntax extras
let g:chopsticks_profile = 'full' " engineer + heavier Markdown feedback
```
`minimal` avoids LSP, ALE, completion plugins, extra language syntax plugins,
Startify, UndoTree, and browser Markdown preview. `full` keeps those and opts
into Markdown lint, format, spell, conceal, Marksman, and LSP virtual text.
Project updates leave `~/.config/chopsticks.vim` alone, so put local choices
there instead of editing the managed `.vimrc`. The `,?` cheat sheet follows the
active profile and only shows keys for enabled features.
## Keys ## Keys
Leader: `,` — press `,?` for the full cheat sheet inside vim. Leader: `,`
``` ```
Ctrl+p fuzzy find file gd go to definition Ctrl+p fuzzy find file gd go to definition
,rg ripgrep project K hover docs ,rg ripgrep project K hover docs
,gs git status ,cr run current file ,e toggle file sidebar ,cr run current file
,zen zen mode ,f format ,gs git status ,f format
,w save ,q quit ,w save ,q quit
jk exit insert mode ,? cheat sheet jk exit insert mode ,? cheat sheet
``` ```
@ -80,27 +109,35 @@ jk exit insert mode ,? cheat sheet
### Files ### Files
`Ctrl+p` find | `,b` buffers | `,rg` grep | `,rG` grep word | `,fh` recent | `,e` browser | `,,` last file `Ctrl+p` find | `,b` buffers | `,rg` grep | `,rG` grep word | `,fh` recent | `,fl` lines | `,e` browser | `,E` browser (file dir) | `,,` last file
### Code ### Code
`gd` def | `gy` type | `gi` impl | `gr` refs | `K` docs | `[g` `]g` diagnostics | `,rn` rename | `,ca` action | `,o` outline | `,cr` run `gd` def | `gy` type | `gi` impl | `gr` refs | `K` docs | `[g` `]g` diagnostics | `[e` `]e` ALE errors | `,rn` rename | `,ca` action | `,o` outline | `,cr` run
### Edit ### Edit
`s`+2ch jump | `gc` comment | `cs"'` surround | `Alt+j/k` move line | `,u` undo tree | `,y` clipboard | `,*` replace word `,S`+2ch jump | `gc` comment | `cs"'` surround | `Alt+j/k` move line | `,u` undo tree | `,y` clipboard | `,*` replace word | `,F` re-indent | `,W` strip whitespace | `[<Space>` `]<Space>` blank lines
### Git ### Git
`,gs` status | `,gd` diff | `,gb` blame | `,gc` commit | `,gp` push | `]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
`Ctrl+hjkl` navigate (+ tmux) | `,z` maximize | `,h` `,l` buffers | `,tv` terminal | `Esc Esc` exit terminal `Ctrl+hjkl` navigate (+ tmux) | `,z` maximize | `,h` `,l` buffers | `,bd` close buffer | `,=` `,` resize | `,tv` `,th` terminal | `Esc Esc` exit terminal
### Writing ### Markdown
`,zen` zen mode | `,mp` markdown preview | `,mt` table of contents `,mp` preview in browser | `,mt` table of contents
### Toggle
`F2` paste | `F3` line numbers | `F4` relative numbers | `F6` invisible chars | `,ss` spell check | `,af` format on save
### Utilities
`,cp` copy full path | `,cf` copy filename | `,ev` edit vimrc | `,sv` reload vimrc | `,wa` save all | `:ChopsticksStatus` diagnostics
</details> </details>
@ -109,65 +146,79 @@ jk exit insert mode ,? cheat sheet
```vim ```vim
:LspInstallServer " auto-detects filetype :LspInstallServer " auto-detects filetype
:LspStatus " check what's running :LspStatus " check what's running
:ChopsticksStatus " see all tools + LSP + linters at a glance
``` ```
pylsp, gopls, rust-analyzer, clangd, marksman, sqls — no Node.js. JS/TS servers need Node. pylsp, gopls, rust-analyzer, clangd, sqls — no Node.js. JS/TS servers need Node.
Markdown LSP (`marksman`) is opt-in so prose buffers stay quiet by default.
ALE and vim-lsp coexist cleanly (`ale_disable_lsp=1`). ALE handles linting + formatting. vim-lsp handles everything else. ALE and vim-lsp coexist cleanly (`ale_disable_lsp=1`). ALE handles linting + formatting. vim-lsp handles everything else.
## Markdown
Markdown opens in writing mode: wrapped text, no spell noise, no concealed
syntax, no sign column, no real-time markdownlint, and no Marksman diagnostics.
The explicit commands still work:
```vim
,mp " preview in browser
,mt " table of contents
```
Opt into heavier Markdown tooling from your own vimrc before loading
chopsticks:
```vim
let g:chopsticks_markdown_lint = 1
let g:chopsticks_markdown_format_on_save = 1
let g:chopsticks_markdown_lsp = 1
let g:chopsticks_markdown_spell = 1
let g:chopsticks_markdown_conceal = 1
let g:previm_enable_realtime = 1
```
For Markdown LSP, install or select `marksman` first.
## Architecture ## Architecture
``` ```
~/.vim/ ~/.vim/
├── .vimrc thin loader (12 lines) ├── .vimrc thin loader
├── modules/ ├── modules/
│ ├── env.vim TTY detection, truecolor │ ├── env.vim TTY detection, truecolor, skip built-in plugins
│ ├── plugins.vim vim-plug + 29 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 │ ├── editing.vim easymotion, yank highlight, blank lines
│ ├── navigation.vim fzf, netrw, windows, terminal │ ├── navigation.vim fzf, netrw sidebar, windows, terminal
│ ├── lsp.vim vim-lsp, asyncomplete │ ├── lsp.vim vim-lsp, asyncomplete
│ ├── lint.vim ale, format-on-save │ ├── lint.vim ale, format-on-save
│ ├── git.vim fugitive, gitgutter │ ├── git.vim fugitive, gitgutter, conflict nav
│ ├── writing.vim markdown, previm, zen mode │ ├── languages.vim vim-go, markdown, filetype settings
│ ├── languages.vim vim-go, filetype settings │ └── tools.vim run file, quickfix, cheat sheet, diagnostics
│ └── tools.vim cheat sheet, run file, helpers
└── tutor/
└── chopsticks.tutor
``` ```
Each module is self-contained. Comment out one line in `.vimrc` to disable it. Add your own with `call s:load('mine')`. Each module is self-contained. Comment out one line in `.vimrc` to disable it. Add your own with `call s:load('mine')`.
## Learn
```vim
:ChopsticksLearn " interactive tutorial — 10 lessons
,? " cheat sheet (every binding)
```
## Performance ## Performance
| Metric | Value | | Metric | Value |
|--------|-------| | ------------------------ | ------------------------------------------- |
| Startup time | **19ms** (29 plugins loaded) | | Lazy-loaded | 7 plugins (on command or filetype) |
| Lazy-loaded | 8 plugins (on command or filetype) | | Built-in plugins skipped | 12 (gzip, tar, zip, vimball, logiPat, etc.) |
| Built-in plugins skipped | 10 (gzip, tar, zip, vimball, etc.) |
| Runtime lint delay | 200ms (no thrashing during edits) |
| 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) |
Measured with `vim --startuptime`. We benchmark every change.
## Troubleshooting ## Troubleshooting
| Problem | Fix | | Problem | Fix |
|---------|-----| | ------------------- | --------------------------------------------- |
| Plugins not loading | `:PlugInstall` then `:PlugUpdate` | | Plugins not loading | `:PlugInstall` then `:PlugUpdate` |
| LSP not starting | `:LspInstallServer` for current filetype | | LSP not starting | `:LspInstallServer` for current filetype |
| Colors wrong | `export COLORTERM=truecolor` in shell rc | | Colors wrong | `export COLORTERM=truecolor` in shell rc |
| `Ctrl+s` freezes | `stty -ixon` in shell rc | | `Ctrl+s` freezes | `stty -ixon` in shell rc |
| Everything slow | Large file? Auto-disabled >10MB | | Everything slow | Large file? Auto-disabled >10MB |
| What's installed? | `:ChopsticksStatus` shows tools, LSP, linters |
More in the [wiki](https://github.com/m1ngsama/chopsticks/wiki). More in the [wiki](https://github.com/m1ngsama/chopsticks/wiki).

82
get.sh Normal file → Executable file
View file

@ -4,11 +4,37 @@
# Usage: # 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
# curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --yes # curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --yes
# CHOPSTICKS_DEST=/absolute/path bash get.sh --dry-run
set -eo pipefail set -eo pipefail
REPO="https://github.com/m1ngsama/chopsticks.git" REPO="https://github.com/m1ngsama/chopsticks.git"
DEST="$HOME/.vim" DEST="${CHOPSTICKS_DEST:-$HOME/.vim}"
DRY_RUN=0
INSTALLER_ARGS=()
usage() {
cat <<'EOF'
Usage: get.sh [OPTIONS] [INSTALLER_OPTIONS]
Options:
--dry-run Show what would happen without cloning, pulling, or installing
--help, -h Show this help and exit
Environment:
CHOPSTICKS_DEST Absolute install path (default: ~/.vim)
All other options are passed to install.sh after clone/update.
EOF
}
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=1 ;;
--help|-h) usage; exit 0 ;;
*) INSTALLER_ARGS+=("$arg") ;;
esac
done
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; BOLD='\033[1m'; NC='\033[0m' GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; BOLD='\033[1m'; NC='\033[0m'
@ -19,11 +45,36 @@ ok() { echo -e "${GREEN}[OK]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; } warn() { echo -e "${YELLOW}[!]${NC} $1"; }
die() { echo -e "${RED}[FATAL]${NC} $1" >&2; exit 1; } die() { echo -e "${RED}[FATAL]${NC} $1" >&2; exit 1; }
step() { echo -e "\n${BOLD}==> $1${NC}"; } step() { echo -e "\n${BOLD}==> $1${NC}"; }
info() { echo " $1"; }
case "$DEST" in
/*) ;;
*) die "CHOPSTICKS_DEST must be an absolute path: $DEST" ;;
esac
repo_origin() {
git -C "$DEST" config --get remote.origin.url 2>/dev/null || true
}
is_chopsticks_repo() {
local origin="$1"
origin="${origin%/}"
origin="${origin%.git}"
case "$origin" in
https://github.com/m1ngsama/chopsticks|\
git@github.com:m1ngsama/chopsticks|\
ssh://git@github.com/m1ngsama/chopsticks)
return 0 ;;
*)
return 1 ;;
esac
}
echo -e "${BOLD}chopsticks — One-command installer${NC}" echo -e "${BOLD}chopsticks — One-command installer${NC}"
echo "----------------------------------" echo "----------------------------------"
echo " Repo: $REPO" echo " Repo: $REPO"
echo " Dest: $DEST" echo " Dest: $DEST"
[[ $DRY_RUN -eq 1 ]] && echo " Mode: dry-run"
# ── git ─────────────────────────────────────────────────────────────────────── # ── git ───────────────────────────────────────────────────────────────────────
step "Checking for git" step "Checking for git"
@ -43,15 +94,36 @@ ok "git $(git --version | awk '{print $3}')"
step "Setting up $DEST" step "Setting up $DEST"
if [[ -d "$DEST/.git" ]]; then if [[ -d "$DEST/.git" ]]; then
ORIGIN="$(repo_origin)"
if ! is_chopsticks_repo "$ORIGIN"; then
die "$DEST is a git repo, but it does not look like chopsticks.
origin: ${ORIGIN:-none}
Back it up first: mv \"$DEST\" \"$DEST.bak\"
Then re-run: curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash"
fi
[[ -f "$DEST/install.sh" && -f "$DEST/.vimrc" ]] || \
die "$DEST looks incomplete. Expected install.sh and .vimrc.
Back it up first: mv \"$DEST\" \"$DEST.bak\"
Then re-run: curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash"
if [[ $DRY_RUN -eq 1 ]]; then
info "Would update existing chopsticks repo at $DEST"
info "Would run: bash install.sh ${INSTALLER_ARGS[*]:-(no installer options)}"
exit 0
fi
warn "$DEST already exists — pulling latest changes" warn "$DEST already exists — pulling latest changes"
git -C "$DEST" pull --ff-only origin main 2>/dev/null || \ git -C "$DEST" pull --ff-only origin main 2>/dev/null || \
warn "Could not pull latest — using existing version (run: git -C ~/.vim pull)" warn "Could not pull latest — using existing version (run: git -C ~/.vim pull)"
ok "Repository updated ($(git -C "$DEST" describe --tags 2>/dev/null || git -C "$DEST" rev-parse --short HEAD))" ok "Repository updated ($(git -C "$DEST" describe --tags 2>/dev/null || git -C "$DEST" rev-parse --short HEAD))"
elif [[ -d "$DEST" ]]; then elif [[ -d "$DEST" ]]; then
die "$HOME/.vim exists but is not a chopsticks git repo. die "$DEST exists but is not a chopsticks git repo.
Back it up first: mv ~/.vim ~/.vim.bak Back it up first: mv \"$DEST\" \"$DEST.bak\"
Then re-run: curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash" Then re-run: curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash"
else else
if [[ $DRY_RUN -eq 1 ]]; then
info "Would clone $REPO to $DEST"
info "Would run: bash install.sh ${INSTALLER_ARGS[*]:-(no installer options)}"
exit 0
fi
git clone --depth=1 "$REPO" "$DEST" || \ git clone --depth=1 "$REPO" "$DEST" || \
die "Clone failed — check your network connection" die "Clone failed — check your network connection"
ok "Cloned to $DEST ($(git -C "$DEST" describe --tags 2>/dev/null || git -C "$DEST" rev-parse --short HEAD))" ok "Cloned to $DEST ($(git -C "$DEST" describe --tags 2>/dev/null || git -C "$DEST" rev-parse --short HEAD))"
@ -67,7 +139,7 @@ cd "$DEST"
# Use a test-open to check /dev/tty is actually accessible (it may exist but be # Use a test-open to check /dev/tty is actually accessible (it may exist but be
# unusable in non-interactive SSH sessions or container environments). # unusable in non-interactive SSH sessions or container environments).
if { true </dev/tty; } 2>/dev/null; then if { true </dev/tty; } 2>/dev/null; then
exec bash install.sh "$@" </dev/tty exec bash install.sh "${INSTALLER_ARGS[@]}" </dev/tty
else else
exec bash install.sh "$@" exec bash install.sh "${INSTALLER_ARGS[@]}"
fi fi

View file

@ -1,28 +1,53 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# install.sh - chopsticks vim configuration installer # install.sh - chopsticks vim configuration installer
# Usage: cd /path/to/chopsticks && ./install.sh [--yes] [--help] # Usage: cd /path/to/chopsticks && ./install.sh [--yes] [--profile=engineer] [--help]
# #
# --yes non-interactive: install all optional components automatically # --yes non-interactive: use default profile/component selections
# --profile=PROFILE choose minimal, engineer, or full without prompting
# --configure-only update local chopsticks profile config and exit
# --dry-run show resolved profile/config path without writing files
# --help show this help and exit # --help show this help and exit
set -eo pipefail set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AUTO_YES=0 AUTO_YES=0
REQUESTED_PROFILE=""
CONFIGURE_ONLY=0
DRY_RUN=0
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
--yes) AUTO_YES=1 ;; --yes) AUTO_YES=1 ;;
--profile=*) REQUESTED_PROFILE="${arg#*=}" ;;
--profile)
echo "Use --profile=minimal, --profile=engineer, or --profile=full" >&2
exit 1 ;;
--configure-only) CONFIGURE_ONLY=1 ;;
--dry-run) DRY_RUN=1 ;;
--help|-h) --help|-h)
echo "Usage: ./install.sh [OPTIONS]" echo "Usage: ./install.sh [OPTIONS]"
echo "" echo ""
echo "Options:" echo "Options:"
echo " --yes Non-interactive mode: select all defaults automatically" echo " --yes Non-interactive mode: use default profile/component selections"
echo " --profile=PROFILE"
echo " Select minimal, engineer, or full without prompting"
echo " --configure-only"
echo " Update local profile config and exit; no plugins or tools installed"
echo " --dry-run"
echo " Show resolved profile/config path without writing files"
echo " --help Show this help and exit" echo " --help Show this help and exit"
echo "" echo ""
echo "Supported platforms: macOS (brew), Debian/Ubuntu (apt), Arch (pacman), Fedora (dnf)" echo "Supported platforms: macOS (brew), Debian/Ubuntu (apt), Arch (pacman), Fedora (dnf)"
exit 0 ;; exit 0 ;;
esac esac
done done
case "$REQUESTED_PROFILE" in
""|minimal|engineer|full) ;;
*)
echo "Invalid profile: $REQUESTED_PROFILE" >&2
echo "Use: minimal, engineer, or full" >&2
exit 1 ;;
esac
# ── Colours (respect NO_COLOR and non-TTY) ─────────────────────────────────── # ── Colours (respect NO_COLOR and non-TTY) ───────────────────────────────────
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
@ -51,6 +76,13 @@ info() { echo " $1"; }
INSTALLED=() INSTALLED=()
SKIPPED=() SKIPPED=()
FAILED=() FAILED=()
if [[ -n "${XDG_CONFIG_HOME:-}" && "$XDG_CONFIG_HOME" == /* ]]; then
CONFIG_HOME="$XDG_CONFIG_HOME"
else
CONFIG_HOME="$HOME/.config"
fi
LOCAL_CONFIG="$CONFIG_HOME/chopsticks.vim"
CONFIG_PROFILE="${REQUESTED_PROFILE:-engineer}"
# Ask yes/no; reads from /dev/tty so it works under: curl | bash # Ask yes/no; reads from /dev/tty so it works under: curl | bash
ask() { ask() {
@ -66,16 +98,160 @@ ask() {
[[ "$reply" =~ ^[Yy]$ ]] [[ "$reply" =~ ^[Yy]$ ]]
} }
# ── Error trap ──────────────────────────────────────────────────────────────── profile_from_config() {
[[ -f "$LOCAL_CONFIG" ]] || return 0
sed -nE "s/^[[:space:]]*let[[:space:]]+g:chopsticks_profile[[:space:]]*=[[:space:]]*['\"](minimal|engineer|full)['\"].*/\1/p" \
"$LOCAL_CONFIG" | tail -n1
}
choose_profile() {
local default="${1:-engineer}" reply
while true; do
echo "Choose Vim profile:"
echo " 1) minimal core navigation/editing/git/markdown; no LSP/ALE/completion extras"
echo " 2) engineer default; LSP, ALE, completion, syntax extras"
echo " 3) full engineer plus heavier Markdown lint/spell/conceal/LSP feedback"
if [[ -t 0 ]]; then
read -r -p "Profile [$default]: " reply
elif { true </dev/tty; } 2>/dev/null; then
read -r -p "Profile [$default]: " reply </dev/tty
else
CONFIG_PROFILE="$default"
return
fi
reply="${reply:-$default}"
case "$reply" in
1|minimal) CONFIG_PROFILE="minimal"; return ;;
2|engineer) CONFIG_PROFILE="engineer"; return ;;
3|full) CONFIG_PROFILE="full"; return ;;
*) warn "Choose 1, 2, 3, minimal, engineer, or full." ;;
esac
done
}
write_profile_config() {
local profile="$1" config_dir tmp
config_dir="$(dirname "$LOCAL_CONFIG")"
mkdir -p "$config_dir"
if [[ -f "$LOCAL_CONFIG" ]] && \
grep -Eq '^[[:space:]]*let[[:space:]]+g:chopsticks_profile[[:space:]]*=' "$LOCAL_CONFIG"; then
tmp="$_TMPDIR/chopsticks-local-config"
awk -v profile="$profile" '
/^[[:space:]]*let[[:space:]]+g:chopsticks_profile[[:space:]]*=/ && !done {
printf "let g:chopsticks_profile = \047%s\047\n", profile
done = 1
next
}
{ print }
' "$LOCAL_CONFIG" > "$tmp"
mv "$tmp" "$LOCAL_CONFIG"
else
[[ -s "$LOCAL_CONFIG" ]] && printf '\n' >> "$LOCAL_CONFIG"
{
echo '" chopsticks local preferences'
echo "let g:chopsticks_profile = '$profile'"
} >> "$LOCAL_CONFIG"
fi
}
configure_profile() {
local existing
existing="$(profile_from_config)"
step "Configuring Vim profile"
if [[ $DRY_RUN -eq 1 ]]; then
if [[ -n "$REQUESTED_PROFILE" ]]; then
CONFIG_PROFILE="$REQUESTED_PROFILE"
elif [[ -n "$existing" ]]; then
CONFIG_PROFILE="$existing"
else
CONFIG_PROFILE="engineer"
fi
info "Dry run: no files will be changed"
info "Profile: $CONFIG_PROFILE"
info "Local config: $LOCAL_CONFIG"
return
fi
if [[ -n "$REQUESTED_PROFILE" ]]; then
CONFIG_PROFILE="$REQUESTED_PROFILE"
write_profile_config "$CONFIG_PROFILE"
ok "Profile saved: $CONFIG_PROFILE ($LOCAL_CONFIG)"
return
fi
if [[ -n "$existing" ]]; then
CONFIG_PROFILE="$existing"
ok "Profile: $CONFIG_PROFILE ($LOCAL_CONFIG)"
if [[ $AUTO_YES -eq 1 ]] || ! { true </dev/tty; } 2>/dev/null; then
return
fi
if ask "Change profile now?"; then
choose_profile "$existing"
if [[ "$CONFIG_PROFILE" != "$existing" ]]; then
write_profile_config "$CONFIG_PROFILE"
ok "Profile saved: $CONFIG_PROFILE ($LOCAL_CONFIG)"
else
skip "profile unchanged"
fi
fi
else
if [[ $AUTO_YES -eq 1 ]] || ! { true </dev/tty; } 2>/dev/null; then
CONFIG_PROFILE="engineer"
if [[ $CONFIGURE_ONLY -eq 1 ]]; then
write_profile_config "$CONFIG_PROFILE"
ok "Profile saved: $CONFIG_PROFILE ($LOCAL_CONFIG)"
return
fi
info "Profile: engineer (default; create $LOCAL_CONFIG to change later)"
return
fi
choose_profile engineer
if [[ "$CONFIG_PROFILE" == "engineer" && $CONFIGURE_ONLY -eq 0 ]]; then
info "Profile: engineer (default; no local override written)"
else
write_profile_config "$CONFIG_PROFILE"
ok "Profile saved: $CONFIG_PROFILE ($LOCAL_CONFIG)"
fi
fi
}
# ── Error trap / cleanup ──────────────────────────────────────────────────────
on_error() { on_error() {
echo -e "\n${RED}[FATAL]${NC} Command '${BASH_COMMAND}' failed at line ${BASH_LINENO[0]}." >&2 echo -e "\n${RED}[FATAL]${NC} Command '${BASH_COMMAND}' failed at line ${BASH_LINENO[0]}." >&2
echo " To get a full debug log:" >&2 echo " To get a full debug log:" >&2
echo " ./install.sh 2>&1 | tee /tmp/chopsticks-install.log" >&2 echo " ./install.sh 2>&1 | tee /tmp/chopsticks-install.log" >&2
echo " Report issues: https://github.com/m1ngsama/chopsticks/issues" >&2 echo " Report issues: https://github.com/m1ngsama/chopsticks/issues" >&2
} }
cleanup() {
if [[ ${_MENU_CURSOR_HIDDEN:-0} -eq 1 ]]; then
tput cnorm 2>/dev/null || true
fi
[[ -n "${_TMPDIR:-}" ]] && rm -rf "$_TMPDIR" 2>/dev/null
}
on_interrupt() {
echo "" >&2
die "Interrupted."
}
trap on_error ERR trap on_error ERR
_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/chopsticks-XXXXXX") _TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/chopsticks-XXXXXX")
trap 'rm -rf "$_TMPDIR" 2>/dev/null' EXIT trap cleanup EXIT
trap on_interrupt INT TERM
if [[ $DRY_RUN -eq 1 || $CONFIGURE_ONLY -eq 1 ]]; then
echo -e "${BOLD}chopsticks — Vim Configuration Installer${NC}"
echo "----------------------------------------"
configure_profile
if [[ $DRY_RUN -eq 1 ]]; then
exit 0
fi
echo ""
echo -e "${GREEN}Configuration complete.${NC}"
echo " Profile: $CONFIG_PROFILE"
echo " Local config: $LOCAL_CONFIG"
exit 0
fi
# ── Safe download helper ────────────────────────────────────────────────────── # ── Safe download helper ──────────────────────────────────────────────────────
safe_download() { safe_download() {
@ -183,7 +359,9 @@ _menu_checkbox() {
local _lines=$(( 3 + 2 * _MENU_N )) local _lines=$(( 3 + 2 * _MENU_N ))
local _key _esc _i local _key _esc _i
tput civis 2>/dev/null # hide cursor if tput civis 2>/dev/null; then
_MENU_CURSOR_HIDDEN=1
fi
local _first=1 local _first=1
while true; do while true; do
@ -211,7 +389,10 @@ _menu_checkbox() {
fi fi
done done
tput cnorm 2>/dev/null # restore cursor if [[ ${_MENU_CURSOR_HIDDEN:-0} -eq 1 ]]; then
tput cnorm 2>/dev/null || true
_MENU_CURSOR_HIDDEN=0
fi
echo "" echo ""
MENU_SEL=("${_MENU_SELS[@]}") MENU_SEL=("${_MENU_SELS[@]}")
} }
@ -381,6 +562,8 @@ fi
mkdir -p "$HOME/.vim" mkdir -p "$HOME/.vim"
configure_profile
# ============================================================================ # ============================================================================
# 3. vim-plug + Plugins # 3. vim-plug + Plugins
# ============================================================================ # ============================================================================
@ -423,13 +606,52 @@ _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
verify_plugins() {
local required_file="$_TMPDIR/required-plugins.txt"
local verify_script="$_TMPDIR/required-plugins.vim"
local missing=() dir
cat > "$verify_script" <<'VIMEOF'
if !exists('g:plugs')
cquit
endif
let s:dirs = []
for s:plug in values(g:plugs)
let s:dir = get(s:plug, 'dir', '')
if !empty(s:dir)
call add(s:dirs, fnamemodify(s:dir, ':p'))
endif
endfor
call writefile(s:dirs, $CHOPSTICKS_REQUIRED_PLUGINS)
qa!
VIMEOF
CHOPSTICKS_REQUIRED_PLUGINS="$required_file" \
vim -u "$SCRIPT_DIR/.vimrc" -i NONE -es -N -S "$verify_script" >/dev/null 2>&1 || return 1
[[ -s "$required_file" ]] || return 1
while IFS= read -r dir; do
[[ -z "$dir" ]] && continue
[[ -d "$dir" ]] || missing+=("$dir")
done < "$required_file"
if [[ ${#missing[@]} -gt 0 ]]; then
fail "Plugin installation incomplete — missing:"
for dir in "${missing[@]}"; do echo " ! $dir"; done
return 1
fi
}
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)
_vim_run +'PlugInstall --sync' +qall || true # fzf post-install hook may exit non-zero; harmless _vim_run +'PlugInstall --sync' +qall || true # fzf post-install hook may exit non-zero; harmless
_plug_count=$(ls -1 "$HOME/.vim/plugged" 2>/dev/null | wc -l | tr -d ' ') verify_plugins || die "Plugin installation failed — retry with a stable network connection."
_plug_count=$(find "$HOME/.vim/plugged" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')
if [[ $_plug_count -eq 0 ]]; then if [[ $_plug_count -eq 0 ]]; then
die "Plugin installation failed — ~/.vim/plugged is empty. Check network and retry." die "Plugin installation failed — ~/.vim/plugged is empty. Check network and retry."
fi fi
@ -440,9 +662,14 @@ ok "Plugins installed ($_plug_count)"
# ============================================================================ # ============================================================================
step "Select optional components" step "Select optional components"
info "Vim profile: $CONFIG_PROFILE"
_ITEMS=() _ITEMS=()
_idx=0 _idx=0
_PROFILE_TOOLING=1
[[ $CONFIG_PROFILE == "minimal" ]] && _PROFILE_TOOLING=0
_MARKSMAN_DEFAULT=0
[[ $CONFIG_PROFILE == "full" ]] && _MARKSMAN_DEFAULT=1
# Index map (-1 = not in menu / unavailable) # Index map (-1 = not in menu / unavailable)
_I_RIPGREP=-1; _I_FZF=-1; _I_CTAGS=-1; _I_SHELLCHECK=-1 _I_RIPGREP=-1; _I_FZF=-1; _I_CTAGS=-1; _I_SHELLCHECK=-1
@ -467,42 +694,46 @@ if [[ $HAS_PKG_MGR -eq 1 ]]; then
: $(( _idx++ )) : $(( _idx++ ))
_I_CTAGS=$_idx _I_CTAGS=$_idx
_ITEMS+=("universal-ctags|code symbol index (backing engine for ,rt tag jumps)|1") _ITEMS+=("universal-ctags|Optional symbol index for ,rt tag jumps|0")
: $(( _idx++ )) : $(( _idx++ ))
if [[ $_PROFILE_TOOLING -eq 1 ]]; then
_I_SHELLCHECK=$_idx _I_SHELLCHECK=$_idx
_ITEMS+=("shellcheck|shell script static analysis (ALE integration, on-save)|1") _ITEMS+=("shellcheck|Optional shell script static analysis via ALE|0")
: $(( _idx++ )) : $(( _idx++ ))
_I_HADOLINT=$_idx _I_HADOLINT=$_idx
_ITEMS+=("hadolint|Dockerfile linting (ALE integration, on-save)|1") _ITEMS+=("hadolint|Optional Dockerfile linting via ALE|0")
: $(( _idx++ )) : $(( _idx++ ))
_I_MARKSMAN=$_idx _I_MARKSMAN=$_idx
_ITEMS+=("marksman|Markdown LSP — completion · go-to-definition · live diagnostics|1") _ITEMS+=("marksman|Markdown LSP for full profile or explicit Markdown LSP|$_MARKSMAN_DEFAULT")
: $(( _idx++ )) : $(( _idx++ ))
else
skip "lint/LSP system tools hidden by minimal profile"
fi
else else
warn "No package manager available — system tools skipped" warn "No package manager available — system tools skipped"
fi fi
# ── npm tools ──────────────────────────────────────────────────────────────── # ── npm tools ────────────────────────────────────────────────────────────────
if [[ $HAS_NODE -eq 1 ]]; then if [[ $HAS_NODE -eq 1 && $_PROFILE_TOOLING -eq 1 ]]; then
_I_NPM=$_idx _I_NPM=$_idx
_ITEMS+=("npm formatter suite|prettier / eslint / markdownlint / stylelint / tsc — ALE fix-on-save|1") _ITEMS+=("npm formatter suite|Optional prettier / eslint / markdownlint / stylelint / tsc|0")
: $(( _idx++ )) : $(( _idx++ ))
fi fi
# ── Python tools ───────────────────────────────────────────────────────────── # ── Python tools ─────────────────────────────────────────────────────────────
if [[ $HAS_PIP -eq 1 ]]; then if [[ $HAS_PIP -eq 1 && $_PROFILE_TOOLING -eq 1 ]]; then
_I_PYTHON=$_idx _I_PYTHON=$_idx
_ITEMS+=("Python tool suite|black / isort / flake8 / pylint / yamllint / sqlfluff — ALE fix-on-save|1") _ITEMS+=("Python tool suite|Optional black / isort / flake8 / pylint / yamllint / sqlfluff|0")
: $(( _idx++ )) : $(( _idx++ ))
fi fi
# ── Go tools ───────────────────────────────────────────────────────────────── # ── Go tools ─────────────────────────────────────────────────────────────────
if [[ $HAS_GO -eq 1 ]]; then if [[ $HAS_GO -eq 1 && $_PROFILE_TOOLING -eq 1 ]]; then
_I_GO=$_idx _I_GO=$_idx
_ITEMS+=("Go tool suite|gopls (LSP) / goimports / staticcheck — completion · format · analysis|1") _ITEMS+=("Go tool suite|Optional gopls / goimports / staticcheck|0")
: $(( _idx++ )) : $(( _idx++ ))
fi fi
@ -510,7 +741,7 @@ fi
if command -v tmux >/dev/null 2>&1; then if command -v tmux >/dev/null 2>&1; then
if ! grep -q 'vim-tmux-navigator' "$HOME/.tmux.conf" 2>/dev/null; then if ! grep -q 'vim-tmux-navigator' "$HOME/.tmux.conf" 2>/dev/null; then
_I_TMUX=$_idx _I_TMUX=$_idx
_ITEMS+=("tmux integration|seamless Ctrl+h/j/k/l navigation between vim and tmux panes|1") _ITEMS+=("tmux integration|Optional Ctrl+h/j/k/l navigation between vim and tmux panes|0")
: $(( _idx++ )) : $(( _idx++ ))
else else
ok "tmux integration (vim-tmux-navigator already configured)" ok "tmux integration (vim-tmux-navigator already configured)"
@ -680,6 +911,9 @@ step "npm tools (formatters + linters)"
if [[ $HAS_NODE -eq 0 ]]; then if [[ $HAS_NODE -eq 0 ]]; then
skip "npm tools (Node.js not installed)" skip "npm tools (Node.js not installed)"
SKIPPED+=("prettier" "markdownlint-cli" "stylelint" "eslint" "typescript") SKIPPED+=("prettier" "markdownlint-cli" "stylelint" "eslint" "typescript")
elif [[ $_PROFILE_TOOLING -eq 0 ]]; then
skip "npm tools (minimal profile)"
SKIPPED+=("prettier" "markdownlint-cli" "stylelint" "eslint" "typescript")
elif [[ $_I_NPM -lt 0 ]] || ! _selected "$_I_NPM"; then elif [[ $_I_NPM -lt 0 ]] || ! _selected "$_I_NPM"; then
skip "npm formatter suite (skipped by user)" skip "npm formatter suite (skipped by user)"
SKIPPED+=("prettier" "markdownlint-cli" "stylelint" "eslint" "typescript") SKIPPED+=("prettier" "markdownlint-cli" "stylelint" "eslint" "typescript")
@ -712,6 +946,9 @@ step "Python tools (formatters + linters)"
if [[ $HAS_PIP -eq 0 ]]; then if [[ $HAS_PIP -eq 0 ]]; then
skip "Python tools (pip3 not installed)" skip "Python tools (pip3 not installed)"
SKIPPED+=("black" "isort" "flake8" "pylint" "yamllint" "sqlfluff") SKIPPED+=("black" "isort" "flake8" "pylint" "yamllint" "sqlfluff")
elif [[ $_PROFILE_TOOLING -eq 0 ]]; then
skip "Python tools (minimal profile)"
SKIPPED+=("black" "isort" "flake8" "pylint" "yamllint" "sqlfluff")
elif [[ $_I_PYTHON -lt 0 ]] || ! _selected "$_I_PYTHON"; then elif [[ $_I_PYTHON -lt 0 ]] || ! _selected "$_I_PYTHON"; then
skip "Python tool suite (skipped by user)" skip "Python tool suite (skipped by user)"
SKIPPED+=("black" "isort" "flake8" "pylint" "yamllint" "sqlfluff") SKIPPED+=("black" "isort" "flake8" "pylint" "yamllint" "sqlfluff")
@ -747,6 +984,9 @@ step "Go tools"
if [[ $HAS_GO -eq 0 ]]; then if [[ $HAS_GO -eq 0 ]]; then
skip "Go tools (go not installed — see https://go.dev/dl/)" skip "Go tools (go not installed — see https://go.dev/dl/)"
SKIPPED+=("gopls" "goimports" "staticcheck") SKIPPED+=("gopls" "goimports" "staticcheck")
elif [[ $_PROFILE_TOOLING -eq 0 ]]; then
skip "Go tools (minimal profile)"
SKIPPED+=("gopls" "goimports" "staticcheck")
elif [[ $_I_GO -lt 0 ]] || ! _selected "$_I_GO"; then elif [[ $_I_GO -lt 0 ]] || ! _selected "$_I_GO"; then
skip "Go tool suite (skipped by user)" skip "Go tool suite (skipped by user)"
SKIPPED+=("gopls" "goimports" "staticcheck") SKIPPED+=("gopls" "goimports" "staticcheck")
@ -789,14 +1029,26 @@ elif ! _selected "$_I_TMUX"; then
SKIPPED+=("tmux-navigator-config") SKIPPED+=("tmux-navigator-config")
else else
TMUX_CONF="$HOME/.tmux.conf" TMUX_CONF="$HOME/.tmux.conf"
cat >> "$TMUX_CONF" << 'TMUXEOF' TMUX_BEGIN="# >>> chopsticks vim-tmux-navigator >>>"
TMUX_END="# <<< chopsticks vim-tmux-navigator <<<"
if [[ -f "$TMUX_CONF" ]] && grep -Fq "$TMUX_BEGIN" "$TMUX_CONF"; then
tmp="$_TMPDIR/tmux.conf"
awk -v begin="$TMUX_BEGIN" -v end="$TMUX_END" '
$0 == begin { skip = 1; next }
$0 == end { skip = 0; next }
!skip { print }
' "$TMUX_CONF" > "$tmp"
mv "$tmp" "$TMUX_CONF"
fi
cat >> "$TMUX_CONF" << TMUXEOF
# vim-tmux-navigator: seamless Ctrl+h/j/k/l navigation between vim and tmux $TMUX_BEGIN
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\S+\/)?g?(view|n?vim?x?)(diff)?$'" 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-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-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-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' bind-key -n 'C-l' if-shell "\$is_vim" 'send-keys C-l' 'select-pane -R'
$TMUX_END
TMUXEOF TMUXEOF
ok "vim-tmux-navigator bindings appended to ~/.tmux.conf" ok "vim-tmux-navigator bindings appended to ~/.tmux.conf"
warn "Reload tmux config now: tmux source-file ~/.tmux.conf" warn "Reload tmux config now: tmux source-file ~/.tmux.conf"
@ -828,6 +1080,11 @@ echo -e "${BOLD}=======================================${NC}"
echo -e "${GREEN}Installation complete.${NC}" echo -e "${GREEN}Installation complete.${NC}"
echo -e "${BOLD}=======================================${NC}" echo -e "${BOLD}=======================================${NC}"
echo -e "\n${BOLD}Profile:${NC} $CONFIG_PROFILE"
if [[ -f "$LOCAL_CONFIG" ]]; then
echo " Local config: $LOCAL_CONFIG"
fi
if [[ ${#INSTALLED[@]} -gt 0 ]]; then if [[ ${#INSTALLED[@]} -gt 0 ]]; then
echo -e "\n${GREEN}Installed:${NC}" echo -e "\n${GREEN}Installed:${NC}"
for t in "${INSTALLED[@]}"; do echo " + $t"; done for t in "${INSTALLED[@]}"; do echo " + $t"; done

View file

@ -57,8 +57,14 @@ endif
set display+=lastline set display+=lastline
set ffs=unix,dos,mac set ffs=unix,dos,mac
set nowb set writebackup
set noswapfile
if has('unix')
let s:swap_dir = expand(get(g:, 'chopsticks_swap_dir', '~/.vim/.swap'))
let &directory = s:swap_dir . '//,/tmp//'
silent! call mkdir(s:swap_dir, 'p', 0700)
endif
set swapfile
if has('persistent_undo') if has('persistent_undo')
set undofile set undofile
@ -89,7 +95,7 @@ let mapleader = ","
" ── Basic Keymaps ─────────────────────────────────────────────────────────── " ── Basic Keymaps ───────────────────────────────────────────────────────────
nnoremap <leader>w :w!<cr> nnoremap <leader>w :w<cr>
nnoremap <leader>q :q<cr> nnoremap <leader>q :q<cr>
nnoremap <leader>x :x<cr> nnoremap <leader>x :x<cr>
@ -100,23 +106,10 @@ nnoremap <leader>ba :bufdo bd<cr>
nnoremap <leader>l :bnext<cr> nnoremap <leader>l :bnext<cr>
nnoremap <leader>h :bprevious<cr> nnoremap <leader>h :bprevious<cr>
nnoremap <leader>tn :tabnew<cr>
nnoremap <leader>to :tabonly<cr>
nnoremap <leader>tc :tabclose<cr>
nnoremap <leader>tm :tabmove
nnoremap <leader>t<leader> :tabnext<cr>
let g:lasttab = 1
nnoremap <Leader>tl :exe "tabn ".g:lasttab<CR>
augroup ChopstickTabHistory
autocmd!
autocmd TabLeave * let g:lasttab = tabpagenr()
augroup END
nnoremap <leader>te :tabedit <C-r>=expand("%:p:h")<cr>/
nnoremap <leader>cd :lcd %:p:h<cr>:pwd<cr> nnoremap <leader>cd :lcd %:p:h<cr>:pwd<cr>
nnoremap 0 ^ nnoremap 0 ^
vnoremap 0 ^
nnoremap gV `[v`] nnoremap gV `[v`]
cnoremap <C-p> <Up> cnoremap <C-p> <Up>
@ -127,16 +120,12 @@ nnoremap <M-k> :m .-2<CR>==
vnoremap <M-j> :m '>+1<CR>gv=gv vnoremap <M-j> :m '>+1<CR>gv=gv
vnoremap <M-k> :m '<-2<CR>gv=gv vnoremap <M-k> :m '<-2<CR>gv=gv
nnoremap <leader>ss :setlocal spell!<cr> nnoremap <silent> <leader>ss :setlocal spell!<CR>:echo 'Spell: ' . (&spell ? 'ON' : 'OFF')<CR>
nnoremap <leader>sn ]s
nnoremap <leader>sp [s
nnoremap <leader>sa zg
nnoremap <leader>s? z=
set pastetoggle=<F2> nnoremap <silent> <F2> :set paste!<CR>:echo 'Paste: ' . (&paste ? 'ON' : 'OFF')<CR>
nnoremap <F3> :set invnumber<CR> nnoremap <silent> <F3> :set invnumber<CR>:echo 'Line numbers: ' . (&number ? 'ON' : 'OFF')<CR>
nnoremap <F4> :set invrelativenumber<CR> nnoremap <silent> <F4> :set invrelativenumber<CR>:echo 'Relative numbers: ' . (&relativenumber ? 'ON' : 'OFF')<CR>
nnoremap <F6> :set list!<CR> nnoremap <silent> <F6> :set list!<CR>:echo 'List chars: ' . (&list ? 'ON' : 'OFF')<CR>
nnoremap <space> za nnoremap <space> za
@ -157,14 +146,18 @@ nnoremap <silent> <C-s> :w<CR>
inoremap <silent> <C-s> <C-o>:w<CR> inoremap <silent> <C-s> <C-o>:w<CR>
nnoremap <C-d> <C-d>zz nnoremap <C-d> <C-d>zz
vnoremap <C-d> <C-d>zz
nnoremap <C-u> <C-u>zz nnoremap <C-u> <C-u>zz
vnoremap <C-u> <C-u>zz
if has('clipboard') if has('clipboard')
nnoremap <leader>y "+y nnoremap <leader>y "+y
vnoremap <leader>y "+y vnoremap <leader>y "+y
nnoremap <leader>Y "+Y nnoremap <leader>Y "+Y
nnoremap <leader>p "+p nnoremap <leader>p "+p
vnoremap <leader>p "+p
nnoremap <leader>P "+P nnoremap <leader>P "+P
vnoremap <leader>P "+P
endif endif
nnoremap <leader>qo :copen<CR> nnoremap <leader>qo :copen<CR>
@ -178,22 +171,21 @@ 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
if g:is_tty if g:is_tty
set signcolumn=auto set signcolumn=auto
set synmaxcol=120 set synmaxcol=120
else
if has("patch-8.1.1564")
set signcolumn=number
else
set signcolumn=yes
endif
endif endif
" non-TTY signcolumn is set in ui.vim (=yes, fixed-width to prevent jitter)
" ── Project-Local Config ──────────────────────────────────────────────────── " ── Project-Local Config ────────────────────────────────────────────────────
@ -201,6 +193,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

@ -6,7 +6,7 @@ let g:EasyMotion_do_mapping = 0
let g:EasyMotion_smartcase = 1 let g:EasyMotion_smartcase = 1
if exists('g:plugs["vim-easymotion"]') if exists('g:plugs["vim-easymotion"]')
nmap s <Plug>(easymotion-overwin-f2) nmap <Leader>S <Plug>(easymotion-overwin-f2)
nmap <Leader>j <Plug>(easymotion-j) nmap <Leader>j <Plug>(easymotion-j)
nmap <Leader>k <Plug>(easymotion-k) nmap <Leader>k <Plug>(easymotion-k)
endif endif
@ -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!
@ -44,3 +44,26 @@ augroup ChopstickSearchHL
autocmd! autocmd!
autocmd CursorHold * if get(v:, 'hlsearch', 0) | let v:hlsearch = 0 | endif autocmd CursorHold * if get(v:, 'hlsearch', 0) | let v:hlsearch = 0 | endif
augroup END augroup END
" ── Overlength Highlight (only chars past textwidth, not the whole column) ─
function! s:OverLengthApply() abort
if exists('w:overlength_match')
silent! call matchdelete(w:overlength_match)
unlet w:overlength_match
endif
if &textwidth <= 0 || &buftype !=# '' | return | endif
let w:overlength_match = matchadd('OverLength', '\%>' . &textwidth . 'v.\+', -1)
endfunction
function! s:OverLengthDefineHL() abort
hi default OverLength ctermbg=52 ctermfg=NONE guibg=#3a1f1f guifg=NONE
endfunction
augroup ChopstickOverLength
autocmd!
autocmd ColorScheme * call s:OverLengthDefineHL()
autocmd OptionSet textwidth call s:OverLengthApply()
autocmd BufWinEnter,WinEnter,FileType * call s:OverLengthApply()
augroup END
call s:OverLengthDefineHL()

View file

@ -6,14 +6,48 @@ let g:is_tty = empty($TERM) || $TERM ==# 'dumb' || $TERM =~# 'linux'
\ || $TERM =~# 'screen' || &term =~# 'builtin' \ || $TERM =~# 'screen' || &term =~# 'builtin'
let g:has_true_color = ($COLORTERM ==# 'truecolor' || $COLORTERM ==# '24bit') let g:has_true_color = ($COLORTERM ==# 'truecolor' || $COLORTERM ==# '24bit')
let g:chopsticks_profile = get(g:, 'chopsticks_profile', 'engineer')
if index(['minimal', 'engineer', 'full'], g:chopsticks_profile) < 0
let g:chopsticks_profile = 'engineer'
endif
let s:profile_full = g:chopsticks_profile ==# 'full'
let s:profile_minimal = g:chopsticks_profile ==# 'minimal'
let g:chopsticks_enable_lsp = get(g:, 'chopsticks_enable_lsp',
\ !s:profile_minimal)
let g:chopsticks_enable_lint = get(g:, 'chopsticks_enable_lint',
\ !s:profile_minimal)
let g:chopsticks_enable_extra_languages = get(g:,
\ 'chopsticks_enable_extra_languages', !s:profile_minimal)
let g:chopsticks_enable_ui_extras = get(g:, 'chopsticks_enable_ui_extras',
\ !s:profile_minimal)
let g:chopsticks_enable_markdown_preview = get(g:,
\ 'chopsticks_enable_markdown_preview', !s:profile_minimal)
let g:chopsticks_markdown_lint = get(g:, 'chopsticks_markdown_lint',
\ s:profile_full)
let g:chopsticks_markdown_format_on_save = get(g:,
\ 'chopsticks_markdown_format_on_save', s:profile_full)
let g:chopsticks_markdown_lsp = get(g:, 'chopsticks_markdown_lsp',
\ s:profile_full)
let g:chopsticks_markdown_spell = get(g:, 'chopsticks_markdown_spell',
\ s:profile_full)
let g:chopsticks_markdown_conceal = get(g:, 'chopsticks_markdown_conceal',
\ s:profile_full)
let g:chopsticks_lsp_virtual_text = get(g:, 'chopsticks_lsp_virtual_text',
\ s:profile_full && !g:is_tty)
" Skip built-in plugins we never use " Skip built-in plugins we never use
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

@ -1,4 +1,32 @@
" languages.vim — vim-go config, per-filetype autocmds " languages.vim — vim-go, vim-markdown, per-filetype autocmds
" ── vim-markdown ───────────────────────────────────────────────────────────
let g:vim_markdown_conceal = get(g:, 'vim_markdown_conceal',
\ g:chopsticks_markdown_conceal)
let g:vim_markdown_conceal_code_blocks = 0
let g:vim_markdown_folding_disabled = get(g:, 'vim_markdown_folding_disabled', 1)
let g:vim_markdown_folding_level = 2
let g:vim_markdown_frontmatter = 1
let g:vim_markdown_toml_frontmatter = 1
let g:vim_markdown_json_frontmatter = 1
let g:vim_markdown_follow_anchor = 1
let g:vim_markdown_new_list_item_indent = 2
let g:vim_markdown_strikethrough = 1
if exists('g:plugs["vim-markdown"]')
nnoremap <leader>mt :Toc<CR>
endif
if has('macunix')
let g:previm_open_cmd = '/usr/bin/open'
elseif executable('xdg-open')
let g:previm_open_cmd = 'xdg-open'
endif
let g:previm_enable_realtime = get(g:, 'previm_enable_realtime', 0)
if exists('g:plugs["previm"]')
nnoremap <leader>mp :PrevimOpen<CR>
endif
" ── vim-go (syntax only — vim-lsp handles intelligence) ───────────────────── " ── vim-go (syntax only — vim-lsp handles intelligence) ─────────────────────
@ -13,6 +41,22 @@ let g:go_highlight_function_calls = 1
" ── Filetype Detection ────────────────────────────────────────────────────── " ── Filetype Detection ──────────────────────────────────────────────────────
function! s:MarkdownDefaults() abort
setlocal wrap linebreak textwidth=0 colorcolumn=0 signcolumn=no
let &l:conceallevel = get(g:, 'chopsticks_markdown_conceal', 0) ? 2 : 0
if get(g:, 'chopsticks_markdown_spell', 0)
setlocal spell
else
setlocal nospell
endif
if !get(g:, 'chopsticks_markdown_lint', 0)
\ && !get(g:, 'chopsticks_markdown_format_on_save', 0)
let b:ale_enabled = 0
endif
endfunction
augroup ChopstickFiletype augroup ChopstickFiletype
autocmd! autocmd!
@ -20,23 +64,22 @@ augroup ChopstickFiletype
\ if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif \ if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif
autocmd FileType python autocmd FileType python
\ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=88 colorcolumn=+1 \ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=88
autocmd FileType javascript,typescript autocmd FileType javascript,typescript
\ setlocal expandtab shiftwidth=2 tabstop=2 textwidth=100 colorcolumn=+1 \ setlocal expandtab shiftwidth=2 tabstop=2 textwidth=100
autocmd FileType go autocmd FileType go
\ setlocal noexpandtab shiftwidth=4 tabstop=4 textwidth=120 colorcolumn=+1 \ setlocal noexpandtab shiftwidth=4 tabstop=4 textwidth=120
autocmd FileType rust autocmd FileType rust
\ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=100 colorcolumn=+1 \ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=100
autocmd FileType c,cpp autocmd FileType c,cpp
\ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=80 colorcolumn=+1 \ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=80
autocmd FileType html,css autocmd FileType html,css
\ setlocal expandtab shiftwidth=2 tabstop=2 \ setlocal expandtab shiftwidth=2 tabstop=2
autocmd FileType yaml autocmd FileType yaml
\ setlocal expandtab shiftwidth=2 tabstop=2 \ setlocal expandtab shiftwidth=2 tabstop=2
autocmd FileType markdown autocmd FileType markdown call s:MarkdownDefaults()
\ setlocal wrap linebreak spell textwidth=0 colorcolumn=0 conceallevel=2
autocmd FileType sh autocmd FileType sh
\ setlocal expandtab shiftwidth=2 tabstop=2 textwidth=80 colorcolumn=+1 \ setlocal expandtab shiftwidth=2 tabstop=2 textwidth=80
autocmd FileType make autocmd FileType make
\ setlocal noexpandtab shiftwidth=8 tabstop=8 \ setlocal noexpandtab shiftwidth=8 tabstop=8
autocmd FileType json autocmd FileType json

View file

@ -1,8 +1,12 @@
" lint.vim — ALE async linting and format-on-save " lint.vim — ALE async linting and format-on-save
if !g:chopsticks_enable_lint
finish
endif
let g:ale_disable_lsp = 1 let g:ale_disable_lsp = 1
let g:ale_linters = { let s:ale_linters = {
\ 'python': ['flake8', 'pylint'], \ 'python': ['flake8', 'pylint'],
\ 'javascript': ['eslint'], \ 'javascript': ['eslint'],
\ 'typescript': ['eslint'], \ 'typescript': ['eslint'],
@ -14,11 +18,16 @@ let g:ale_linters = {
\ 'dockerfile': ['hadolint'], \ 'dockerfile': ['hadolint'],
\ 'css': ['stylelint'], \ 'css': ['stylelint'],
\ 'scss': ['stylelint'], \ 'scss': ['stylelint'],
\ 'markdown': ['markdownlint'],
\ 'sql': ['sqlfluff'], \ 'sql': ['sqlfluff'],
\} \}
let g:ale_fixers = { if g:chopsticks_markdown_lint
let s:ale_linters.markdown = ['markdownlint']
endif
let g:ale_linters = s:ale_linters
let s:ale_fixers = {
\ '*': ['remove_trailing_lines', 'trim_whitespace'], \ '*': ['remove_trailing_lines', 'trim_whitespace'],
\ 'python': ['black', 'isort'], \ 'python': ['black', 'isort'],
\ 'javascript': ['prettier', 'eslint'], \ 'javascript': ['prettier', 'eslint'],
@ -32,11 +41,16 @@ let g:ale_fixers = {
\ 'css': ['prettier'], \ 'css': ['prettier'],
\ 'scss': ['prettier'], \ 'scss': ['prettier'],
\ 'less': ['prettier'], \ 'less': ['prettier'],
\ 'markdown': ['prettier'],
\ 'sql': ['sqlfluff'], \ 'sql': ['sqlfluff'],
\} \}
let g:ale_fix_on_save = 1 if g:chopsticks_markdown_format_on_save
let s:ale_fixers.markdown = ['prettier']
endif
let g:ale_fixers = s:ale_fixers
let g:ale_fix_on_save = get(g:, 'ale_fix_on_save', 1)
let g:ale_python_isort_options = '--profile black' let g:ale_python_isort_options = '--profile black'
let g:ale_sign_error = 'X' let g:ale_sign_error = 'X'
let g:ale_sign_warning = '!' let g:ale_sign_warning = '!'
@ -45,9 +59,12 @@ let g:ale_lint_on_insert_leave = 1
let g:ale_lint_on_enter = 1 let g:ale_lint_on_enter = 1
let g:ale_lint_delay = 200 let g:ale_lint_delay = 200
let g:ale_echo_delay = 100 let g:ale_echo_delay = 100
let g:ale_virtualtext_cursor = get(g:, 'ale_virtualtext_cursor', 'disabled')
if exists('g:plugs["ale"]') 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

@ -1,5 +1,9 @@
" lsp.vim — vim-lsp settings, asyncomplete, LSP buffer keymaps " lsp.vim — vim-lsp settings, asyncomplete, LSP buffer keymaps
if !g:chopsticks_enable_lsp
finish
endif
let g:lsp_settings_lazyload = 1 let g:lsp_settings_lazyload = 1
let g:lsp_settings_filetype_python = ['pylsp'] let g:lsp_settings_filetype_python = ['pylsp']
@ -14,10 +18,13 @@ let g:lsp_settings_filetype_css = ['vscode-css-language-server']
let g:lsp_settings_filetype_scss = ['vscode-css-language-server'] let g:lsp_settings_filetype_scss = ['vscode-css-language-server']
let g:lsp_settings_filetype_json = ['vscode-json-language-server'] let g:lsp_settings_filetype_json = ['vscode-json-language-server']
let g:lsp_settings_filetype_yaml = ['yaml-language-server'] let g:lsp_settings_filetype_yaml = ['yaml-language-server']
let g:lsp_settings_filetype_markdown = ['marksman']
let g:lsp_settings_filetype_sql = ['sqls'] let g:lsp_settings_filetype_sql = ['sqls']
let g:lsp_diagnostics_virtual_text_enabled = !g:is_tty if g:chopsticks_markdown_lsp
let g:lsp_settings_filetype_markdown = ['marksman']
endif
let g:lsp_diagnostics_virtual_text_enabled = g:chopsticks_lsp_virtual_text
let g:lsp_diagnostics_virtual_text_delay = 200 let g:lsp_diagnostics_virtual_text_delay = 200
let g:lsp_diagnostics_highlights_enabled = !g:is_tty let g:lsp_diagnostics_highlights_enabled = !g:is_tty
let g:lsp_document_highlight_enabled = !g:is_tty let g:lsp_document_highlight_enabled = !g:is_tty
@ -52,7 +59,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 && &filetype !=# 'markdown'
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

@ -4,13 +4,37 @@
let g:netrw_liststyle = 3 let g:netrw_liststyle = 3
let g:netrw_banner = 0 let g:netrw_banner = 0
let g:netrw_browse_split = 0 let g:netrw_browse_split = 4
let g:netrw_winsize = 25 let g:netrw_winsize = 25
let g:netrw_altv = 1
let g:netrw_list_hide = '\(^\|\s\s\)\zs\.\S\+' let g:netrw_list_hide = '\(^\|\s\s\)\zs\.\S\+'
let g:netrw_list_hide .= ',\.pyc$,node_modules,\.git,__pycache__,\.DS_Store' let g:netrw_list_hide .= ',\.pyc$,node_modules,\.git,__pycache__,\.DS_Store'
nnoremap <leader>e :Explore<CR> function! s:ToggleSidebar(...) abort
nnoremap <leader>E :Vexplore<CR> let l:dir = a:0 ? a:1 : getcwd()
if getbufvar(winbufnr(1), '&filetype') ==# 'netrw' && getwinvar(1, '&winfixwidth')
let l:cur = winnr()
1wincmd w
close
if l:cur > 1
execute (l:cur - 1) . 'wincmd w'
endif
return
endif
execute 'topleft vertical 30new'
execute 'Explore ' . fnameescape(l:dir)
setlocal winfixwidth
setlocal bufhidden=wipe
wincmd p
endfunction
nnoremap <silent> <leader>e :call <SID>ToggleSidebar()<CR>
nnoremap <silent> <leader>E :call <SID>ToggleSidebar(expand('%:p:h'))<CR>
augroup ChopstickNetrw
autocmd!
autocmd FileType netrw setlocal bufhidden=wipe
augroup END
" ── FZF ───────────────────────────────────────────────────────────────────── " ── FZF ─────────────────────────────────────────────────────────────────────
@ -36,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%' }
@ -46,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 ──────────────────────────────────────────────────
@ -78,9 +101,11 @@ function! s:ToggleMaximize() abort
if exists('t:maximize_session') if exists('t:maximize_session')
execute t:maximize_session execute t:maximize_session
unlet t:maximize_session unlet t:maximize_session
echo 'Window: restored'
else else
let t:maximize_session = winrestcmd() let t:maximize_session = winrestcmd()
resize | vertical resize resize | vertical resize
echo 'Window: MAXIMIZED'
endif endif
endfunction endfunction
nnoremap <silent> <leader>z :call <SID>ToggleMaximize()<CR> nnoremap <silent> <leader>z :call <SID>ToggleMaximize()<CR>

View file

@ -24,45 +24,43 @@ Plug 'airblade/vim-gitgutter'
Plug 'tpope/vim-surround' Plug 'tpope/vim-surround'
Plug 'tpope/vim-commentary' Plug 'tpope/vim-commentary'
Plug 'tpope/vim-repeat' Plug 'tpope/vim-repeat'
" tpope/vim-unimpaired removed: 2.5ms startup cost, we define our own
" [q/]q (quickfix), [e/]e (ALE), [x/]x (conflict) — unimpaired's [b/]b
" is covered by ,h/,l. Blank line insertion ([<Space>) added below.
Plug 'tpope/vim-sleuth' Plug 'tpope/vim-sleuth'
Plug 'wellle/targets.vim' Plug 'wellle/targets.vim'
Plug 'jiangmiao/auto-pairs' Plug 'jiangmiao/auto-pairs'
Plug 'easymotion/vim-easymotion', { 'on': '<Plug>(easymotion' } Plug 'easymotion/vim-easymotion', { 'on': '<Plug>(easymotion' }
" ── Linting & Formatting ──────────────────────────────────────────────────── if g:chopsticks_enable_lint
" ── Linting & Formatting ────────────────────────────────────────────────
Plug 'dense-analysis/ale' Plug 'dense-analysis/ale'
endif
" ── LSP + Completion (no Node.js required) ────────────────────────────────── if g:chopsticks_enable_lsp
" ── LSP + Completion ─────────────────────────────────────────────────────
Plug 'prabirshrestha/vim-lsp' Plug 'prabirshrestha/vim-lsp'
Plug 'mattn/vim-lsp-settings' Plug 'mattn/vim-lsp-settings'
Plug 'prabirshrestha/asyncomplete.vim' Plug 'prabirshrestha/asyncomplete.vim'
Plug 'prabirshrestha/asyncomplete-lsp.vim' Plug 'prabirshrestha/asyncomplete-lsp.vim'
" ── Language Syntax ──────────────────────────────────────────────────────────
Plug 'pangloss/vim-javascript', { 'for': ['javascript', 'javascript.jsx'] }
Plug 'HerringtonDarkholme/yats.vim', { 'for': ['typescript', 'typescript.tsx'] }
Plug 'preservim/vim-markdown', { 'for': 'markdown' }
Plug 'fatih/vim-go', { 'for': 'go' }
" ── Markdown Preview & Writing ───────────────────────────────────────────────
Plug 'previm/previm', { 'on': 'PrevimOpen' }
Plug 'junegunn/goyo.vim', { 'on': 'Goyo' }
Plug 'junegunn/limelight.vim', { 'on': ['Limelight', 'Limelight!'] }
" ── UI ───────────────────────────────────────────────────────────────────────
Plug 'mbbill/undotree', { 'on': 'UndotreeToggle' }
Plug 'mhinz/vim-startify'
Plug 'lifepillar/vim-solarized8'
if !g:is_tty
Plug 'Yggdroot/indentLine'
endif endif
" ── Session & Navigation ──────────────────────────────────────────────────── " ── Language Syntax ──────────────────────────────────────────────────────────
Plug 'tpope/vim-obsession' Plug 'preservim/vim-markdown', { 'for': 'markdown' }
if g:chopsticks_enable_markdown_preview
Plug 'previm/previm', { 'on': 'PrevimOpen' }
endif
if g:chopsticks_enable_extra_languages
Plug 'pangloss/vim-javascript', { 'for': ['javascript', 'javascript.jsx'] }
Plug 'HerringtonDarkholme/yats.vim', { 'for': ['typescript', 'typescript.tsx'] }
Plug 'fatih/vim-go', { 'for': 'go' }
endif
" ── UI ───────────────────────────────────────────────────────────────────────
if g:chopsticks_enable_ui_extras
Plug 'mbbill/undotree', { 'on': 'UndotreeToggle' }
Plug 'mhinz/vim-startify'
endif
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

@ -1,11 +1,6 @@
" tools.vim — cheat sheet, run file, sudo save, quickfix, helpers " tools.vim — run file, sudo save, quickfix, helpers
" ── Helper Functions ──────────────────────────────────────────────────────── " ── Buffer Close ───────────────────────────────────────────────────────────
function! HasPaste()
if &paste | return 'PASTE MODE ' | endif
return ''
endfunction
command! Bclose call <SID>BufcloseCloseIt() command! Bclose call <SID>BufcloseCloseIt()
function! <SID>BufcloseCloseIt() function! <SID>BufcloseCloseIt()
@ -24,50 +19,31 @@ function! <SID>BufcloseCloseIt()
endif endif
endfunction endfunction
fun! CleanExtraSpaces() " ── Utilities ──────────────────────────────────────────────────────────────
let save_cursor = getpos(".")
let old_query = getreg('/')
silent! %s/\s\+$//e
call setpos('.', save_cursor)
call setreg('/', old_query)
endfun
function! ToggleNumber()
if(&relativenumber == 1)
set norelativenumber
set number
else
set relativenumber
endif
endfunc
" ── Additional Utilities ────────────────────────────────────────────────────
nnoremap <leader>F gg=G`` nnoremap <leader>F gg=G``
vnoremap <leader>F =
nnoremap <leader>wa :wa<CR> nnoremap <leader>wa :wa<CR>
nnoremap <silent> <Leader>= :exe "resize " . (winheight(0) * 3/2)<CR> nnoremap <silent> <Leader>= :exe "resize " . (winheight(0) * 3/2)<CR>
nnoremap <silent> <Leader>- :exe "resize " . (winheight(0) * 2/3)<CR> nnoremap <silent> <Leader>- :exe "resize " . (winheight(0) * 2/3)<CR>
nnoremap <silent> <Leader>+ :exe "vertical resize " . (winwidth(0) * 3/2)<CR>
nnoremap <silent> <Leader>_ :exe "vertical resize " . (winwidth(0) * 2/3)<CR>
nnoremap <leader><leader> <c-^> nnoremap <leader><leader> <c-^>
nnoremap <leader>W :%s/\s\+$//<CR>:let @/=''<CR> nnoremap <leader>W :%s/\s\+$//<CR>:let @/=''<CR>
vnoremap <leader>W :s/\s\+$//<CR>:let @/=''<CR>gv
nnoremap <leader>so :if &filetype ==# 'vim' <Bar> source % <Bar> echo "Sourced " . expand('%') <Bar> else <Bar> echo "Not a vim file" <Bar> endif<CR>
nnoremap <leader>ev :edit $MYVIMRC<CR> nnoremap <leader>ev :edit $MYVIMRC<CR>
nnoremap <leader>sv :source $MYVIMRC<CR>:echo "vimrc reloaded"<CR> nnoremap <leader>sv :unlet! g:chopsticks_loaded<CR>:execute 'source ' . fnameescape($MYVIMRC)<CR>:echo "vimrc reloaded"<CR>
nnoremap <leader>* :%s/\<<C-r><C-w>\>//g<Left><Left> nnoremap <leader>* :%s/\<<C-r><C-w>\>//g<Left><Left>
vnoremap <leader>* :s///g<Left><Left><Left>
if has('clipboard') if has('clipboard')
nnoremap <leader>cp :let @+ = expand("%:p")<CR>:echo "Copied: " . expand("%:p")<CR> nnoremap <leader>cp :let @+ = expand("%:p")<CR>:echo "Copied: " . expand("%:p")<CR>
nnoremap <leader>cf :let @+ = expand("%:t")<CR>:echo "Copied: " . expand("%:t")<CR> nnoremap <leader>cf :let @+ = expand("%:t")<CR>:echo "Copied: " . expand("%:t")<CR>
endif endif
nnoremap <leader>ms :e ~/buffer.md<cr>
" ── Auto-Create Directories ───────────────────────────────────────────────── " ── Auto-Create Directories ─────────────────────────────────────────────────
function! s:MkNonExDir(file, buf) function! s:MkNonExDir(file, buf)
@ -88,31 +64,43 @@ augroup END
" ── Large File Handling ────────────────────────────────────────────────────── " ── Large File Handling ──────────────────────────────────────────────────────
let g:LargeFile = 1024 * 1024 * 10 let g:LargeFile = get(g:, 'LargeFile', 1024 * 1024 * 10)
let s:tty_large = g:is_tty ? 512000 : g:LargeFile let s:tty_large = g:is_tty ? 512000 : g:LargeFile
function! s:ApplyLargeFileSettings() abort
if get(b:, 'chopsticks_large_file', 0)
setlocal bufhidden=unload undolevels=-1 noswapfile
let b:ale_enabled = 0
if &l:syntax !=# ''
setlocal syntax=
endif
elseif get(b:, 'chopsticks_tty_large_file', 0)
if &l:syntax !=# ''
setlocal syntax=
endif
endif
endfunction
function! s:MarkLargeFile(file) abort
if empty(a:file)
return
endif
let l:fsize = getfsize(a:file)
if l:fsize > g:LargeFile || l:fsize == -2
let b:chopsticks_large_file = 1
elseif g:is_tty && l:fsize > s:tty_large
let b:chopsticks_tty_large_file = 1
endif
call s:ApplyLargeFileSettings()
endfunction
augroup ChopstickLargeFile augroup ChopstickLargeFile
autocmd! autocmd!
autocmd BufReadPre * autocmd BufReadPre * call s:MarkLargeFile(expand('<afile>'))
\ if !empty(expand('<afile>')) | autocmd BufReadPost,FileType,Syntax * call s:ApplyLargeFileSettings()
\ let s:fsize = getfsize(expand('<afile>')) |
\ if s:fsize > g:LargeFile || s:fsize == -2 |
\ setlocal bufhidden=unload undolevels=-1 noswapfile syntax= |
\ let b:ale_enabled = 0 |
\ elseif g:is_tty && s:fsize > s:tty_large |
\ setlocal syntax= |
\ endif |
\ endif
augroup END augroup END
if g:is_tty && !exists("g:tty_message_shown")
augroup TTYMessage
autocmd!
autocmd VimEnter * echom "TTY mode — visual features disabled"
augroup END
let g:tty_message_shown = 1
endif
" ── Run Current File (,cr) ────────────────────────────────────────────────── " ── Run Current File (,cr) ──────────────────────────────────────────────────
function! s:RunFile() abort function! s:RunFile() abort
@ -138,7 +126,7 @@ nnoremap <leader>cr :call <SID>RunFile()<CR>
cnoremap w!! w !sudo tee > /dev/null % cnoremap w!! w !sudo tee > /dev/null %
" ── QuickFix Improvements ─────────────────────────────────────────────────── " ── QuickFix ────────────────────────────────────────────────────────────────
augroup ChopstickQF augroup ChopstickQF
autocmd! autocmd!
@ -149,112 +137,251 @@ augroup END
nnoremap <silent> ]q :cnext<CR> nnoremap <silent> ]q :cnext<CR>
nnoremap <silent> [q :cprev<CR> nnoremap <silent> [q :cprev<CR>
" ── Debug Helpers ─────────────────────────────────────────────────────────── " ── Status Diagnostic (:ChopsticksStatus) ───────────────────────────────────
nnoremap <leader>sh :call <SID>SynStack()<CR> function! s:Check(name, cmd) abort
function! <SID>SynStack() return executable(a:cmd) ? ' OK ' . a:name : ' -- ' . a:name . ' (missing: ' . a:cmd . ')'
if !exists("*synstack") | return | endif endfunction
echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")')
endfunc function! s:Off(name, reason) abort
return ' off ' . a:name . ' (' . a:reason . ')'
endfunction
function! s:LspCheck(ft, server) abort
if !get(g:, 'chopsticks_enable_lsp', 1)
return s:Off(a:ft, 'LSP disabled by profile')
endif
if !exists('*lsp#get_server_names')
return ' -- ' . a:ft . ' (vim-lsp not loaded)'
endif
let l:dir = expand('~/.local/share/vim-lsp-settings/servers/' . a:server)
if isdirectory(l:dir)
return ' OK ' . a:ft . ' (' . a:server . ')'
endif
return ' -- ' . a:ft . ' (:LspInstallServer in a ' . a:ft . ' file)'
endfunction
function! s:ChopsticksStatus() abort
let l:lines = []
call add(l:lines, 'chopsticks status')
call add(l:lines, repeat('─', 50))
call add(l:lines, '')
call add(l:lines, '── system tools ──')
call add(l:lines, s:Check('fzf', 'fzf'))
call add(l:lines, s:Check('ripgrep', 'rg'))
call add(l:lines, s:Check('git', 'git'))
call add(l:lines, s:Check('curl', 'curl'))
call add(l:lines, s:Check('node', 'node'))
call add(l:lines, s:Check('python3', 'python3'))
call add(l:lines, s:Check('go', 'go'))
call add(l:lines, '')
call add(l:lines, '── lsp servers ── (:LspInstallServer to install)')
call add(l:lines, s:LspCheck('python', 'pylsp'))
call add(l:lines, s:LspCheck('go', 'gopls'))
call add(l:lines, s:LspCheck('rust', 'rust-analyzer'))
call add(l:lines, s:LspCheck('typescript', 'typescript-language-server'))
call add(l:lines, s:LspCheck('c/c++', 'clangd'))
call add(l:lines, s:LspCheck('bash', 'bash-language-server'))
call add(l:lines, s:LspCheck('html', 'vscode-html-language-server'))
call add(l:lines, s:LspCheck('json', 'vscode-json-language-server'))
call add(l:lines, s:LspCheck('yaml', 'yaml-language-server'))
call add(l:lines, s:LspCheck('markdown', 'marksman'))
call add(l:lines, s:LspCheck('sql', 'sqls'))
call add(l:lines, '')
call add(l:lines, '── linters ──')
if get(g:, 'chopsticks_enable_lint', 1)
call add(l:lines, s:Check('flake8 (python)', 'flake8'))
call add(l:lines, s:Check('pylint (python)', 'pylint'))
call add(l:lines, s:Check('eslint (js/ts)', 'eslint'))
call add(l:lines, s:Check('staticcheck (go)', 'staticcheck'))
call add(l:lines, s:Check('shellcheck (sh)', 'shellcheck'))
call add(l:lines, s:Check('yamllint (yaml)', 'yamllint'))
call add(l:lines, s:Check('hadolint (docker)', 'hadolint'))
if get(g:, 'chopsticks_markdown_lint', 0)
call add(l:lines, s:Check('markdownlint (md)', 'markdownlint'))
else
call add(l:lines, s:Off('markdownlint (md)', 'disabled by default'))
endif
else
call add(l:lines, s:Off('ALE linters', 'lint disabled by profile'))
endif
call add(l:lines, '')
call add(l:lines, '── formatters ── (format-on-save is ' . (get(g:, 'ale_fix_on_save', 0) ? 'ON' : 'OFF') . ')')
if get(g:, 'chopsticks_enable_lint', 1)
call add(l:lines, s:Check('black (python)', 'black'))
call add(l:lines, s:Check('isort (python)', 'isort'))
call add(l:lines, s:Check('prettier (js/ts/json)', 'prettier'))
if get(g:, 'chopsticks_markdown_format_on_save', 0)
call add(l:lines, s:Check('prettier (md)', 'prettier'))
else
call add(l:lines, s:Off('prettier (md)', 'disabled by default'))
endif
call add(l:lines, s:Check('goimports (go)', 'goimports'))
call add(l:lines, s:Check('rustfmt (rust)', 'rustfmt'))
call add(l:lines, s:Check('clang-format (c)', 'clang-format'))
else
call add(l:lines, s:Off('ALE formatters', 'lint disabled by profile'))
endif
call add(l:lines, '')
let l:ok = len(filter(copy(l:lines), 'v:val =~# " OK "'))
let l:miss = len(filter(copy(l:lines), 'v:val =~# " -- "'))
call add(l:lines, repeat('─', 50))
call add(l:lines, ' ' . l:ok . ' ready, ' . l:miss . ' missing')
call add(l:lines, '')
call add(l:lines, ' Install missing tools with ./install.sh')
if get(g:, 'chopsticks_enable_lsp', 1)
call add(l:lines, ' Install LSP servers with :LspInstallServer')
endif
let l:name = '__ChopsticksStatus__'
if bufwinnr(l:name) > 0
execute bufwinnr(l:name) . 'wincmd w | bd'
endif
execute 'botright new ' . l:name
resize 45
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile
setlocal nowrap nonumber norelativenumber signcolumn=no
call setline(1, l:lines)
setlocal nomodifiable readonly
nnoremap <buffer> <silent> q :bd<CR>
endfunction
command! ChopsticksStatus call s:ChopsticksStatus()
" ── Cheat Sheet (,?) ──────────────────────────────────────────────────────── " ── Cheat Sheet (,?) ────────────────────────────────────────────────────────
function! s:CheatSheet() abort function! s:CheatSheet() abort
let l:name = '__ChopsticksCheatSheet__' let l:name = '__ChopsticksCheatSheet__'
if bufwinnr(l:name) > 0 if bufwinnr(l:name) > 0
execute bufwinnr(l:name) . 'wincmd w' execute bufwinnr(l:name) . 'wincmd w | bd'
return return
endif endif
execute 'botright new ' . l:name
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile let l:has_lsp = get(g:, 'chopsticks_enable_lsp', 1)
call setline(1, [ let l:has_lint = get(g:, 'chopsticks_enable_lint', 1)
\ '=== chopsticks — Quick Reference ===', let l:has_undotree = exists('g:plugs["undotree"]')
let l:has_previm = exists('g:plugs["previm"]')
let l:lines = [
\ ' chopsticks ,? close',
\ ' ─────────────────────────────',
\ '', \ '',
\ 'SURVIVAL', \ ' ── files ──────────────────',
\ ' Esc / jk Exit insert or visual mode', \ ' Ctrl+p find file',
\ ' :q! + Enter Quit without saving', \ ' ,b buffers',
\ ' ,x Save+quit ,w Save Ctrl+s Save (any mode)', \ ' ,rg grep project',
\ ' :w!! Sudo save (when you forgot to open as root)', \ ' ,rG grep word',
\ ' ,e sidebar (cwd)',
\ ' ,E sidebar (file dir)',
\ ' ,, last file',
\ ' ,fh recent files',
\ ' ,fl lines in buffer',
\ ' ,fc commands',
\ ' ,fm marks',
\ '', \ '',
\ 'FILES & SEARCH', \ ' ── code ──────────────────',
\ ' Ctrl+p Fuzzy find file (git-aware)', \ ]
\ ' ,e / ,E File browser / vertical split',
\ ' ,b Search open buffers', if l:has_lsp
\ ' ,rg Search project contents (ripgrep)', call extend(l:lines, [
\ ' ,rG Ripgrep word under cursor', \ ' gd definition',
\ ' ,fh Recent files history', \ ' gy type definition',
\ ' ,fl / ,fL Search lines in buffer / all buffers', \ ' gi implementation',
\ ' ,fc Commands | ,fm Marks', \ ' gr references',
\ ' ,f/ / ,f: Search / command history', \ ' K hover docs',
\ ' ,, Switch to last file (Ctrl+^)', \ ' ,rn rename',
\ '', \ ' ,ca code action',
\ 'CODE INTELLIGENCE (vim-lsp)', \ ' ,f format',
\ ' gd Definition gy Type def gi Impl gr Refs', \ ' ,o outline',
\ ' K Hover documentation', \ ' [g ]g LSP diagnostics',
\ ' [g / ]g Prev / next LSP diagnostic', \ ' :LspInstallServer setup LSP',
\ ' [e / ]e Prev / next ALE error',
\ ' ,ca Code action ,rn Rename ,f Format',
\ ' ,o File outline ,ws Workspace symbols',
\ ' ,cr Run current file',
\ '',
\ 'MARKDOWN & WRITING',
\ ' ,mp Live browser preview (previm)',
\ ' ,mt Table of contents',
\ ' ,zen Zen mode (Goyo + Limelight)',
\ ' zr / zm Unfold / fold all headings',
\ '',
\ 'EDITING',
\ ' gc Toggle comment (visual mode too)',
\ ' s + 2 chars EasyMotion jump anywhere',
\ ' ,u / F5 Undo tree',
\ ' ,y / ,Y Yank to system clipboard',
\ ' Alt+j / Alt+k Move line down / up',
\ ' ,F Re-indent file ,W Strip trailing whitespace',
\ ' ,* Search and replace word under cursor',
\ '',
\ 'GIT',
\ ' ,gs Status ,gd Diff ,gb Blame',
\ ' ,gc Commit ,gp Push ,gl Pull',
\ ' [x / ]x Navigate git conflict markers',
\ '',
\ 'WINDOWS & PANES',
\ ' Ctrl+h/j/k/l Navigate splits and tmux panes',
\ ' ,h / ,l Prev / next buffer ,bd Close buffer',
\ ' ,z Maximize / restore current window',
\ ' ,tv / ,th Terminal (vertical / horizontal)',
\ ' Esc Esc Exit terminal mode',
\ ' ,= / ,- Resize height ,+ / ,_ Resize width',
\ '',
\ 'QUICKFIX',
\ ' ,qo / ,qc Open / close quickfix',
\ ' ]q / [q Next / prev quickfix entry',
\ '',
\ 'UTILITIES',
\ ' ,ev / ,sv Edit / reload ~/.vimrc',
\ ' ,cp / ,cf Copy file path / filename to clipboard',
\ ' ,ms Scratch buffer ,cd CD to file dir',
\ ' ,ss Toggle spell ,so Source current vim file',
\ ' F2 Paste F3 Line# F4 Relative# F6 Invisible',
\ '',
\ '(press q to close)',
\ ]) \ ])
endif
call add(l:lines, ' ,cr run file')
if l:has_previm
call add(l:lines, ' ,mp markdown preview')
endif
call add(l:lines, ' ,mt table of contents')
if l:has_lint
call extend(l:lines, [
\ ' [e ]e ALE errors',
\ ' ,af format on save',
\ ])
endif
call extend(l:lines, [
\ '',
\ ' ── edit ──────────────────',
\ ' gc comment',
\ ' ,S+2ch easymotion jump',
\ ' cs"'' surround',
\ ])
if l:has_undotree
call add(l:lines, ' ,u undo tree')
endif
call extend(l:lines, [
\ ' ,y ,p clipboard y/p (v)',
\ ' Alt+j/k move line (v)',
\ ' ,* replace word (v)',
\ ' ,F re-indent (v)',
\ ' ,W strip trailing (v)',
\ '',
\ ' ── git ───────────────────',
\ ' ,gs status',
\ ' ,gd diff',
\ ' ,gb blame',
\ ' ,gc commit',
\ ' ,gp push',
\ ' ,gl pull',
\ ' ,gL log graph',
\ ' ,gC FZF commits',
\ ' [x ]x conflict markers',
\ '',
\ ' ── windows ───────────────',
\ ' Ctrl+hjkl navigate splits',
\ ' ,h ,l prev / next buf',
\ ' ,bd close buffer',
\ ' ,z maximize toggle',
\ ' ,= ,- resize height',
\ ' ,tv ,th terminal v / h',
\ ' ]q [q next / prev qf',
\ ' ,qo ,qc open / close qf',
\ '',
\ ' ── toggle ────────────────',
\ ' F2 paste mode',
\ ' F3 line numbers',
\ ' F4 relative numbers',
\ ' F6 invisible chars',
\ ' ,ss spell check',
\ '',
\ ' ── survival ──────────────',
\ ' ,w save',
\ ' ,q quit',
\ ' ,x save + quit',
\ ' Ctrl+s save (any mode)',
\ ' jk exit insert',
\ ' :w!! sudo save',
\ ' ,ev edit vimrc',
\ ' ,sv reload vimrc',
\ ' :ChopsticksStatus health',
\ ])
execute 'vertical botright new ' . l:name
vertical resize 42
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile
setlocal nowrap nonumber norelativenumber signcolumn=no
setlocal winfixwidth
call setline(1, l:lines)
setlocal nomodifiable readonly setlocal nomodifiable readonly
nnoremap <buffer> <silent> q :bd<CR> nnoremap <buffer> <silent> q :bd<CR>
nnoremap <buffer> <silent> <leader>? :bd<CR>
endfunction endfunction
nnoremap <silent> <leader>? :call <SID>CheatSheet()<CR> nnoremap <silent> <leader>? :call <SID>CheatSheet()<CR>
" ── Interactive Tutorial ────────────────────────────────────────────────────
function! s:ChopsticksLearn() abort
let l:tutor = g:chopsticks_dir . '/tutor/chopsticks.tutor'
if !filereadable(l:tutor)
echo "Tutorial not found: " . l:tutor
return
endif
execute 'edit ' . fnameescape(l:tutor)
setlocal nomodifiable readonly
setlocal buftype=nofile bufhidden=wipe
setlocal filetype=text
setlocal wrap linebreak
endfunction
command! ChopsticksLearn call s:ChopsticksLearn()

View file

@ -1,4 +1,4 @@
" ui.vim — colorscheme, statusline, startify, indentline " ui.vim — colorscheme, statusline, startify
" ── Colorscheme (Solarized Dark — matches tmux palette) ──────────────────── " ── Colorscheme (Solarized Dark — matches tmux palette) ────────────────────
@ -10,16 +10,49 @@ endif
set background=dark set background=dark
function! s:WarnSolarized8Missing(...) abort
echohl WarningMsg
echom 'chopsticks: solarized8 not installed — run :PlugInstall'
echohl None
endfunction
if !g:is_tty if !g:is_tty
try try
colorscheme solarized8 colorscheme solarized8
catch catch /^Vim\%((\a\+)\)\=:E185/
colorscheme default colorscheme default
if has('timers')
call timer_start(500, function('s:WarnSolarized8Missing'))
else
augroup ChopstickColorschemeWarn
autocmd!
autocmd VimEnter * call s:WarnSolarized8Missing()
augroup END
endif
endtry endtry
else else
colorscheme default colorscheme default
endif endif
" ── Window separators, fillchars, cursorline visibility ────────────────────
if !g:is_tty
set fillchars+=vert:│,eob:\
endif
function! s:UIPolish() abort
hi VertSplit ctermbg=234 ctermfg=240 guibg=#002b36 guifg=#586e75 cterm=NONE gui=NONE
hi CursorLine ctermbg=235 guibg=#0c4452 cterm=NONE gui=NONE
hi CursorLineNr ctermbg=235 ctermfg=136 guibg=#0c4452 guifg=#b58900 cterm=bold gui=bold
hi SignColumn ctermbg=234 guibg=#002b36
endfunction
augroup ChopstickUIPolish
autocmd!
autocmd ColorScheme * call s:UIPolish()
augroup END
if !g:is_tty | call s:UIPolish() | endif
if has("gui_running") if has("gui_running")
if has("gui_gtk2") || has("gui_gtk3") if has("gui_gtk2") || has("gui_gtk3")
set guifont=Hack\ 12,Source\ Code\ Pro\ 12,Monospace\ 12 set guifont=Hack\ 12,Source\ Code\ Pro\ 12,Monospace\ 12
@ -28,49 +61,16 @@ if has("gui_running")
endif endif
endif endif
" ── IndentLine (non-TTY only) ───────────────────────────────────────────────
if !g:is_tty && exists('g:plugs["indentLine"]')
let g:indentLine_char = '|'
let g:indentLine_first_char = '|'
let g:indentLine_showFirstIndentLevel = 1
let g:indentLine_fileTypeExclude = ['text', 'help', 'startify', 'markdown']
let g:indentLine_bufTypeExclude = ['help', 'terminal', 'nofile']
let g:indentLine_setConceal = 2
let g:indentLine_concealcursor = ''
endif
" ── Startify ──────────────────────────────────────────────────────────────── " ── Startify ────────────────────────────────────────────────────────────────
if exists('g:plugs["vim-startify"]') if exists('g:plugs["vim-startify"]')
let g:startify_custom_header = [
\ ' ██████╗██╗ ██╗ ██████╗ ██████╗ ███████╗████████╗██╗ ██████╗██╗ ██╗███████╗',
\ ' ██╔════╝██║ ██║██╔═══██╗██╔══██╗██╔════╝╚══██╔══╝██║██╔════╝██║ ██╔╝██╔════╝',
\ ' ██║ ███████║██║ ██║██████╔╝███████╗ ██║ ██║██║ █████╔╝ ███████╗',
\ ' ██║ ██╔══██║██║ ██║██╔═══╝ ╚════██║ ██║ ██║██║ ██╔═██╗ ╚════██║',
\ ' ╚██████╗██║ ██║╚██████╔╝██║ ███████║ ██║ ██║╚██████╗██║ ██╗███████║',
\ ' ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝',
\ '',
\ ]
let g:startify_lists = [ let g:startify_lists = [
\ { 'type': 'sessions', 'header': [' Sessions'] }, \ { 'type': 'sessions', 'header': [' Sessions'] },
\ { 'type': 'files', 'header': [' Recent Files'] }, \ { 'type': 'files', 'header': [' Recent Files'] },
\ { 'type': 'dir', 'header': [' Current Dir'] }, \ { 'type': 'dir', 'header': [' Current Dir'] },
\ { 'type': 'bookmarks', 'header': [' Bookmarks'] },
\ ] \ ]
let g:startify_bookmarks = [{'v': '~/.vimrc'}] let g:startify_bookmarks = [{'v': '~/.vimrc'}]
if filereadable(expand('~/.zshrc'))
call add(g:startify_bookmarks, {'z': '~/.zshrc'})
endif
if filereadable(expand('~/.bashrc'))
call add(g:startify_bookmarks, {'b': '~/.bashrc'})
endif
if filereadable(expand('~/.config/fish/config.fish'))
call add(g:startify_bookmarks, {'f': '~/.config/fish/config.fish'})
endif
let g:startify_session_persistence = 1 let g:startify_session_persistence = 1
let g:startify_session_autoload = 1 let g:startify_session_autoload = 1
let g:startify_change_to_vcs_root = 1 let g:startify_change_to_vcs_root = 1
@ -79,15 +79,29 @@ if exists('g:plugs["vim-startify"]')
let g:startify_files_number = 8 let g:startify_files_number = 8
let g:startify_padding_left = 4 let g:startify_padding_left = 4
function! s:SetupDirView() abort
if argc() != 1 || !isdirectory(argv()[0]) || exists('s:std_in')
return
endif
let l:dir = fnameescape(argv()[0])
execute 'cd ' . l:dir
vertical rightbelow vnew
if exists(':Startify') == 2
Startify
else
enew
endif
wincmd h
vertical resize 30
setlocal winfixwidth
wincmd l
endfunction
if !g:is_tty if !g:is_tty
augroup ChopstickStartup augroup ChopstickStartup
autocmd! autocmd!
autocmd StdinReadPre * let s:std_in = 1 autocmd StdinReadPre * let s:std_in = 1
autocmd VimEnter * autocmd VimEnter * nested call <SID>SetupDirView()
\ if argc() == 1 && isdirectory(argv()[0]) && !exists('s:std_in') |
\ exe 'cd ' . fnameescape(argv()[0]) |
\ if exists(':Startify') == 2 | Startify | else | enew | endif |
\ endif
augroup END augroup END
endif endif
@ -144,11 +158,19 @@ function! SLAle() abort
return printf(' E:%d W:%d ', l:e, l:w) return printf(' E:%d W:%d ', l:e, l:w)
endfunction endfunction
function! SLFlags() abort
let l:f = ''
if &paste | let l:f .= ' PASTE' | endif
if &spell | let l:f .= ' SPELL' | endif
return empty(l:f) ? '' : l:f . ' '
endfunction
function! SLBuild() abort function! SLBuild() abort
let [l:label, l:hl] = SLMode() let [l:label, l:hl] = SLMode()
let l:s = '%#' . l:hl . '#' . l:label let l:s = '%#' . l:hl . '#' . l:label
let l:s .= '%#SLBody# %f ' let l:s .= '%#SLBody# %f '
let l:s .= '%#SLFlag#%m%r' let l:s .= '%#SLFlag#%m%r'
let l:s .= '%#SLFlag#' . SLFlags()
let l:s .= '%#SLBody#%=' let l:s .= '%#SLBody#%='
let l:s .= '%#SLFlag#' . SLAle() let l:s .= '%#SLFlag#' . SLAle()
let l:s .= '%#SLGit#' . SLGit() let l:s .= '%#SLGit#' . SLGit()
@ -162,3 +184,43 @@ set statusline=%!SLBuild()
if g:is_tty if g:is_tty
set statusline=%f\ %h%w%m%r\ %=%(%l,%c%V\ %=\ %P%) set statusline=%f\ %h%w%m%r\ %=%(%l,%c%V\ %=\ %P%)
endif endif
" ── Tabline (native — shows listed buffers when >1, else hidden) ───────────
function! s:TLDefineColors() abort
hi TabLine ctermbg=234 ctermfg=244 cterm=none guibg=#002b36 guifg=#839496 gui=none
hi TabLineSel ctermbg=235 ctermfg=136 cterm=bold guibg=#073642 guifg=#b58900 gui=bold
hi TabLineFill ctermbg=234 ctermfg=240 cterm=none guibg=#002b36 guifg=#586e75 gui=none
endfunction
augroup TLColors
autocmd!
autocmd ColorScheme * call s:TLDefineColors()
augroup END
call s:TLDefineColors()
function! TLBuild() abort
let l:s = ''
let l:cur = bufnr('%')
for l:b in range(1, bufnr('$'))
if !buflisted(l:b) || getbufvar(l:b, '&buftype') !=# '' | continue | endif
let l:hl = (l:b == l:cur) ? '%#TabLineSel#' : '%#TabLine#'
let l:name = bufname(l:b)
let l:name = empty(l:name) ? '[No Name]' : fnamemodify(l:name, ':t')
let l:mod = getbufvar(l:b, '&modified') ? ' +' : ''
let l:s .= l:hl . ' ' . l:b . ' ' . l:name . l:mod . ' '
endfor
let l:s .= '%#TabLineFill#%='
return l:s
endfunction
if !g:is_tty
set showtabline=1
set tabline=%!TLBuild()
endif
" ── SignColumn: always reserve a column so width never jitters ─────────────
if !g:is_tty
set signcolumn=yes
endif

View file

@ -1,56 +0,0 @@
" writing.vim — vim-markdown, previm, goyo + limelight zen mode
" ── vim-markdown ────────────────────────────────────────────────────────────
let g:vim_markdown_conceal = 1
let g:vim_markdown_conceal_code_blocks = 0
let g:vim_markdown_folding_disabled = 0
let g:vim_markdown_folding_level = 2
let g:vim_markdown_frontmatter = 1
let g:vim_markdown_toml_frontmatter = 1
let g:vim_markdown_json_frontmatter = 1
let g:vim_markdown_follow_anchor = 1
let g:vim_markdown_new_list_item_indent = 2
let g:vim_markdown_strikethrough = 1
if exists('g:plugs["vim-markdown"]')
nnoremap <leader>mt :Toc<CR>
endif
" ── previm (Markdown browser preview) ───────────────────────────────────────
if has('macunix')
let g:previm_open_cmd = '/usr/bin/open'
elseif executable('xdg-open')
let g:previm_open_cmd = 'xdg-open'
endif
if exists('g:plugs["previm"]')
nnoremap <leader>mp :PrevimOpen<CR>
endif
let g:previm_enable_realtime = 1
" ── Goyo + Limelight (zen mode) ────────────────────────────────────────────
if exists('g:plugs["goyo.vim"]')
let g:goyo_width = 80
let g:goyo_height = '85%'
nnoremap <leader>zen :Goyo<CR>
function! s:goyo_enter()
if exists('g:plugs["limelight.vim"]') | Limelight | endif
set wrap linebreak scrolloff=999
endfunction
function! s:goyo_leave()
if exists('g:plugs["limelight.vim"]') | Limelight! | endif
set nowrap nolinebreak scrolloff=10
endfunction
augroup ChopstickGoyo
autocmd!
autocmd User GoyoEnter nested call s:goyo_enter()
autocmd User GoyoLeave nested call s:goyo_leave()
augroup END
endif
let g:limelight_conceal_ctermfg = 240
let g:limelight_conceal_guifg = '#586e75'

251
scripts/test.sh Executable file
View file

@ -0,0 +1,251 @@
#!/usr/bin/env bash
# Project test runner. CI calls the same groups that maintainers can run locally.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/chopsticks-test-XXXXXX")"
EMPTY_XDG="$TMP_ROOT/xdg-empty"
STARTUP_LIMIT_MS="${STARTUP_LIMIT_MS:-150}"
cleanup() {
rm -rf "$TMP_ROOT"
}
trap cleanup EXIT
cd "$ROOT"
mkdir -p "$EMPTY_XDG"
step() {
printf '\n==> %s\n' "$1"
}
need() {
command -v "$1" >/dev/null 2>&1 || {
echo "Missing required command: $1" >&2
exit 1
}
}
usage() {
cat <<'EOF'
Usage: scripts/test.sh [group...]
Groups:
quick shell, docs, installer, and bootstrap checks
shell shell syntax, executability, and shellcheck
docs markdownlint for project docs
installer install.sh dry-run/configure-only profile checks
bootstrap get.sh dry-run safety checks
vim Vim smoke tests; requires plugins in ~/.vim/plugged
all quick plus vim
Options:
-h, --help show this help
list print group names, one per line
EOF
}
list_groups() {
printf '%s\n' quick shell docs installer bootstrap vim all
}
check_shell() {
step "Shell syntax and lint"
need bash
bash -n install.sh
bash -n get.sh
bash -n scripts/test.sh
test -x install.sh
test -x get.sh
test -x scripts/test.sh
need shellcheck
shellcheck install.sh get.sh scripts/test.sh
}
check_docs() {
step "Markdown lint"
need markdownlint
markdownlint README.md QUICKSTART.md CONTRIBUTING.md CHANGELOG.md
}
check_installer_modes() {
step "Installer profile-only modes"
XDG_CONFIG_HOME="$TMP_ROOT/dry" ./install.sh --dry-run --profile=full \
| tee "$TMP_ROOT/install-dry-run.txt"
grep -q 'Profile: full' "$TMP_ROOT/install-dry-run.txt"
test ! -e "$TMP_ROOT/dry/chopsticks.vim"
XDG_CONFIG_HOME="$TMP_ROOT/config" ./install.sh --configure-only --profile=minimal
grep -q "let g:chopsticks_profile = 'minimal'" "$TMP_ROOT/config/chopsticks.vim"
XDG_CONFIG_HOME="$TMP_ROOT/config" ./install.sh --configure-only --profile=full
grep -q "let g:chopsticks_profile = 'full'" "$TMP_ROOT/config/chopsticks.vim"
XDG_CONFIG_HOME="$TMP_ROOT/default" ./install.sh --configure-only --yes
grep -q "let g:chopsticks_profile = 'engineer'" "$TMP_ROOT/default/chopsticks.vim"
}
check_bootstrap() {
step "Bootstrap dry-run safety"
CHOPSTICKS_DEST="$TMP_ROOT/bootstrap" ./get.sh --dry-run --profile=minimal \
| tee "$TMP_ROOT/get-dry-run.txt"
grep -q 'Would clone' "$TMP_ROOT/get-dry-run.txt"
test ! -e "$TMP_ROOT/bootstrap"
mkdir -p "$TMP_ROOT/not-chopsticks"
git -c init.defaultBranch=main init "$TMP_ROOT/not-chopsticks" >/dev/null
git -C "$TMP_ROOT/not-chopsticks" remote add origin https://github.com/example/not-chopsticks.git
if CHOPSTICKS_DEST="$TMP_ROOT/not-chopsticks" ./get.sh --dry-run; then
echo "Expected get.sh to reject non-chopsticks repo" >&2
exit 1
fi
mkdir -p "$TMP_ROOT/chopsticks-existing"
git -c init.defaultBranch=main init "$TMP_ROOT/chopsticks-existing" >/dev/null
git -C "$TMP_ROOT/chopsticks-existing" remote add origin https://github.com/m1ngsama/chopsticks.git
touch "$TMP_ROOT/chopsticks-existing/install.sh" "$TMP_ROOT/chopsticks-existing/.vimrc"
CHOPSTICKS_DEST="$TMP_ROOT/chopsticks-existing" ./get.sh --dry-run --yes \
| tee "$TMP_ROOT/get-existing.txt"
grep -q 'Would update existing chopsticks repo' "$TMP_ROOT/get-existing.txt"
}
check_plugin_dirs() {
step "Plugin directories"
for plugin in \
fzf fzf.vim vim-fugitive vim-gitgutter ale vim-lsp vim-lsp-settings \
asyncomplete.vim asyncomplete-lsp.vim vim-markdown
do
test -d "$HOME/.vim/plugged/$plugin" || {
echo "Missing plugin directory: $plugin" >&2
exit 1
}
done
}
check_vim() {
step "Vim smoke tests"
need vim
check_plugin_dirs
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N -c 'qa!' 2>&1
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
-c "redir! > $TMP_ROOT/plugs.txt" \
-c 'silent echo len(g:plugs)' \
-c 'redir END' \
-c 'qa!' 2>/dev/null
PLUGS="$(tr -d '[:space:]' < "$TMP_ROOT/plugs.txt")"
echo "Plugins registered: $PLUGS"
if [ "$PLUGS" -lt 20 ]; then
echo "Expected 20+ plugins, got $PLUGS" >&2
exit 1
fi
mkdir -p "$TMP_ROOT/chopsticks path/modules"
cp .vimrc "$TMP_ROOT/chopsticks path/.vimrc"
cp modules/*.vim "$TMP_ROOT/chopsticks path/modules/"
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u "$TMP_ROOT/chopsticks path/.vimrc" \
-i NONE -es -N -c 'qa!' 2>&1
vim -u NONE -i NONE -es -N \
-c 'let g:chopsticks_profile = "minimal"' \
-c 'source .vimrc' \
-c 'if has_key(g:plugs, "ale") || has_key(g:plugs, "vim-lsp") || has_key(g:plugs, "vim-lsp-settings") || has_key(g:plugs, "asyncomplete.vim") | cquit | endif' \
-c 'qa!' 2>&1
mkdir -p "$TMP_ROOT/local"
printf "%s\n" "let g:chopsticks_profile = 'minimal'" > "$TMP_ROOT/local/config.vim"
vim -u NONE -i NONE -es -N \
-c "let g:chopsticks_local_config = '$TMP_ROOT/local/config.vim'" \
-c 'source .vimrc' \
-c 'if g:chopsticks_profile !=# "minimal" || has_key(g:plugs, "ale") || has_key(g:plugs, "vim-lsp") | cquit | endif' \
-c 'qa!' 2>&1
mkdir -p "$TMP_ROOT/xdg"
printf "%s\n" "let g:chopsticks_profile = 'minimal'" > "$TMP_ROOT/xdg/chopsticks.vim"
XDG_CONFIG_HOME="$TMP_ROOT/xdg" vim -u NONE -i NONE -es -N \
-c 'source .vimrc' \
-c 'if g:chopsticks_profile !=# "minimal" || has_key(g:plugs, "ale") || has_key(g:plugs, "vim-lsp") | cquit | endif' \
-c 'qa!' 2>&1
vim -u NONE -i NONE -es -N \
-c 'let g:chopsticks_profile = "minimal"' \
-c 'source .vimrc' \
-c 'normal ,?' \
-c "redir! > $TMP_ROOT/cheat.txt" \
-c 'silent %print' \
-c 'redir END' \
-c 'qa!' 2>&1
if grep -Eq 'definition|LspInstallServer|ALE errors|undo tree|markdown preview' "$TMP_ROOT/cheat.txt"; then
cat "$TMP_ROOT/cheat.txt"
exit 1
fi
grep -q ',cr run file' "$TMP_ROOT/cheat.txt"
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N README.md \
-c 'set filetype=markdown' \
-c 'if &l:spell || &l:conceallevel != 0 || &l:signcolumn !=# "no" || exists("g:lsp_settings_filetype_markdown") | cquit | endif' \
-c 'qa!' 2>&1
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
-c 'if maparg("s", "n") !=# "" | cquit | endif' \
-c 'if maparg(",w", "n") =~# "!" | cquit | endif' \
-c 'if !&swapfile || !&writebackup || &directory !~# "\.vim/.swap" | cquit | endif' \
-c 'qa!' 2>&1
vim -u NONE -i NONE -es -N \
-c 'let g:ale_fix_on_save = 0' \
-c 'source .vimrc' \
-c 'if g:ale_fix_on_save != 0 | cquit | endif' \
-c 'qa!' 2>&1
truncate -s 11000000 "$TMP_ROOT/large.py"
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N "$TMP_ROOT/large.py" \
-c 'set filetype=python' \
-c 'if &l:syntax !=# "" || &l:undolevels != -1 || &l:swapfile || get(b:, "ale_enabled", 1) != 0 | cquit | endif' \
-c 'qa!' 2>&1
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE --startuptime "$TMP_ROOT/startup.log" \
-es -N -c 'qa!' 2>/dev/null
tail -1 "$TMP_ROOT/startup.log"
STARTUP_MS="$(awk 'END { print $1 }' "$TMP_ROOT/startup.log")"
awk -v ms="$STARTUP_MS" -v limit="$STARTUP_LIMIT_MS" \
'BEGIN { if (ms > limit) exit 1 }'
}
run_group() {
case "$1" in
quick)
check_shell
check_docs
check_installer_modes
check_bootstrap
;;
shell) check_shell ;;
docs) check_docs ;;
installer) check_installer_modes ;;
bootstrap) check_bootstrap ;;
vim) check_vim ;;
all)
run_group quick
check_vim
;;
list | --list) list_groups ;;
-h | --help) usage ;;
*)
echo "Unknown test group: $1" >&2
echo >&2
usage >&2
exit 1 ;;
esac
}
if [[ $# -eq 0 ]]; then
set -- all
fi
for group in "$@"; do
run_group "$group"
done

View file

@ -1,269 +0,0 @@
================================================================================
= C H O P S T I C K S I N T E R A C T I V E T U T O R =
================================================================================
This tutorial teaches the key features of the chopsticks Vim configuration.
It assumes you already know basic Vim (if not, run :Tutor first).
Leader key is , (comma) throughout this tutorial.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 1: SURVIVAL BASICS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These four commands get you out of any situation:
1. Press Esc or type jk to return to Normal mode
2. Type ,w to save the current file
3. Type ,x to save and quit
4. Type ,q to quit (without saving)
TIP: You can also press Ctrl+s to save from ANY mode (normal, insert,
or visual). Try it now — press i to enter insert mode, then Ctrl+s
to save without leaving insert mode.
When completely lost, press ,? to open the cheat sheet with every
keybinding. Press q to close it.
>>> Try it now: press ,? then read through the sheet, then press q
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 2: FINDING FILES
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
chopsticks uses FZF (fuzzy finder) for fast navigation.
Ctrl+p Fuzzy find files (git-aware — ignores .gitignore'd files)
,b Search open buffers
,fh Recent file history
,rg Search file contents with ripgrep
,rG Search for the word under your cursor
>>> Try it now: press Ctrl+p and start typing a filename
You can also browse files with the built-in file browser:
,e Open netrw file browser in current window
,E Open netrw in a vertical split
Inside netrw:
Enter Open file
- Go up one directory
% Create new file
d Create new directory
gh Toggle hidden files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 3: CODE INTELLIGENCE
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
chopsticks uses vim-lsp for code intelligence — no Node.js required.
First, install a language server for your filetype:
:LspInstallServer (auto-detects the language)
:LspStatus (check if it's running)
Once a server is running, these keys become available:
gd Go to definition
gy Go to type definition
gi Go to implementation
gr List all references
K Show hover documentation
[g / ]g Jump to previous / next diagnostic
,rn Rename symbol under cursor
,ca Code action (auto-fix)
,f Format buffer (or selection in visual mode)
,o File outline (symbols)
>>> Open a source file and try: gd on a function call, then Ctrl+o
to jump back
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 4: EDITING POWER
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These editing features make chopsticks faster than vanilla Vim.
EASYMOTION — jump anywhere on screen:
Press s then type two characters from your target location.
Matching positions light up — press the highlighted letter to jump.
>>> Try it now: press s then type two letters you can see on screen
MOVE LINES — rearrange code without cut/paste:
Alt+j Move current line down
Alt+k Move current line up
(Works in visual mode too — select lines first with V )
SURROUND — change surrounding characters:
cs"' Change surrounding " to '
cs'<div> Change surrounding ' to <div>...</div>
ds( Delete surrounding parentheses
ysiw" Surround word with "
COMMENT — toggle comments:
gc Toggle comment (works in visual mode on selections)
gcc Toggle comment on current line
VISUAL FEEDBACK:
- Yanked text flashes briefly so you know what was copied
- Search highlights auto-clear after you stop moving
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 5: GIT WORKFLOW
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
All git operations through vim-fugitive:
,gs Git status (press s to stage, press cc to commit)
,gd Diff current file (side-by-side split)
,gb Git blame (who changed each line)
,gc Git commit
,gp Git push
,gl Git pull
GitGutter shows changes in the sign column (left margin):
+ Added line
~ Modified line
- Deleted line
Navigate git conflict markers:
]x Jump to next conflict marker
[x Jump to previous conflict marker
>>> Try ,gs in a git repository to see the status view
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 6: MARKDOWN & WRITING
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Markdown files get special treatment automatically:
- Word wrap enabled
- Spell checking enabled
- Syntax concealment (bold renders as bold, headings hide # markers)
- Folding by heading level
Key bindings for markdown:
,mp Open live browser preview (auto-refreshes as you type)
,mt Table of contents in a side window
,zen Enter zen mode — Goyo + Limelight
(distraction-free writing, only current paragraph highlighted)
zr Unfold all headings
zm Fold all headings
>>> Open a .md file and try ,zen to enter zen mode.
Press ,zen again to leave.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 7: WINDOW MANAGEMENT
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Navigate between splits (works with tmux panes too):
Ctrl+h Move to left split
Ctrl+j Move to split below
Ctrl+k Move to split above
Ctrl+l Move to right split
Buffer navigation:
,h Previous buffer
,l Next buffer
,bd Close buffer (keeps window layout)
,, Switch to last file (toggle between two files)
Window tricks:
,z Maximize current window (press again to restore)
,= / ,- Resize window height (bigger / smaller)
,+ / ,_ Resize window width (bigger / smaller)
Terminal:
,tv Open terminal (vertical split)
,th Open terminal (horizontal, 10 rows)
Esc Esc Exit terminal mode back to normal
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 8: LINTING & FORMATTING
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ALE runs linters and formatters automatically:
- Linting happens as you type (normal mode) and on save
- Format-on-save is enabled by default (black, prettier, gofmt, etc.)
Navigate errors:
[e / ]e Previous / next ALE error
,aD Show error detail in a popup
Supported out of the box:
Python: flake8 + pylint (lint), black + isort (format)
JS/TS: eslint (lint), prettier + eslint (format)
Go: staticcheck (lint), goimports (format)
Rust: cargo (lint), rustfmt (format)
Shell: shellcheck (lint)
And more: yaml, dockerfile, css, markdown, sql
>>> Write some intentionally bad code and watch the sign column
light up with X (error) and ! (warning) markers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 9: RUNNING CODE
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Press ,cr to run the current file. It auto-detects the language:
Python → python3 file.py
JavaScript → node file.js
TypeScript → npx ts-node file.ts
Go → go run file.go
Rust → cargo run
Shell → bash file.sh
C → gcc + run
Lua, Ruby, Perl also supported
>>> Create a test file (e.g. test.py with: print("hello"))
and press ,cr to run it
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 10: DAILY TIPS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SEARCH AND REPLACE:
,* Replace word under cursor throughout the file
// In visual mode, search for selected text
USEFUL SHORTCUTS:
Y Yank to end of line (consistent with D and C)
,y / ,Y Yank to system clipboard
,F Re-indent entire file
,W Strip all trailing whitespace
:w!! Sudo save (when you forgot to open as root)
,ev Edit your vimrc
,sv Reload your vimrc
SESSIONS:
:Obsess Start recording session (auto-restores next time)
:Obsess! Stop recording
The startup screen (Startify) shows recent files, sessions, and bookmarks.
>>> Press ,? one more time to review the full cheat sheet.
You now know the essential chopsticks features. Happy editing!
================================================================================