diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6b9731b..487deef 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,4 +10,4 @@ - [ ] `vim --startuptime` shows no regression - [ ] Tested on macOS / Linux -- [ ] `,?` cheat sheet still accurate +- [ ] `SPC ?` cheat sheet still accurate diff --git a/.github/demo.tape b/.github/demo.tape index 13bd710..74341d2 100644 --- a/.github/demo.tape +++ b/.github/demo.tape @@ -24,16 +24,16 @@ Type "vim server.py" Enter Sleep 3.5s -# ── 2. Fuzzy find files (,ff → type → select) ──────────────────────────── -Type ",ff" +# ── 2. Fuzzy find files (SPC SPC → type → select) ──────────────────────── +Type " " Sleep 1.5s Type "route" Sleep 2.5s Enter Sleep 3s -# ── 3. Ripgrep project search (,rg → query → select) ────────────────────── -Type ",rg" +# ── 3. Ripgrep project search (SPC / → query → select) ─────────────────── +Type " /" Sleep 1.5s Type "def " Sleep 3s @@ -47,12 +47,12 @@ Sleep 4.5s Enter Sleep 1.5s -# ── 5. Cheat sheet (,?) ─────────────────────────────────────────────────── +# ── 5. Cheat sheet (SPC ?) ──────────────────────────────────────────────── # Reset to server.py so cheat sheet shows code on left, keys on right. Type ":edit server.py" Enter Sleep 1s -Type ",?" +Type " ?" Sleep 5.5s Type "q" Sleep 0.5s diff --git a/.vimrc b/.vimrc index 535c074..b4dbd67 100644 --- a/.vimrc +++ b/.vimrc @@ -27,4 +27,12 @@ call s:load('lsp') call s:load('lint') call s:load('git') call s:load('languages') +call s:load('buffers') +call s:load('utilities') +call s:load('files') +call s:load('runner') +call s:load('quickfix') +call s:load('status') +call s:load('cheatsheet') +call s:load('tutor') call s:load('tools') diff --git a/CHANGELOG.md b/CHANGELOG.md index 73c53b5..b2b85cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,57 @@ ## Unreleased +## 3.0.0 — 2026-05-21 + +### Breaking + +- Default keymap style is now `space`: `SPC` is the command leader and `,` + is reserved for filetype-local actions. Set + `let g:chopsticks_keymap_style = 'classic'` in + `${XDG_CONFIG_HOME:-~/.config}/chopsticks.vim` to keep the old comma layout +- In the default Space layout, Normal-mode `s` is now the fastest + EasyMotion over-window two-character jump. Use `cl` for native `s` + substitute and `cc` for native `S` substitute +- Git push and pull hotkeys are removed from both Space and classic layouts; + use `:Git push` / `:Git pull` explicitly for irreversible remote operations + +### Added + +- Canonical QWERTY/CapsLock-friendly Space leader layout: + `SPC SPC`, `SPC /`, `SPC ,`, `SPC w`, `SPC qx`, `SPC rr`, + `SPC gs`, `SPC gl`, `SPC ca`, `SPC cr`, `SPC cf`, and `SPC ?` +- Native LSP motions in the default layout: `gd`, `gr`, `gI`, `gy`, `K`, + `[d`, and `]d` +- `:ChopsticksTutor` guided practice buffer for learning the final keymap +- `:ChopsticksCheatSheet` command, with `SPC ?` as the discoverable default +- Dedicated modules for buffers, utilities, files, runner, quickfix, status, + cheat sheet, and tutor +- Split test runner: `scripts/test.sh` now dispatches to + `scripts/test-quick.sh` and `scripts/test-vim.sh` + +### Changed + +- README, QUICKSTART, installer onboarding text, PR template, and demo tape now + teach the Space layout first while keeping the legacy classic layout documented +- `SPC ?` / `,?` cheat sheet output is generated from the active keymap style + and profile, so minimal installs no longer display disabled features +- Markdown actions now use localleader maps in the Space layout: + `,mp` preview and `,mt` table of contents +- `SPC U` opens UndoTree, `SPC z` toggles maximize, `SPC bp` / `SPC bn` + move between buffers, and quickfix/location-list actions live under `SPC x` +- `tools.vim` is now a compatibility placeholder; runtime behavior lives in + smaller focused modules +- CI/local smoke coverage now asserts Space defaults, classic compatibility, + missing push/pull hotkeys, tutor and cheat-sheet content, runner behavior, + large-file protection, and startup budget + +### Fixed + +- `diffopt` enhancements now degrade safely on macOS system Vim builds that + report the required patch level but reject individual diff options +- Installer system-tool reporting now still detects already-installed tools on + Linux hosts where sudo is unavailable in non-interactive mode + ## 2.2.0 — 2026-05-17 ### Added diff --git a/QUICKSTART.md b/QUICKSTART.md index 5d014d6..1162cf8 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -18,6 +18,14 @@ 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. +The default keymap style is `space`: `SPC` is the command leader and `,` is +reserved for filetype-local actions. To use the legacy comma layout instead, +add this to `${XDG_CONFIG_HOME:-~/.config}/chopsticks.vim`: + +```vim +let g:chopsticks_keymap_style = 'classic' +``` + To switch later without reinstalling anything: ```bash @@ -34,6 +42,16 @@ cd ~/.vim && ./install.sh --configure-only --profile=full ## Survival +``` +Esc back to Normal +SPC w save +SPC qx save + quit +:q! force quit +SPC ? cheat sheet (toggle sidebar) +``` + +Classic layout equivalents: + ``` Esc back to Normal ,w save @@ -45,23 +63,23 @@ Esc back to Normal ## Find things ``` -,ff fuzzy find file (git-aware) -,rg ripgrep project -,b search buffers -,fh recent files -,e file browser -,, last file +SPC SPC fuzzy find file (git-aware) +SPC / ripgrep project +SPC , search buffers +SPC fr recent files +SPC e file browser +SPC Tab last file ``` ## Write code ``` -,dd go to definition -,dk hover docs -,rn rename symbol -,ca code action -,f format -,cr run current file +gd go to definition +K hover docs +SPC cr rename symbol +SPC ca code action +SPC cf format +SPC rr run current file Tab / S-Tab cycle completions ``` @@ -70,31 +88,37 @@ Tab / S-Tab cycle completions ## Git ``` -,gs status (s=stage, cc=commit) -,gd diff -,gb blame -,gp push +SPC gs status (s=stage, cc=commit) +SPC gd diff +SPC gb blame +SPC gl log graph ]x / [x conflict markers ``` ## Edit +In the default Space layout, Normal-mode `s` is a fast visible-text jump. +Use `cl` when you want Vim's original single-character substitute behavior, +and `cc` when you want Vim's original line substitute behavior. + ``` -,S + 2 chars EasyMotion jump +s + 2 chars EasyMotion jump +SPC S + 2 chars same jump, discoverable fallback +cl / cc native s / S substitute replacements gc toggle comment cs"' change surrounding " to ' Alt+j / Alt+k move line -,u undo tree -,y clipboard yank +SPC U undo tree +SPC y clipboard yank ``` ## Navigate ``` h/j/k/l splits -,h / ,l prev / next buffer -,z maximize window -,tv / ,th terminal +SPC bp / SPC bn prev / next buffer +SPC z maximize window +SPC tt / SPC th terminal ``` ## Markdown @@ -110,10 +134,11 @@ syntax. Enable the heavier Markdown tools only when you want them. ## Health check ``` +:ChopsticksTutor guided practice for the final keymap :ChopsticksStatus see what's installed and what's missing ``` -The `,?` cheat sheet follows your active profile, so `minimal` users only see +The `SPC ?` 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. diff --git a/README.md b/README.md index 9529779..41e5da4 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ 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. -**24–25 plugins** (tmux-navigator loads only inside tmux), LSP, linting, and a hand-built statusline. No bloat, no decorations, just tools. +**23–25 plugins** (tmux-navigator loads only inside tmux; auto-pairs is opt-in), LSP, linting, and a hand-built statusline. No bloat, no decorations, just tools. ## What's in the box @@ -39,8 +39,8 @@ chopsticks gives you a production-ready Vim config in one command. Pure VimScrip | **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, goimports, rustfmt on save | | **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) | -| **Run file** | `,cr` — auto-detects Python, Go, Rust, JS, C, Shell, and more | +| **Git** | status, diff, blame, commit, log, conflict markers — [fugitive](https://github.com/tpope/vim-fugitive) + [gitgutter](https://github.com/airblade/vim-gitgutter) | +| **Run file** | `SPC rr` — 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 | @@ -81,6 +81,8 @@ profile or uses `engineer`. 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 +let g:chopsticks_keymap_style = 'space' " default: Space leader grouped layout +let g:chopsticks_keymap_style = 'classic' " optional: legacy comma leader layout let g:chopsticks_enable_jk_escape = 1 " optional: insert-mode jk exits insert let g:chopsticks_enable_ctrl_s_save = 1 " optional: Ctrl-S saves let g:chopsticks_enable_sudo_save_bang = 1 " optional: :w!! sudo save @@ -96,44 +98,63 @@ 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. +there instead of editing the managed `.vimrc`. The `SPC ?` cheat sheet follows +the active profile and only shows keys for enabled features. ## Keys -Leader: `,` +Default layout: `space`, leader `SPC`, localleader `,`. + +This is the canonical layout for QWERTY keyboards with CapsLock mapped to +tap-Esc / hold-Ctrl. Escape and Ctrl stay at the system layer; Vim keeps the +native `` window model and standard LSP motions (`gd`, `gr`, `K`). +Git push/pull are intentionally not bound to default hotkeys. Normal-mode `s` +is a screen-local EasyMotion jump; use `cl` for native `s` substitute and `cc` +for native `S`. + +For onboarding, use `:ChopsticksTutor` for a guided practice page, `SPC ?` for +the active keymap, and `:ChopsticksStatus` for tool/LSP health. +`QUICKSTART.md` is the 5-minute path; this README is the full reference. ``` -,ff fuzzy find file ,dd go to definition -,rg ripgrep project ,dk hover docs -,e toggle file sidebar ,cr run current file -,gs git status ,f format -,w save ,q quit -Esc exit insert mode ,? cheat sheet +SPC SPC fuzzy find file gd go to definition +SPC / ripgrep project K hover docs +SPC e toggle file sidebar SPC rr run current file +SPC gs git status SPC cf format +SPC w save SPC qq quit +Esc exit insert mode SPC ? cheat sheet ```
-All keybindings +Canonical Space keybindings + +### Fast Path + +`SPC SPC` files | `SPC ,` buffers | `SPC /` grep | `SPC Tab` alternate buffer | `SPC e` browser | `SPC E` browser (file dir) ### Files -`,ff` find | `,b` buffers | `,rg` grep | `,rG` grep word | `,fh` recent | `,fl` lines | `,e` browser | `,E` browser (file dir) | `,,` last file +`SPC ff` files | `SPC fb` buffers | `SPC fg` git files | `SPC fr` recent | `SPC fl` buffer lines | `SPC fL` all lines | `SPC fv` edit vimrc | `SPC fV` reload vimrc + +### Search + +`SPC sg` grep | `SPC sw` grep word | `SPC s/` search history | `SPC s:` command history | `SPC sm` marks | `SPC st` tags | `SPC sr` replace word ### Code -`,dd` def | `,dt` type | `,di` impl | `,dr` refs | `,dk` docs | `,dp` `,dn` diagnostics | `[e` `]e` ALE errors | `,rn` rename | `,ca` action | `,o` outline | `,cr` run +`gd` def | `gr` refs | `gI` impl | `gy` type | `K` docs | `[d` `]d` LSP diagnostics | `[e` `]e` ALE errors | `SPC ca` action | `SPC cr` rename | `SPC cf` format | `SPC co` outline | `SPC ci` LSP status | `SPC rr` run ### Edit -`,S`+2ch jump | `gc` comment | `cs"'` surround | `Alt+j/k` move line | `,u` undo tree | `,y` clipboard | `,*` replace word | `,F` re-indent (v) | `,W` strip whitespace | `[` `]` blank lines +`s`+2ch jump | `SPC S` jump fallback | `cl` native `s` substitute | `cc` native `S` substitute | `gc` comment | `cs"'` surround | `Alt+j/k` move line | `SPC U` undo tree | `SPC y` clipboard | `SPC =` re-indent visual | `SPC cW` strip whitespace | `[` `]` blank lines ### Git -`,gs` status | `,gd` diff | `,gb` blame | `,gc` commit | `,gp` push | `,gl` pull | `,gL` log graph | `,gC` FZF commits | `,gB` buffer commits | `]x` `[x` conflict +`SPC gs` status | `SPC gd` diff | `SPC gb` blame | `SPC gc` commit | `SPC gl` log graph | `SPC gC` FZF commits | `SPC gB` buffer commits | `]x` `[x` conflict ### Windows -`hjkl` navigate | `,z` maximize | `,h` `,l` buffers | `,bd` close buffer | `,=` `,−` resize | `,tv` `,th` terminal +`hjkl` navigate | `SPC z` maximize | `SPC bp` `SPC bn` buffers | `SPC bd` close buffer | `SPC bo` close other buffers | `SPC tt` `SPC th` terminal | `]q` `[q` quickfix | `SPC xq` `SPC xQ` open/close quickfix | `SPC xl` `SPC xL` open/close loclist ### Markdown @@ -141,6 +162,43 @@ Esc exit insert mode ,? cheat sheet ### Toggle +`F2` paste | `F3` line numbers | `F4` relative numbers | `F6` invisible chars | `SPC us` spell check | `SPC uf` format on save + +### Survival + +`SPC w` save | `SPC W` save all | `SPC qq` quit | `SPC qx` save and quit | `SPC ?` cheat sheet | `:ChopsticksTutor` practice | `:ChopsticksStatus` diagnostics + +
+ +
+Legacy classic keybindings + +### Classic Files + +`,ff` find | `,b` buffers | `,rg` grep | `,rG` grep word | `,fh` recent | `,fl` lines | `,e` browser | `,E` browser (file dir) | `,,` last file + +### Classic Code + +`,dd` def | `,dt` type | `,di` impl | `,dr` refs | `,dk` docs | `,dp` `,dn` diagnostics | `[e` `]e` ALE errors | `,rn` rename | `,ca` action | `,o` outline | `,cr` run + +### Classic Edit + +`,S`+2ch jump | `gc` comment | `cs"'` surround | `Alt+j/k` move line | `,u` undo tree | `,y` clipboard | `,*` replace word | `,F` re-indent (v) | `,W` strip whitespace | `[` `]` blank lines + +### Classic Git + +`,gs` status | `,gd` diff | `,gb` blame | `,gc` commit | `,gL` log graph | `,gC` FZF commits | `,gB` buffer commits | `]x` `[x` conflict + +### Classic Windows + +`hjkl` navigate | `,z` maximize | `,h` `,l` buffers | `,bd` close buffer | `,=` `,−` resize | `,tv` `,th` terminal + +### Classic Markdown + +`,mp` preview in browser | `,mt` table of contents + +### Classic Toggle + `F2` paste | `F3` line numbers | `F4` relative numbers | `F6` invisible chars | `,ss` spell check | `,af` format on save ### Utilities @@ -194,7 +252,7 @@ For Markdown LSP, install or select `marksman` first. ├── .vimrc thin loader ├── modules/ │ ├── env.vim TTY detection, truecolor, skip built-in plugins -│ ├── plugins.vim vim-plug + 24–25 plugins +│ ├── plugins.vim vim-plug + 23–25 plugins │ ├── core.vim settings, keymaps, performance │ ├── ui.vim solarized, statusline, startify │ ├── editing.vim easymotion, yank highlight, blank lines @@ -203,7 +261,15 @@ For Markdown LSP, install or select `marksman` first. │ ├── lint.vim ale, format-on-save │ ├── git.vim fugitive, gitgutter, conflict nav │ ├── languages.vim vim-go, markdown, filetype settings -│ └── tools.vim run file, quickfix, cheat sheet, diagnostics +│ ├── buffers.vim buffer commands +│ ├── utilities.vim reindent, trim, clipboard, vimrc helpers +│ ├── files.vim auto mkdir, large-file protection +│ ├── runner.vim run current file +│ ├── quickfix.vim quickfix and location-list helpers +│ ├── status.vim :ChopsticksStatus diagnostics +│ ├── cheatsheet.vim SPC ? and :ChopsticksCheatSheet +│ ├── tutor.vim :ChopsticksTutor guided practice +│ └── tools.vim compatibility placeholder ``` Each module is self-contained. Comment out one line in `.vimrc` to disable it. Add your own with `call s:load('mine')`. diff --git a/install.sh b/install.sh index 38da9a4..8cfd327 100755 --- a/install.sh +++ b/install.sh @@ -741,22 +741,22 @@ _I_NPM=-1; _I_PYTHON=-1; _I_GO=-1; _I_TMUX=-1 # Is any package manager available? HAS_PKG_MGR=0 if [[ $HAS_BREW -eq 1 ]] || \ - { [[ $HAS_APT -eq 1 || $HAS_PACMAN -eq 1 || $HAS_DNF -eq 1 ]] && [[ $HAS_SUDO -eq 1 ]]; }; then + [[ $HAS_APT -eq 1 || $HAS_PACMAN -eq 1 || $HAS_DNF -eq 1 ]]; then HAS_PKG_MGR=1 fi # ── System tools ───────────────────────────────────────────────────────────── if [[ $HAS_PKG_MGR -eq 1 ]]; then _I_RIPGREP=$_idx - _ITEMS+=("ripgrep|,rg / ,rG project-wide search · powers FZF preview|1") + _ITEMS+=("ripgrep|SPC / project search · SPC sw word search · powers FZF preview|1") : $(( _idx++ )) _I_FZF=$_idx - _ITEMS+=("fzf|,ff fuzzy file search · ,b buffers · ,rt tag search|1") + _ITEMS+=("fzf|SPC SPC files · SPC , buffers · SPC st tag search|1") : $(( _idx++ )) _I_CTAGS=$_idx - _ITEMS+=("universal-ctags|Optional symbol index for ,rt tag jumps|0") + _ITEMS+=("universal-ctags|Optional symbol index for SPC st tag jumps|0") : $(( _idx++ )) if [[ $_PROFILE_TOOLING -eq 1 ]]; then @@ -848,6 +848,11 @@ _do_sys() { if command -v "$check" >/dev/null 2>&1; then ok "$name (already installed)"; return fi + if [[ $OS != "macos" && $HAS_SUDO -ne 1 ]]; then + skip "$name — sudo not available, install manually" + SKIPPED+=("$name") + return + fi if pkg_install "$brew_p" "$apt_p" "$pac_p" "$dnf_p"; then ok "$name"; INSTALLED+=("$name") else @@ -1201,9 +1206,9 @@ echo -e " ${CYAN}vim .${NC} Open dashboard in current directory" echo -e " ${CYAN}vim myfile${NC} Edit a specific file" echo "" echo -e "${BOLD} First steps inside Vim${NC}" -echo -e " ${CYAN},?${NC} Open cheat sheet — your map of every keybinding" +echo -e " ${CYAN}SPC ?${NC} Open cheat sheet — your map of every keybinding" echo -e " ${CYAN}Esc${NC} Exit insert mode → back to Normal" -echo -e " ${CYAN},x${NC} Save and quit" +echo -e " ${CYAN}SPC qx${NC} Save and quit" echo -e " ${CYAN}:q!${NC} + Enter Emergency quit without saving" if [[ $CONFIG_PROFILE != "minimal" ]]; then echo -e " ${CYAN}:LspInstallServer${NC} Install LSP for current filetype" diff --git a/modules/buffers.vim b/modules/buffers.vim new file mode 100644 index 0000000..9f43f9e --- /dev/null +++ b/modules/buffers.vim @@ -0,0 +1,18 @@ +" buffers.vim — buffer commands + +command! Bclose call BufcloseCloseIt() +function! BufcloseCloseIt() + let l:currentBufNum = bufnr("%") + let l:alternateBufNum = bufnr("#") + if buflisted(l:alternateBufNum) + buffer # + else + bnext + endif + if bufnr("%") == l:currentBufNum + new + endif + if buflisted(l:currentBufNum) + execute("bdelete! " . l:currentBufNum) + endif +endfunction diff --git a/modules/cheatsheet.vim b/modules/cheatsheet.vim new file mode 100644 index 0000000..0fd4320 --- /dev/null +++ b/modules/cheatsheet.vim @@ -0,0 +1,267 @@ +" cheatsheet.vim — active keymap reference + +function! s:OpenCheatSheet(lines) abort + let l:name = '__ChopsticksCheatSheet__' + if bufwinnr(l:name) > 0 + execute bufwinnr(l:name) . 'wincmd w | bd' + return + endif + + 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, a:lines) + setlocal nomodifiable readonly + nnoremap q :bd + nnoremap ? :bd +endfunction + +function! s:CheatSheet() abort + let l:has_lsp = get(g:, 'chopsticks_enable_lsp', 1) + let l:has_lint = get(g:, 'chopsticks_enable_lint', 1) + let l:has_undotree = exists('g:plugs["undotree"]') + let l:has_previm = exists('g:plugs["previm"]') + + if g:chopsticks_space_keymaps + let l:lines = [ + \ ' chopsticks ? close', + \ ' ─────────────────────────────────', + \ '', + \ ' ── fast path ─────────────', + \ ' SPC SPC files', + \ ' SPC , buffers', + \ ' SPC / grep project', + \ ' SPC Tab last file', + \ ' SPC e sidebar (cwd)', + \ ' SPC E sidebar (file dir)', + \ '', + \ ' ── files/find ────────────', + \ ' SPC ff files', + \ ' SPC fb buffers', + \ ' SPC fg git files', + \ ' SPC fr recent files', + \ ' SPC fl lines in buffer', + \ ' SPC sc commands', + \ ' SPC sm marks', + \ ' SPC s/ search history', + \ ' SPC s: command history', + \ ' SPC sg grep project', + \ ' SPC sw grep word', + \ ' SPC st tags', + \ '', + \ ' ── code ──────────────────', + \ ] + + if l:has_lsp + call extend(l:lines, [ + \ ' gd definition', + \ ' gr references', + \ ' gI implementation', + \ ' gy type definition', + \ ' K hover docs', + \ ' [d ]d LSP diagnostics', + \ ' SPC ca code action', + \ ' SPC cr rename', + \ ' SPC cf format', + \ ' SPC ci LSP status', + \ ' SPC co outline', + \ ' SPC cS workspace symbols', + \ ' :LspInstallServer setup LSP', + \ ' :ChopsticksStatus check LSP setup', + \ ]) + endif + + call extend(l:lines, [ + \ ' SPC rr run file', + \ ' SPC cW strip trailing (v)', + \ ' SPC c= re-indent file (opt-in)', + \ ' SPC = re-indent (v)', + \ ]) + 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', + \ ' SPC xd ALE detail', + \ ' SPC uf format on save', + \ ]) + endif + + call extend(l:lines, [ + \ '', + \ ' ── edit ──────────────────', + \ ' s+2ch easymotion jump', + \ ' gc comment', + \ ' cl / cc native s / S substitute', + \ ' SPC S+2ch jump fallback', + \ ' cs"'' surround', + \ ]) + + if l:has_undotree + call add(l:lines, ' SPC U undo tree') + endif + + call extend(l:lines, [ + \ ' SPC y/p clipboard y/p (v)', + \ ' Alt+j/k move line (v)', + \ ' SPC sr replace word (v)', + \ '', + \ ' ── git ───────────────────', + \ ' SPC gs status', + \ ' SPC gd diff', + \ ' SPC gb blame', + \ ' SPC gc commit', + \ ' SPC gl log graph', + \ ' SPC gC FZF commits', + \ ' SPC gB FZF buffer commits', + \ ' [x ]x conflict markers', + \ '', + \ ' ── windows ───────────────', + \ ' hjkl navigate splits', + \ ' SPC bp/bn prev / next buf', + \ ' SPC bd close buffer', + \ ' SPC bo close other buffers', + \ ' SPC z maximize toggle', + \ ' SPC tt/th terminal / split', + \ ' ]q [q next / prev qf', + \ ' SPC xq/xQ open / close qf', + \ ' SPC xl/xL open / close loclist', + \ '', + \ ' ── toggle ────────────────', + \ ' F2 paste mode', + \ ' F3 line numbers', + \ ' F4 relative numbers', + \ ' F6 invisible chars', + \ ' SPC us spell check', + \ '', + \ ' ── survival ──────────────', + \ ' SPC w save', + \ ' SPC W save all', + \ ' SPC qq quit', + \ ' SPC qx save + quit', + \ ' Esc exit insert', + \ ' SPC fv edit vimrc', + \ ' SPC fV reload vimrc', + \ ' :ChopsticksTutor practice', + \ ' :ChopsticksStatus health', + \ ]) + + call s:OpenCheatSheet(l:lines) + return + endif + + let l:lines = [ + \ ' chopsticks ,? close', + \ ' ─────────────────────────────', + \ '', + \ ' ── files ──────────────────', + \ ' ,ff files', + \ ' ,b buffers', + \ ' ,rg grep project', + \ ' ,rG grep word', + \ ' ,e sidebar (cwd)', + \ ' ,E sidebar (file dir)', + \ ' ,, last file', + \ ' ,fh recent files', + \ ' ,fl lines in buffer', + \ ' ,fc commands', + \ ' ,fm marks', + \ '', + \ ' ── code ──────────────────', + \ ] + + if l:has_lsp + call extend(l:lines, [ + \ ' ,dd definition', + \ ' ,dt type definition', + \ ' ,di implementation', + \ ' ,dr references', + \ ' ,dk hover docs', + \ ' ,rn rename', + \ ' ,ca code action', + \ ' ,f format', + \ ' ,o outline', + \ ' ,dp ,dn LSP diagnostics', + \ ' :LspInstallServer setup LSP', + \ ' :ChopsticksStatus check LSP setup', + \ ]) + 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', + \ ' ,gL log graph', + \ ' ,gC FZF commits', + \ ' [x ]x conflict markers', + \ '', + \ ' ── windows ───────────────', + \ ' 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', + \ ' Esc exit insert', + \ ' ,ev edit vimrc', + \ ' ,sv reload vimrc', + \ ' :ChopsticksTutor practice', + \ ' :ChopsticksStatus health', + \ ]) + + call s:OpenCheatSheet(l:lines) +endfunction +command! ChopsticksCheatSheet call s:CheatSheet() +nnoremap ? :ChopsticksCheatSheet diff --git a/modules/core.vim b/modules/core.vim index 2194ff5..81174dc 100644 --- a/modules/core.vim +++ b/modules/core.vim @@ -94,22 +94,44 @@ set smartindent " ── Leader ────────────────────────────────────────────────────────────────── -let mapleader = "," +if g:chopsticks_space_keymaps + let mapleader = "\" + let maplocalleader = "," +else + let mapleader = "," +endif " ── Basic Keymaps ─────────────────────────────────────────────────────────── -nnoremap w :w -nnoremap q :q -nnoremap x :x +if g:chopsticks_space_keymaps + nnoremap w :w + nnoremap W :wa + nnoremap qq :q + nnoremap qx :x -nnoremap :noh + nnoremap uh :noh -nnoremap bd :Bclose -nnoremap ba :bufdo bd -nnoremap l :bnext -nnoremap h :bprevious + nnoremap bd :Bclose + nnoremap ba :bufdo bd + nnoremap bo :%bde#bd# + nnoremap bn :bnext + nnoremap bp :bprevious -nnoremap cd :lcd %:p:h:pwd + nnoremap fd :lcd %:p:h:pwd +else + nnoremap w :w + nnoremap q :q + nnoremap x :x + + nnoremap :noh + + nnoremap bd :Bclose + nnoremap ba :bufdo bd + nnoremap l :bnext + nnoremap h :bprevious + + nnoremap cd :lcd %:p:h:pwd +endif nnoremap v `[v`] @@ -118,7 +140,11 @@ nnoremap :m .-2== vnoremap :m '>+1gv=gv vnoremap :m '<-2gv=gv -nnoremap ss :setlocal spell!:echo 'Spell: ' . (&spell ? 'ON' : 'OFF') +if g:chopsticks_space_keymaps + nnoremap us :setlocal spell!:echo 'Spell: ' . (&spell ? 'ON' : 'OFF') +else + nnoremap ss :setlocal spell!:echo 'Spell: ' . (&spell ? 'ON' : 'OFF') +endif nnoremap :set paste!:echo 'Paste: ' . (&paste ? 'ON' : 'OFF') nnoremap :set invnumber:echo 'Line numbers: ' . (&number ? 'ON' : 'OFF') @@ -157,8 +183,15 @@ if has('clipboard') vnoremap P "+P endif -nnoremap qo :copen -nnoremap qc :cclose +if g:chopsticks_space_keymaps + nnoremap xq :copen + nnoremap xQ :cclose + nnoremap xl :lopen + nnoremap xL :lclose +else + nnoremap qo :copen + nnoremap qc :cclose +endif augroup ChopstickResize autocmd! @@ -193,7 +226,10 @@ endif 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 + silent! set diffopt+=internal + silent! set diffopt+=context:3 + silent! set diffopt+=algorithm:histogram + silent! set diffopt+=indent-heuristic endif " ── Format Options ────────────────────────────────────────────────────────── diff --git a/modules/editing.vim b/modules/editing.vim index 9a9dd0c..e434006 100644 --- a/modules/editing.vim +++ b/modules/editing.vim @@ -6,16 +6,27 @@ let g:EasyMotion_do_mapping = 0 let g:EasyMotion_smartcase = 1 if exists('g:plugs["vim-easymotion"]') - nmap S (easymotion-overwin-f2) - nmap j (easymotion-j) - nmap k (easymotion-k) + if g:chopsticks_space_keymaps + " In the canonical layout, cl/cc cover native s/S substitute behavior; + " s becomes the fastest screen-local jump entry. + nmap s (easymotion-overwin-f2) + nmap S (easymotion-overwin-f2) + else + nmap S (easymotion-overwin-f2) + nmap j (easymotion-j) + nmap k (easymotion-k) + endif endif " ── UndoTree ──────────────────────────────────────────────────────────────── if exists('g:plugs["undotree"]') nnoremap :UndotreeToggle - nnoremap u :UndotreeToggle + if g:chopsticks_space_keymaps + nnoremap U :UndotreeToggle + else + nnoremap u :UndotreeToggle + endif endif " ── Yank Highlight ────────────────────────────────────────────────────────── diff --git a/modules/env.vim b/modules/env.vim index 6e0a3b6..7fc42e9 100644 --- a/modules/env.vim +++ b/modules/env.vim @@ -11,6 +11,12 @@ if index(['minimal', 'engineer', 'full'], g:chopsticks_profile) < 0 let g:chopsticks_profile = 'engineer' endif +let g:chopsticks_keymap_style = get(g:, 'chopsticks_keymap_style', 'space') +if index(['classic', 'space'], g:chopsticks_keymap_style) < 0 + let g:chopsticks_keymap_style = 'space' +endif +let g:chopsticks_space_keymaps = g:chopsticks_keymap_style ==# 'space' + let s:profile_full = g:chopsticks_profile ==# 'full' let s:profile_minimal = g:chopsticks_profile ==# 'minimal' diff --git a/modules/files.vim b/modules/files.vim new file mode 100644 index 0000000..4f64bc1 --- /dev/null +++ b/modules/files.vim @@ -0,0 +1,54 @@ +" files.vim — file safety and large-file handling + +function! s:MkNonExDir(file, buf) + if empty(getbufvar(a:buf, '&buftype')) && a:file !~# '\v^\w+\:\/' + let dir = fnamemodify(a:file, ':h') + if !isdirectory(dir) + call mkdir(dir, 'p') + endif + endif +endfunction +augroup BWCCreateDir + autocmd! + autocmd BufWritePre * + \ if !empty(expand('')) | + \ call s:MkNonExDir(expand(''), +expand('')) | + \ endif +augroup END + +let g:LargeFile = get(g:, 'LargeFile', 1024 * 1024 * 10) +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 + autocmd! + autocmd BufReadPre * call s:MarkLargeFile(expand('')) + autocmd BufReadPost,FileType,Syntax * call s:ApplyLargeFileSettings() +augroup END diff --git a/modules/git.vim b/modules/git.vim index 3bd4b1d..7392897 100644 --- a/modules/git.vim +++ b/modules/git.vim @@ -14,11 +14,13 @@ let g:gitgutter_sign_modified_removed = '~' if exists('g:plugs["vim-fugitive"]') nnoremap gs :Git status nnoremap gc :Git commit - nnoremap gp :Git push - nnoremap gl :Git pull nnoremap gd :Gdiffsplit nnoremap gb :Git blame - nnoremap gL :Git log --oneline --graph -20 + if g:chopsticks_space_keymaps + nnoremap gl :Git log --oneline --graph -20 + else + nnoremap gL :Git log --oneline --graph -20 + endif endif " ── Conflict Navigation ──────────────────────────────────────────────────── diff --git a/modules/languages.vim b/modules/languages.vim index 2c4688d..11f1204 100644 --- a/modules/languages.vim +++ b/modules/languages.vim @@ -14,7 +14,16 @@ 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"]') +function! s:MarkdownKeymaps() abort + if exists('g:plugs["vim-markdown"]') + nnoremap mt :Toc + endif + if exists('g:plugs["previm"]') + nnoremap mp :PrevimOpen + endif +endfunction + +if exists('g:plugs["vim-markdown"]') && !g:chopsticks_space_keymaps nnoremap mt :Toc endif @@ -24,7 +33,7 @@ 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"]') +if exists('g:plugs["previm"]') && !g:chopsticks_space_keymaps nnoremap mp :PrevimOpen endif @@ -78,6 +87,7 @@ augroup ChopstickFiletype autocmd FileType yaml \ setlocal expandtab shiftwidth=2 tabstop=2 autocmd FileType markdown call s:MarkdownDefaults() + autocmd FileType markdown if g:chopsticks_space_keymaps | call s:MarkdownKeymaps() | endif autocmd FileType sh \ setlocal expandtab shiftwidth=2 tabstop=2 textwidth=80 autocmd FileType make diff --git a/modules/lint.vim b/modules/lint.vim index fd01286..4d4de28 100644 --- a/modules/lint.vim +++ b/modules/lint.vim @@ -64,7 +64,13 @@ let g:ale_virtualtext_cursor = get(g:, 'ale_virtualtext_cursor', 'disabled') if exists('g:plugs["ale"]') nnoremap [e :ALEPrevious nnoremap ]e :ALENext - nnoremap aD :ALEDetail - nnoremap af :let g:ale_fix_on_save = !g:ale_fix_on_save - \ echo 'Format on save: ' . (g:ale_fix_on_save ? 'ON' : 'OFF') + if g:chopsticks_space_keymaps + nnoremap xd :ALEDetail + nnoremap uf :let g:ale_fix_on_save = !g:ale_fix_on_save + \ echo 'Format on save: ' . (g:ale_fix_on_save ? 'ON' : 'OFF') + else + nnoremap aD :ALEDetail + nnoremap af :let g:ale_fix_on_save = !g:ale_fix_on_save + \ echo 'Format on save: ' . (g:ale_fix_on_save ? 'ON' : 'OFF') + endif endif diff --git a/modules/lsp.vim b/modules/lsp.vim index 9ff421b..9919731 100644 --- a/modules/lsp.vim +++ b/modules/lsp.vim @@ -65,23 +65,42 @@ function! s:on_lsp_buffer_enabled() abort setlocal signcolumn=yes endif - nmap dd (lsp-definition) - nmap dt (lsp-type-definition) - nmap di (lsp-implementation) - nmap dr (lsp-references) - nmap dp (lsp-previous-diagnostic) - nmap dn (lsp-next-diagnostic) + if g:chopsticks_space_keymaps + nmap gd (lsp-definition) + nmap gr (lsp-references) + nmap gI (lsp-implementation) + nmap gy (lsp-type-definition) + nmap K (lsp-hover) + nmap [d (lsp-previous-diagnostic) + nmap ]d (lsp-next-diagnostic) - nmap dk (lsp-hover) + nmap ca (lsp-code-action) + nmap cr (lsp-rename) + nmap cf (lsp-document-format) + xmap cf (lsp-document-range-format) - nmap rn (lsp-rename) - nmap ca (lsp-code-action) - nmap f (lsp-document-format) - xmap f (lsp-document-range-format) + nnoremap ci :LspStatus + nmap co (lsp-document-symbol-search) + nmap cS (lsp-workspace-symbol-search) + else + nmap dd (lsp-definition) + nmap dt (lsp-type-definition) + nmap di (lsp-implementation) + nmap dr (lsp-references) + nmap dp (lsp-previous-diagnostic) + nmap dn (lsp-next-diagnostic) - nmap o (lsp-document-symbol-search) - nmap ws (lsp-workspace-symbol-search) - nmap cD (lsp-document-diagnostics) + nmap dk (lsp-hover) + + nmap rn (lsp-rename) + nmap ca (lsp-code-action) + nmap f (lsp-document-format) + xmap f (lsp-document-range-format) + + nmap o (lsp-document-symbol-search) + nmap ws (lsp-workspace-symbol-search) + nmap cD (lsp-document-diagnostics) + endif endfunction augroup lsp_install diff --git a/modules/navigation.vim b/modules/navigation.vim index 45b2ad5..d133dd4 100644 --- a/modules/navigation.vim +++ b/modules/navigation.vim @@ -47,21 +47,42 @@ function! s:SmartFiles() abort endfunction if exists('g:plugs["fzf.vim"]') - nnoremap ff :call SmartFiles() - nnoremap b :Buffers - nnoremap rg :Rg - nnoremap rG :RgWord - nnoremap rt :Tags - nnoremap gF :GFiles - nnoremap fh :History - nnoremap fc :Commands - nnoremap fm :Marks - nnoremap fl :BLines - nnoremap fL :Lines - nnoremap f/ :History/ - nnoremap f: :History: - nnoremap gC :Commits - nnoremap gB :BCommits + if g:chopsticks_space_keymaps + nnoremap :call SmartFiles() + nnoremap , :Buffers + nnoremap / :Rg + nnoremap ff :call SmartFiles() + nnoremap fb :Buffers + nnoremap fg :GFiles + nnoremap fr :History + nnoremap fl :BLines + nnoremap fL :Lines + nnoremap s/ :History/ + nnoremap s: :History: + nnoremap sc :Commands + nnoremap sm :Marks + nnoremap sg :Rg + nnoremap sw :RgWord + nnoremap st :Tags + nnoremap gC :Commits + nnoremap gB :BCommits + else + nnoremap ff :call SmartFiles() + nnoremap b :Buffers + nnoremap rg :Rg + nnoremap rG :RgWord + nnoremap rt :Tags + nnoremap gF :GFiles + nnoremap fh :History + nnoremap fc :Commands + nnoremap fm :Marks + nnoremap fl :BLines + nnoremap fL :Lines + nnoremap f/ :History/ + nnoremap f: :History: + nnoremap gC :Commits + nnoremap gB :BCommits + endif endif let g:fzf_layout = { 'down': '40%' } @@ -99,13 +120,22 @@ function! s:ToggleMaximize() abort echo 'Window: MAXIMIZED' endif endfunction -nnoremap z :call ToggleMaximize() +if g:chopsticks_space_keymaps + nnoremap z :call ToggleMaximize() +else + nnoremap z :call ToggleMaximize() +endif " ── Terminal ──────────────────────────────────────────────────────────────── if has('terminal') - nnoremap tv :terminal - nnoremap th :terminal ++rows=10 + if g:chopsticks_space_keymaps + nnoremap tt :terminal + nnoremap th :terminal ++rows=10 + else + nnoremap tv :terminal + nnoremap th :terminal ++rows=10 + endif if g:chopsticks_enable_terminal_keymaps tnoremap tnoremap h diff --git a/modules/quickfix.vim b/modules/quickfix.vim new file mode 100644 index 0000000..f7ffee3 --- /dev/null +++ b/modules/quickfix.vim @@ -0,0 +1,10 @@ +" quickfix.vim — quickfix and location-list helpers + +augroup ChopstickQF + autocmd! + autocmd QuickFixCmdPost [^l]* cwindow + autocmd QuickFixCmdPost l* lwindow +augroup END + +nnoremap ]q :cnext +nnoremap [q :cprev diff --git a/modules/runner.vim b/modules/runner.vim new file mode 100644 index 0000000..3618b24 --- /dev/null +++ b/modules/runner.vim @@ -0,0 +1,28 @@ +" runner.vim — run the current file by filetype + +function! s:RunFile() abort + write + let l:ft = &filetype + let l:file = shellescape(expand('%:p')) + if l:ft ==# 'python' | execute '!python3 ' . l:file + elseif l:ft ==# 'javascript' | execute '!node ' . l:file + elseif l:ft ==# 'typescript' | execute '!npx ts-node ' . l:file + elseif l:ft ==# 'go' | execute '!go run ' . l:file + elseif l:ft ==# 'rust' | execute '!cargo run' + elseif l:ft ==# 'sh' | execute '!bash ' . l:file + elseif l:ft ==# 'c' + let l:out_path = tempname() + let l:out = shellescape(l:out_path) + execute '!gcc -o ' . l:out . ' ' . l:file . ' && ' . l:out + call delete(l:out_path) + elseif l:ft ==# 'lua' | execute '!lua ' . l:file + elseif l:ft ==# 'ruby' | execute '!ruby ' . l:file + elseif l:ft ==# 'perl' | execute '!perl ' . l:file + else | echo 'No runner for filetype: ' . l:ft + endif +endfunction +if g:chopsticks_space_keymaps + nnoremap rr :call RunFile() +else + nnoremap cr :call RunFile() +endif diff --git a/modules/status.vim b/modules/status.vim new file mode 100644 index 0000000..bafcf06 --- /dev/null +++ b/modules/status.vim @@ -0,0 +1,165 @@ +" status.vim — health diagnostics + +function! s:Check(name, cmd) abort + return executable(a:cmd) ? ' OK ' . a:name : ' -- ' . a:name . ' (missing: ' . a:cmd . ')' +endfunction + +function! s:Off(name, reason) abort + return ' off ' . a:name . ' (' . a:reason . ')' +endfunction + +function! s:PlugDir(name) abort + if !exists('g:plugs') || !has_key(g:plugs, a:name) + return '' + endif + return fnamemodify(get(g:plugs[a:name], 'dir', ''), ':p') +endfunction + +function! s:PlugInstalled(name) abort + let l:dir = s:PlugDir(a:name) + return !empty(l:dir) && isdirectory(l:dir) +endfunction + +function! s:LspStackIssue() abort + if !get(g:, 'chopsticks_enable_lsp', 1) + return 'LSP disabled by profile' + endif + if empty(s:PlugDir('vim-lsp')) + return 'vim-lsp not declared by this profile' + endif + if !s:PlugInstalled('vim-lsp') + return 'vim-lsp not installed; run :PlugInstall' + endif + if empty(s:PlugDir('vim-lsp-settings')) + return 'vim-lsp-settings not declared by this profile' + endif + if !s:PlugInstalled('vim-lsp-settings') + return 'vim-lsp-settings not installed; run :PlugInstall' + endif + return '' +endfunction + +function! s:LspStackCheck() abort + let l:issue = s:LspStackIssue() + if l:issue ==# 'LSP disabled by profile' + return s:Off('vim-lsp stack', l:issue) + endif + if !empty(l:issue) + return ' -- vim-lsp stack (' . l:issue . ')' + endif + if exists(':LspStatus') == 2 || exists(':LspInstallServer') == 2 + return ' OK vim-lsp stack (installed)' + endif + return ' OK vim-lsp stack (installed; not loaded yet)' +endfunction + +function! s:LspCheck(ft, server) abort + let l:issue = s:LspStackIssue() + if l:issue ==# 'LSP disabled by profile' + return s:Off(a:ft, l:issue) + endif + if !empty(l:issue) + return ' -- ' . a:ft . ' (' . l:issue . ')' + 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:LspStackCheck()) + if get(g:, 'chopsticks_enable_lsp', 1) + call add(l:lines, ' LSP actions are buffer-local and start after a server attaches.') + call add(l:lines, ' Missing one? Open that filetype and run :LspInstallServer once.') + endif + 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 q :bd +endfunction +command! ChopsticksStatus call s:ChopsticksStatus() diff --git a/modules/tools.vim b/modules/tools.vim index 8506389..9d2b120 100644 --- a/modules/tools.vim +++ b/modules/tools.vim @@ -1,445 +1,4 @@ -" tools.vim — run file, sudo save, quickfix, helpers - -" ── Buffer Close ─────────────────────────────────────────────────────────── - -command! Bclose call BufcloseCloseIt() -function! BufcloseCloseIt() - let l:currentBufNum = bufnr("%") - let l:alternateBufNum = bufnr("#") - if buflisted(l:alternateBufNum) - buffer # - else - bnext - endif - if bufnr("%") == l:currentBufNum - new - endif - if buflisted(l:currentBufNum) - execute("bdelete! " . l:currentBufNum) - endif -endfunction - -" ── Utilities ────────────────────────────────────────────────────────────── - -if get(g:, 'chopsticks_enable_reindent_file', 0) - nnoremap F gg=G`` -endif -vnoremap F = -nnoremap wa :wa - -nnoremap = :exe "resize " . (winheight(0) * 3/2) -nnoremap - :exe "resize " . (winheight(0) * 2/3) - -nnoremap - -nnoremap W :%s/\s\+$//:let @/='' -vnoremap W :s/\s\+$//:let @/=''gv - -nnoremap ev :edit $MYVIMRC -nnoremap sv :unlet! g:chopsticks_loaded:execute 'source ' . fnameescape($MYVIMRC):echo "vimrc reloaded" - -nnoremap * :%s/\<\>//g -vnoremap * :s///g - -if has('clipboard') - nnoremap cp :let @+ = expand("%:p"):echo "Copied: " . expand("%:p") - nnoremap cf :let @+ = expand("%:t"):echo "Copied: " . expand("%:t") -endif - -" ── Auto-Create Directories ───────────────────────────────────────────────── - -function! s:MkNonExDir(file, buf) - if empty(getbufvar(a:buf, '&buftype')) && a:file !~# '\v^\w+\:\/' - let dir = fnamemodify(a:file, ':h') - if !isdirectory(dir) - call mkdir(dir, 'p') - endif - endif -endfunction -augroup BWCCreateDir - autocmd! - autocmd BufWritePre * - \ if !empty(expand('')) | - \ call s:MkNonExDir(expand(''), +expand('')) | - \ endif -augroup END - -" ── Large File Handling ────────────────────────────────────────────────────── - -let g:LargeFile = get(g:, 'LargeFile', 1024 * 1024 * 10) -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 - autocmd! - autocmd BufReadPre * call s:MarkLargeFile(expand('')) - autocmd BufReadPost,FileType,Syntax * call s:ApplyLargeFileSettings() -augroup END - -" ── Run Current File (,cr) ────────────────────────────────────────────────── - -function! s:RunFile() abort - write - let l:ft = &filetype - let l:file = shellescape(expand('%:p')) - if l:ft ==# 'python' | execute '!python3 ' . l:file - elseif l:ft ==# 'javascript' | execute '!node ' . l:file - elseif l:ft ==# 'typescript' | execute '!npx ts-node ' . l:file - elseif l:ft ==# 'go' | execute '!go run ' . l:file - elseif l:ft ==# 'rust' | execute '!cargo run' - elseif l:ft ==# 'sh' | execute '!bash ' . l:file - elseif l:ft ==# 'c' - let l:out_path = tempname() - let l:out = shellescape(l:out_path) - execute '!gcc -o ' . l:out . ' ' . l:file . ' && ' . l:out - call delete(l:out_path) - elseif l:ft ==# 'lua' | execute '!lua ' . l:file - elseif l:ft ==# 'ruby' | execute '!ruby ' . l:file - elseif l:ft ==# 'perl' | execute '!perl ' . l:file - else | echo 'No runner for filetype: ' . l:ft - endif -endfunction -nnoremap cr :call RunFile() - -" ── Sudo Save ─────────────────────────────────────────────────────────────── - -if get(g:, 'chopsticks_enable_sudo_save_bang', 0) - cnoremap w!! w !sudo tee > /dev/null % -endif - -" ── QuickFix ──────────────────────────────────────────────────────────────── - -augroup ChopstickQF - autocmd! - autocmd QuickFixCmdPost [^l]* cwindow - autocmd QuickFixCmdPost l* lwindow -augroup END - -nnoremap ]q :cnext -nnoremap [q :cprev - -" ── Status Diagnostic (:ChopsticksStatus) ─────────────────────────────────── - -function! s:Check(name, cmd) abort - return executable(a:cmd) ? ' OK ' . a:name : ' -- ' . a:name . ' (missing: ' . a:cmd . ')' -endfunction - -function! s:Off(name, reason) abort - return ' off ' . a:name . ' (' . a:reason . ')' -endfunction - -function! s:PlugDir(name) abort - if !exists('g:plugs') || !has_key(g:plugs, a:name) - return '' - endif - return fnamemodify(get(g:plugs[a:name], 'dir', ''), ':p') -endfunction - -function! s:PlugInstalled(name) abort - let l:dir = s:PlugDir(a:name) - return !empty(l:dir) && isdirectory(l:dir) -endfunction - -function! s:LspStackIssue() abort - if !get(g:, 'chopsticks_enable_lsp', 1) - return 'LSP disabled by profile' - endif - if empty(s:PlugDir('vim-lsp')) - return 'vim-lsp not declared by this profile' - endif - if !s:PlugInstalled('vim-lsp') - return 'vim-lsp not installed; run :PlugInstall' - endif - if empty(s:PlugDir('vim-lsp-settings')) - return 'vim-lsp-settings not declared by this profile' - endif - if !s:PlugInstalled('vim-lsp-settings') - return 'vim-lsp-settings not installed; run :PlugInstall' - endif - return '' -endfunction - -function! s:LspStackCheck() abort - let l:issue = s:LspStackIssue() - if l:issue ==# 'LSP disabled by profile' - return s:Off('vim-lsp stack', l:issue) - endif - if !empty(l:issue) - return ' -- vim-lsp stack (' . l:issue . ')' - endif - if exists(':LspStatus') == 2 || exists(':LspInstallServer') == 2 - return ' OK vim-lsp stack (installed)' - endif - return ' OK vim-lsp stack (installed; not loaded yet)' -endfunction - -function! s:LspCheck(ft, server) abort - let l:issue = s:LspStackIssue() - if l:issue ==# 'LSP disabled by profile' - return s:Off(a:ft, l:issue) - endif - if !empty(l:issue) - return ' -- ' . a:ft . ' (' . l:issue . ')' - 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:LspStackCheck()) - if get(g:, 'chopsticks_enable_lsp', 1) - call add(l:lines, ' LSP actions are buffer-local and start after a server attaches.') - call add(l:lines, ' Missing one? Open that filetype and run :LspInstallServer once.') - endif - 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 q :bd -endfunction -command! ChopsticksStatus call s:ChopsticksStatus() - -" ── Cheat Sheet (,?) ──────────────────────────────────────────────────────── - -function! s:CheatSheet() abort - let l:name = '__ChopsticksCheatSheet__' - if bufwinnr(l:name) > 0 - execute bufwinnr(l:name) . 'wincmd w | bd' - return - endif - - let l:has_lsp = get(g:, 'chopsticks_enable_lsp', 1) - let l:has_lint = get(g:, 'chopsticks_enable_lint', 1) - let l:has_undotree = exists('g:plugs["undotree"]') - let l:has_previm = exists('g:plugs["previm"]') - - let l:lines = [ - \ ' chopsticks ,? close', - \ ' ─────────────────────────────', - \ '', - \ ' ── files ──────────────────', - \ ' ,ff files', - \ ' ,b buffers', - \ ' ,rg grep project', - \ ' ,rG grep word', - \ ' ,e sidebar (cwd)', - \ ' ,E sidebar (file dir)', - \ ' ,, last file', - \ ' ,fh recent files', - \ ' ,fl lines in buffer', - \ ' ,fc commands', - \ ' ,fm marks', - \ '', - \ ' ── code ──────────────────', - \ ] - - if l:has_lsp - call extend(l:lines, [ - \ ' ,dd definition', - \ ' ,dt type definition', - \ ' ,di implementation', - \ ' ,dr references', - \ ' ,dk hover docs', - \ ' ,rn rename', - \ ' ,ca code action', - \ ' ,f format', - \ ' ,o outline', - \ ' ,dp ,dn LSP diagnostics', - \ ' :LspInstallServer setup LSP', - \ ' :ChopsticksStatus check LSP setup', - \ ]) - 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 ───────────────', - \ ' 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', - \ ' Esc exit insert', - \ ' ,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 - nnoremap q :bd - nnoremap ? :bd -endfunction -nnoremap ? :call CheatSheet() +" tools.vim — compatibility placeholder +" +" Tooling was split into granular modules: +" buffers, utilities, files, runner, quickfix, status, cheatsheet, and tutor. diff --git a/modules/tutor.vim b/modules/tutor.vim new file mode 100644 index 0000000..5d023b9 --- /dev/null +++ b/modules/tutor.vim @@ -0,0 +1,102 @@ +" tutor.vim — guided practice for chopsticks keymaps + +function! s:OpenTutor(lines) abort + let l:name = '__ChopsticksTutor__' + if bufwinnr(l:name) > 0 + execute bufwinnr(l:name) . 'wincmd w | bd' + return 0 + endif + + execute 'botright new ' . l:name + resize 38 + setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile + setlocal nowrap nonumber norelativenumber signcolumn=no + call setline(1, a:lines) + setlocal nomodifiable readonly + nnoremap q :bd + return 1 +endfunction + +function! s:ChopsticksTutor() abort + if g:chopsticks_space_keymaps + let l:lines = [ + \ ' chopsticks tutor q close', + \ ' ───────────────────────────────', + \ '', + \ ' 1. survival', + \ ' Esc Normal mode', + \ ' SPC ? active cheat sheet', + \ ' SPC w save', + \ ' SPC qx save and quit', + \ ' :ChopsticksStatus health check', + \ '', + \ ' 2. find and switch', + \ ' SPC SPC find files', + \ ' SPC / grep project', + \ ' SPC , buffers', + \ ' SPC Tab alternate buffer', + \ ' SPC e/E sidebar cwd / file dir', + \ '', + \ ' 3. jump and edit', + \ ' s + 2 chars visible jump', + \ ' SPC S same jump fallback', + \ ' cl / cc native s / S substitute', + \ ' gc comment', + \ ' SPC U undo tree', + \ '', + \ ' 4. code loop', + \ ' gd / gr / K definition / refs / docs', + \ ' gI / gy implementation / type', + \ ' [d ]d LSP diagnostics', + \ ' SPC ca/cr/cf action / rename / format', + \ ' SPC rr run current file', + \ '', + \ ' 5. git and windows', + \ ' SPC gs/gd/gb status / diff / blame', + \ ' SPC gl log graph', + \ ' hjkl split navigation', + \ ' SPC z maximize split', + \ '', + \ ' daily drill', + \ ' Open a project, run SPC SPC, jump with s, inspect with gd/K,', + \ ' edit with gc/SPC cf, check SPC gs, then save with SPC w.', + \ ] + else + let l:lines = [ + \ ' chopsticks tutor q close', + \ ' ───────────────────────────────', + \ '', + \ ' classic layout', + \ ' ,? active cheat sheet', + \ ' ,w / ,x save / save and quit', + \ ' ,ff find files', + \ ' ,rg grep project', + \ ' ,b buffers', + \ ' ,, alternate buffer', + \ '', + \ ' code loop', + \ ' ,dd / ,dr definition / refs', + \ ' ,dk hover docs', + \ ' ,ca / ,rn action / rename', + \ ' ,f format', + \ ' ,cr run current file', + \ '', + \ ' edit and git', + \ ' ,S + 2 chars EasyMotion jump', + \ ' gc comment', + \ ' ,u undo tree', + \ ' ,gs/,gd/,gb status / diff / blame', + \ ' hjkl split navigation', + \ '', + \ ' support', + \ ' :ChopsticksStatus health check', + \ ' README.md full reference', + \ ' QUICKSTART.md 5-minute path', + \ ] + endif + + if s:OpenTutor(l:lines) + nnoremap ? :ChopsticksCheatSheet + endif +endfunction +command! ChopsticksTutor call s:ChopsticksTutor() diff --git a/modules/utilities.vim b/modules/utilities.vim new file mode 100644 index 0000000..9988a42 --- /dev/null +++ b/modules/utilities.vim @@ -0,0 +1,64 @@ +" utilities.vim — small editing and config helpers + +if get(g:, 'chopsticks_enable_reindent_file', 0) + if g:chopsticks_space_keymaps + nnoremap c= gg=G`` + else + nnoremap F gg=G`` + endif +endif +if g:chopsticks_space_keymaps + vnoremap = = +else + vnoremap F = + nnoremap wa :wa +endif + +if !g:chopsticks_space_keymaps + nnoremap = :exe "resize " . (winheight(0) * 3/2) + nnoremap - :exe "resize " . (winheight(0) * 2/3) +endif + +if g:chopsticks_space_keymaps + nnoremap +else + nnoremap +endif + +if g:chopsticks_space_keymaps + nnoremap cW :%s/\s\+$//:let @/='' + vnoremap cW :s/\s\+$//:let @/=''gv +else + nnoremap W :%s/\s\+$//:let @/='' + vnoremap W :s/\s\+$//:let @/=''gv +endif + +if g:chopsticks_space_keymaps + nnoremap fv :edit $MYVIMRC + nnoremap fV :unlet! g:chopsticks_loaded:execute 'source ' . fnameescape($MYVIMRC):echo "vimrc reloaded" +else + nnoremap ev :edit $MYVIMRC + nnoremap sv :unlet! g:chopsticks_loaded:execute 'source ' . fnameescape($MYVIMRC):echo "vimrc reloaded" +endif + +if g:chopsticks_space_keymaps + nnoremap sr :%s/\<\>//g + vnoremap sr :s///g +else + nnoremap * :%s/\<\>//g + vnoremap * :s///g +endif + +if has('clipboard') + if g:chopsticks_space_keymaps + nnoremap fp :let @+ = expand("%:p"):echo "Copied: " . expand("%:p") + nnoremap fn :let @+ = expand("%:t"):echo "Copied: " . expand("%:t") + else + nnoremap cp :let @+ = expand("%:p"):echo "Copied: " . expand("%:p") + nnoremap cf :let @+ = expand("%:t"):echo "Copied: " . expand("%:t") + endif +endif + +if get(g:, 'chopsticks_enable_sudo_save_bang', 0) + cnoremap w!! w !sudo tee > /dev/null % +endif diff --git a/scripts/test-common.sh b/scripts/test-common.sh new file mode 100644 index 0000000..4de3988 --- /dev/null +++ b/scripts/test-common.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Shared setup for chopsticks test scripts. + +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 + } +} diff --git a/scripts/test-quick.sh b/scripts/test-quick.sh new file mode 100755 index 0000000..8942cbc --- /dev/null +++ b/scripts/test-quick.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +# Shell, docs, installer, and bootstrap checks. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=scripts/test-common.sh +source "$SCRIPT_DIR/test-common.sh" + +check_shell() { + step "Shell syntax and lint" + need bash + bash -n install.sh + bash -n get.sh + bash -n scripts/test.sh + bash -n scripts/test-common.sh + bash -n scripts/test-quick.sh + bash -n scripts/test-vim.sh + test -x install.sh + test -x get.sh + test -x scripts/test.sh + + need shellcheck + shellcheck install.sh get.sh scripts/test.sh \ + scripts/test-common.sh scripts/test-quick.sh scripts/test-vim.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/no-git-bin" + printf '%s\n' \ + '#!/usr/bin/env bash' \ + "echo \"brew was called\" >> \"\$BREW_LOG\"" \ + 'exit 42' > "$TMP_ROOT/no-git-bin/brew" + chmod +x "$TMP_ROOT/no-git-bin/brew" + BREW_LOG="$TMP_ROOT/no-git-brew.log" \ + PATH="$TMP_ROOT/no-git-bin" \ + CHOPSTICKS_DEST="$TMP_ROOT/no-git-bootstrap" \ + /bin/bash ./get.sh --dry-run --profile=full \ + | tee "$TMP_ROOT/get-no-git-dry-run.txt" + grep -q 'Would require: git' "$TMP_ROOT/get-no-git-dry-run.txt" + grep -q 'Would clone' "$TMP_ROOT/get-no-git-dry-run.txt" + test ! -e "$TMP_ROOT/no-git-brew.log" + test ! -e "$TMP_ROOT/no-git-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" +} + +run_quick_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 ;; + *) + echo "Unknown quick test group: $1" >&2 + exit 1 ;; + esac +} + +if [[ $# -eq 0 ]]; then + set -- quick +fi + +for group in "$@"; do + run_quick_group "$group" +done diff --git a/scripts/test-vim.sh b/scripts/test-vim.sh new file mode 100755 index 0000000..9620fb2 --- /dev/null +++ b/scripts/test-vim.sh @@ -0,0 +1,414 @@ +#!/usr/bin/env bash +# Vim smoke tests. Requires plugins in ~/.vim/plugged. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=scripts/test-common.sh +source "$SCRIPT_DIR/test-common.sh" + +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 + if [ -x /usr/bin/vim ] && [ "$(command -v vim)" != "/usr/bin/vim" ]; then + XDG_CONFIG_HOME="$EMPTY_XDG" /usr/bin/vim -u .vimrc -i NONE -es -N -c 'qa!' 2>&1 + fi + + 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 + + XDG_CONFIG_HOME="$EMPTY_XDG" 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") || has_key(g:plugs, "auto-pairs") | 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") || has_key(g:plugs, "auto-pairs") | 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") || has_key(g:plugs, "auto-pairs") | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ + -c 'ChopsticksStatus' \ + -c "redir! > $TMP_ROOT/status-default.txt" \ + -c 'silent %print' \ + -c 'redir END' \ + -c 'qa!' 2>&1 + if grep -Fq 'vim-lsp not loaded' "$TMP_ROOT/status-default.txt"; then + cat "$TMP_ROOT/status-default.txt" + exit 1 + fi + grep -Fq 'OK vim-lsp stack (installed)' "$TMP_ROOT/status-default.txt" + grep -Fq 'python (:LspInstallServer in a python file)' "$TMP_ROOT/status-default.txt" + grep -Fq 'LSP actions are buffer-local and start after a server attaches.' "$TMP_ROOT/status-default.txt" + grep -Fq 'Open that filetype and run :LspInstallServer once.' "$TMP_ROOT/status-default.txt" + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ + -c 'let last_change_map = nr2char(96) . "[v" . nr2char(96) . "]"' \ + -c 'if maparg("0", "n") !=# "" || maparg("0", "v") !=# "" || maparg("Y", "n") !=# "" || maparg("Q", "n") !=# "" || maparg("", "n") !=# "" || maparg("//", "v") !=# "" || maparg("gV", "n") !=# "" || maparg("jk", "i") !=# "" || maparg("", "n") !=# "" || maparg("", "i") !=# "" || maparg("", "n") !=# "" || maparg("", "n") !=# "" || maparg("", "n") !=# "" || maparg("", "n") !=# "" || maparg("", "n") !=# "" || maparg("", "c") !=# "" || maparg("", "c") !=# "" || maparg("w!!", "c") !=# "" | cquit | endif' \ + -c 'if has_key(g:plugs, "auto-pairs") || maparg("", "i") =~# "pumvisible" || maparg("", "i") =~# "pumvisible" || maparg("", "i") =~# "asyncomplete#close_popup" || maparg("", "i") =~# "AutoPairs" | cquit | endif' \ + -c 'if maparg("", "t") !=# "" || maparg("", "t") !=# "" || maparg("", "t") !=# "" || maparg("", "t") !=# "" || maparg("", "t") !=# "" | cquit | endif' \ + -c 'if maparg("s", "n") !~# "easymotion-overwin-f2" | cquit | endif' \ + -c 'if maparg("/", "v") !~# "escape" || maparg("v", "n") !=# last_change_map || maparg("", "n") !~# "SmartFiles" | cquit | endif' \ + -c 'if maparg(",/", "v") !=# "" || maparg(",v", "n") !=# "" || maparg(",ff", "n") !=# "" | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_keymap_style = "classic"' \ + -c 'source .vimrc' \ + -c 'let last_change_map = nr2char(96) . "[v" . nr2char(96) . "]"' \ + -c 'if mapleader !=# "," || maparg("s", "n") !=# "" || maparg(",/", "v") !~# "escape" || maparg(",v", "n") !=# last_change_map || maparg(",ff", "n") !~# "SmartFiles" | cquit | endif' \ + -c 'if maparg(",gp", "n") !=# "" || maparg(",gl", "n") !=# "" | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_enable_jk_escape = 1' \ + -c 'source .vimrc' \ + -c 'if maparg("jk", "i") !~# "" | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_enable_ctrl_s_save = 1' \ + -c 'let g:chopsticks_enable_sudo_save_bang = 1' \ + -c 'let g:chopsticks_enable_completion_keymaps = 1' \ + -c 'source .vimrc' \ + -c 'if maparg("", "n") !~# ":w" || maparg("", "i") !~# ":w" || maparg("w!!", "c") !~# "sudo tee" | cquit | endif' \ + -c 'if maparg("", "i") !~# "pumvisible" || maparg("", "i") !~# "pumvisible" || maparg("", "i") !~# "asyncomplete#close_popup" | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_enable_auto_pairs = 1' \ + -c 'source .vimrc' \ + -c 'if !has_key(g:plugs, "auto-pairs") | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_enable_terminal_keymaps = 1' \ + -c 'source .vimrc' \ + -c 'if has("terminal") && (maparg("", "t") !~# "" || maparg("", "t") !~# "h" || maparg("", "t") !~# "j" || maparg("", "t") !~# "k" || maparg("", "t") !~# "l") | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ + -c 'if &exrc || &secure | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_enable_exrc = 1' \ + -c 'source .vimrc' \ + -c 'if !&exrc || !&secure | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ + -c 'if maparg("c=", "n") !=# "" | cquit | endif' \ + -c 'if maparg("=", "v") !~# "=" | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_enable_reindent_file = 1' \ + -c 'source .vimrc' \ + -c 'if maparg("c=", "n") !~# "gg=G" | cquit | endif' \ + -c 'qa!' 2>&1 + + TERM=xterm-256color XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ + -c 'if g:is_tty || &ttimeoutlen != 10 | cquit | endif' \ + -c 'qa!' 2>&1 + + TERM=linux XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ + -c 'if !g:is_tty || &ttimeoutlen != 50 | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ + -c 'if !exists("loaded_gzip") || !exists("loaded_logiPat") || !exists("loaded_rrhelper") || !exists("loaded_spellfile_plugin") | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ + -c 'silent! delcommand LspStatus' \ + -c 'silent! delcommand LspInstallServer' \ + -c 'ChopsticksStatus' \ + -c "redir! > $TMP_ROOT/status-lsp-not-loaded.txt" \ + -c 'silent %print' \ + -c 'redir END' \ + -c 'qa!' 2>&1 + grep -Fq 'OK vim-lsp stack (installed; not loaded yet)' "$TMP_ROOT/status-lsp-not-loaded.txt" + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_profile = "minimal"' \ + -c 'source .vimrc' \ + -c 'ChopsticksStatus' \ + -c "redir! > $TMP_ROOT/status-minimal.txt" \ + -c 'silent %print' \ + -c 'redir END' \ + -c 'qa!' 2>&1 + grep -Fq 'off vim-lsp stack (LSP disabled by profile)' "$TMP_ROOT/status-minimal.txt" + grep -Fq 'off python (LSP disabled by profile)' "$TMP_ROOT/status-minimal.txt" + if grep -Fq 'LSP actions are buffer-local' "$TMP_ROOT/status-minimal.txt"; then + cat "$TMP_ROOT/status-minimal.txt" + exit 1 + fi + + mkdir -p "$TMP_ROOT/missing-home/.vim/autoload" + cp "$HOME/.vim/autoload/plug.vim" "$TMP_ROOT/missing-home/.vim/autoload/plug.vim" + HOME="$TMP_ROOT/missing-home" XDG_CONFIG_HOME="$EMPTY_XDG" \ + vim -u .vimrc -i NONE -es -N \ + -c 'ChopsticksStatus' \ + -c "redir! > $TMP_ROOT/status-missing-plugin.txt" \ + -c 'silent %print' \ + -c 'redir END' \ + -c 'qa!' 2>&1 + grep -Fq 'vim-lsp not installed; run :PlugInstall' "$TMP_ROOT/status-missing-plugin.txt" + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ + -c 'doautocmd User lsp_buffer_enabled' \ + -c 'if maparg("gd", "n") !~# "lsp-definition" || maparg("gr", "n") !~# "lsp-references" || maparg("gI", "n") !~# "lsp-implementation" || maparg("gy", "n") !~# "lsp-type-definition" || maparg("K", "n") !~# "lsp-hover" | cquit | endif' \ + -c 'if maparg("[d", "n") !~# "lsp-previous-diagnostic" || maparg("]d", "n") !~# "lsp-next-diagnostic" | cquit | endif' \ + -c 'if maparg("ca", "n") !~# "lsp-code-action" || maparg("cr", "n") !~# "lsp-rename" || maparg("cf", "n") !~# "lsp-document-format" | cquit | endif' \ + -c 'if maparg("ci", "n") !~# "LspStatus" || maparg("co", "n") !~# "lsp-document-symbol-search" | cquit | endif' \ + -c 'if maparg("cd", "n") !=# "" || maparg("ck", "n") !=# "" || maparg("cp", "n") !=# "" || maparg("cn", "n") !=# "" | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_keymap_style = "classic"' \ + -c 'source .vimrc' \ + -c 'doautocmd User lsp_buffer_enabled' \ + -c 'if maparg("gd", "n") !=# "" || maparg("K", "n") !=# "" || maparg("gI", "n") !=# "" || maparg("gr", "n") !=# "" | cquit | endif' \ + -c 'if maparg(",dd", "n") !~# "lsp-definition" || maparg(",dt", "n") !~# "lsp-type-definition" || maparg(",di", "n") !~# "lsp-implementation" || maparg(",dr", "n") !~# "lsp-references" || maparg(",dk", "n") !~# "lsp-hover" | cquit | endif' \ + -c 'if maparg(",dp", "n") !~# "lsp-previous-diagnostic" | cquit | endif' \ + -c 'if maparg(",dn", "n") !~# "lsp-next-diagnostic" | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_keymap_style = "space"' \ + -c 'source .vimrc' \ + -c 'if mapleader !=# "\" || maplocalleader !=# "," | cquit | endif' \ + -c 'if maparg(",ff", "n") !=# "" || maparg(",w", "n") !=# "" || maparg(",mt", "n") !=# "" || maparg(",gp", "n") !=# "" || maparg("gp", "n") !=# "" | cquit | endif' \ + -c 'if maparg("f", "n") !=# "" || maparg("q", "n") !=# "" || maparg("u", "n") !=# "" || maparg("c", "n") !=# "" || maparg("x", "n") !=# "" || maparg("wm", "n") !=# "" || maparg("w+", "n") !=# "" || maparg("w-", "n") !=# "" | cquit | endif' \ + -c 'if maparg("", "n") !~# "SmartFiles" || maparg("ff", "n") !~# "SmartFiles" || maparg(",", "n") !~# "Buffers" || maparg("bd", "n") !~# "Bclose" | cquit | endif' \ + -c 'if maparg("w", "n") !~# ":w" || maparg("W", "n") !~# ":wa" || maparg("qq", "n") !~# ":q" || maparg("qx", "n") !~# ":x" || maparg("U", "n") !~# "UndotreeToggle" || maparg("fs", "n") !=# "" || maparg("bu", "n") !=# "" | cquit | endif' \ + -c 'if maparg("gl", "n") !~# "Git log" || maparg("gC", "n") !~# "Commits" | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_keymap_style = "space"' \ + -c 'source .vimrc' \ + -c 'doautocmd User lsp_buffer_enabled' \ + -c 'if maparg("cf", "n") !~# "lsp-document-format" | cquit | endif' \ + -c 'if maparg("gd", "n") !~# "lsp-definition" || maparg("gr", "n") !~# "lsp-references" || maparg("K", "n") !~# "lsp-hover" | cquit | endif' \ + -c 'if maparg("cd", "n") !=# "" || maparg("ck", "n") !=# "" | cquit | endif' \ + -c 'if maparg("f", "n") !=# "" || maparg("c", "n") !=# "" | cquit | endif' \ + -c 'if maparg(",f", "n") !=# "" || maparg(",dd", "n") !=# "" | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ + -c 'call feedkeys("\?", "xt")' \ + -c "redir! > $TMP_ROOT/cheat-default.txt" \ + -c 'silent %print' \ + -c 'redir END' \ + -c 'qa!' 2>&1 + grep -Fq ':ChopsticksStatus check LSP setup' "$TMP_ROOT/cheat-default.txt" + grep -Fq 'SPC SPC files' "$TMP_ROOT/cheat-default.txt" + grep -Fq 'gd definition' "$TMP_ROOT/cheat-default.txt" + grep -Fq 'K hover docs' "$TMP_ROOT/cheat-default.txt" + grep -Fq '[d ]d LSP diagnostics' "$TMP_ROOT/cheat-default.txt" + grep -Fq 'hjkl navigate splits' "$TMP_ROOT/cheat-default.txt" + grep -Fq 'SPC w save' "$TMP_ROOT/cheat-default.txt" + grep -Fq 's+2ch easymotion jump' "$TMP_ROOT/cheat-default.txt" + grep -Fq 'cl / cc native s / S substitute' "$TMP_ROOT/cheat-default.txt" + grep -Fq ':ChopsticksTutor practice' "$TMP_ROOT/cheat-default.txt" + if grep -Eq 'Ctrl\\+p find file|Ctrl\\+hjkl navigate splits|Ctrl\\+s save|jk exit insert|SPC fs save|SPC cd definition|SPC ck hover|SPC wm|SPC w\\+/-|\\[g \\]g LSP diagnostics' "$TMP_ROOT/cheat-default.txt"; then + cat "$TMP_ROOT/cheat-default.txt" + exit 1 + fi + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ + -c 'ChopsticksCheatSheet' \ + -c "redir! > $TMP_ROOT/cheat-command.txt" \ + -c 'silent %print' \ + -c 'redir END' \ + -c 'qa!' 2>&1 + grep -Fq 'SPC SPC files' "$TMP_ROOT/cheat-command.txt" + grep -Fq ':ChopsticksTutor practice' "$TMP_ROOT/cheat-command.txt" + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_keymap_style = "classic"' \ + -c 'source .vimrc' \ + -c 'normal ,?' \ + -c "redir! > $TMP_ROOT/cheat-classic.txt" \ + -c 'silent %print' \ + -c 'redir END' \ + -c 'qa!' 2>&1 + grep -Fq ',ff files' "$TMP_ROOT/cheat-classic.txt" + grep -Fq ',dd definition' "$TMP_ROOT/cheat-classic.txt" + grep -Fq ',dk hover docs' "$TMP_ROOT/cheat-classic.txt" + grep -Fq ',dp ,dn LSP diagnostics' "$TMP_ROOT/cheat-classic.txt" + if grep -Eq ',gp push|,gl pull' "$TMP_ROOT/cheat-classic.txt"; then + cat "$TMP_ROOT/cheat-classic.txt" + exit 1 + fi + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_profile = "minimal"' \ + -c 'source .vimrc' \ + -c 'call feedkeys("\?", "xt")' \ + -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 'SPC rr run file' "$TMP_ROOT/cheat.txt" + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_keymap_style = "space"' \ + -c 'source .vimrc' \ + -c 'call feedkeys("\?", "xt")' \ + -c "redir! > $TMP_ROOT/cheat-space.txt" \ + -c 'silent %print' \ + -c 'redir END' \ + -c 'qa!' 2>&1 + grep -Fq 'SPC w save' "$TMP_ROOT/cheat-space.txt" + grep -Fq 'gd definition' "$TMP_ROOT/cheat-space.txt" + grep -Fq 'SPC gl log graph' "$TMP_ROOT/cheat-space.txt" + grep -Fq 's+2ch easymotion jump' "$TMP_ROOT/cheat-space.txt" + if grep -Eq ',w save|,gp push|SPC gp push|SPC gl pull|SPC fs save|SPC cd definition|SPC f format' "$TMP_ROOT/cheat-space.txt"; then + cat "$TMP_ROOT/cheat-space.txt" + exit 1 + fi + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ + -c 'ChopsticksTutor' \ + -c "redir! > $TMP_ROOT/tutor-default.txt" \ + -c 'silent %print' \ + -c 'redir END' \ + -c 'qa!' 2>&1 + grep -Fq 'chopsticks tutor' "$TMP_ROOT/tutor-default.txt" + grep -Fq 'SPC ? active cheat sheet' "$TMP_ROOT/tutor-default.txt" + grep -Fq 's + 2 chars visible jump' "$TMP_ROOT/tutor-default.txt" + grep -Fq 'cl / cc native s / S substitute' "$TMP_ROOT/tutor-default.txt" + grep -Fq 'gd / gr / K definition / refs / docs' "$TMP_ROOT/tutor-default.txt" + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ + -c 'let g:chopsticks_keymap_style = "classic"' \ + -c 'source .vimrc' \ + -c 'ChopsticksTutor' \ + -c "redir! > $TMP_ROOT/tutor-classic.txt" \ + -c 'silent %print' \ + -c 'redir END' \ + -c 'qa!' 2>&1 + grep -Fq 'classic layout' "$TMP_ROOT/tutor-classic.txt" + grep -Fq ',? active cheat sheet' "$TMP_ROOT/tutor-classic.txt" + grep -Fq ',S + 2 chars EasyMotion jump' "$TMP_ROOT/tutor-classic.txt" + + XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N README.md \ + -c 'let g:chopsticks_keymap_style = "space"' \ + -c 'source .vimrc' \ + -c 'set filetype=markdown' \ + -c 'if maparg(",mt", "n") !~# "Toc" || maparg(",mp", "n") !~# "PrevimOpen" | cquit | endif' \ + -c 'qa!' 2>&1 + + 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") !~# "easymotion-overwin-f2" | cquit | endif' \ + -c 'if maparg("w", "n") =~# "!" | cquit | endif' \ + -c 'if !&swapfile || !&writebackup || &directory !~# "\.vim/.swap" | cquit | endif' \ + -c 'qa!' 2>&1 + + XDG_CONFIG_HOME="$EMPTY_XDG" 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 + + mkdir -p "$TMP_ROOT/fake-bin" "$TMP_ROOT/c runner" + cat > "$TMP_ROOT/fake-bin/gcc" <<'GCCEOF' +#!/usr/bin/env bash +set -eu +printf '%s\n' "$@" > "$GCC_ARGS" +out="" +while [ "$#" -gt 0 ]; do + if [ "$1" = "-o" ]; then + shift + out="$1" + fi + shift || true +done +test -n "$out" +printf '%s\n' '#!/usr/bin/env bash' 'exit 0' > "$out" +chmod +x "$out" +GCCEOF + chmod +x "$TMP_ROOT/fake-bin/gcc" + c_file="$TMP_ROOT/c runner/main.c" + c_file_real="$(cd "$TMP_ROOT/c runner" && pwd -P)/main.c" + printf '%s\n' 'int main(void) { return 0; }' > "$c_file" + GCC_ARGS="$TMP_ROOT/gcc-args.txt" \ + PATH="$TMP_ROOT/fake-bin:$PATH" \ + XDG_CONFIG_HOME="$EMPTY_XDG" \ + vim -u .vimrc -i NONE -es -N "$c_file" \ + -c 'set filetype=c' \ + -c 'call feedkeys("\rr", "xt")' \ + -c 'qa!' 2>&1 + c_out="$(sed -n '2p' "$TMP_ROOT/gcc-args.txt")" + test -n "$c_out" + test "$c_out" != "/tmp/a.out" + test ! -e "$c_out" + grep -Fxq "$c_file_real" "$TMP_ROOT/gcc-args.txt" + + 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 }' +} + +check_vim diff --git a/scripts/test.sh b/scripts/test.sh index 41d9a7c..6bec396 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,31 +1,9 @@ #!/usr/bin/env bash -# Project test runner. CI calls the same groups that maintainers can run locally. +# Project test runner. CI calls the same groups 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 - } -} +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" usage() { cat <<'EOF' @@ -50,385 +28,17 @@ 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/no-git-bin" - printf '%s\n' \ - '#!/usr/bin/env bash' \ - "echo \"brew was called\" >> \"\$BREW_LOG\"" \ - 'exit 42' > "$TMP_ROOT/no-git-bin/brew" - chmod +x "$TMP_ROOT/no-git-bin/brew" - BREW_LOG="$TMP_ROOT/no-git-brew.log" \ - PATH="$TMP_ROOT/no-git-bin" \ - CHOPSTICKS_DEST="$TMP_ROOT/no-git-bootstrap" \ - /bin/bash ./get.sh --dry-run --profile=full \ - | tee "$TMP_ROOT/get-no-git-dry-run.txt" - grep -q 'Would require: git' "$TMP_ROOT/get-no-git-dry-run.txt" - grep -q 'Would clone' "$TMP_ROOT/get-no-git-dry-run.txt" - test ! -e "$TMP_ROOT/no-git-brew.log" - test ! -e "$TMP_ROOT/no-git-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 - - XDG_CONFIG_HOME="$EMPTY_XDG" 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") || has_key(g:plugs, "auto-pairs") | 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") || has_key(g:plugs, "auto-pairs") | 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") || has_key(g:plugs, "auto-pairs") | cquit | endif' \ - -c 'qa!' 2>&1 - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ - -c 'ChopsticksStatus' \ - -c "redir! > $TMP_ROOT/status-default.txt" \ - -c 'silent %print' \ - -c 'redir END' \ - -c 'qa!' 2>&1 - if grep -Fq 'vim-lsp not loaded' "$TMP_ROOT/status-default.txt"; then - cat "$TMP_ROOT/status-default.txt" - exit 1 - fi - grep -Fq 'OK vim-lsp stack (installed)' "$TMP_ROOT/status-default.txt" - grep -Fq 'python (:LspInstallServer in a python file)' "$TMP_ROOT/status-default.txt" - grep -Fq 'LSP actions are buffer-local and start after a server attaches.' "$TMP_ROOT/status-default.txt" - grep -Fq 'Open that filetype and run :LspInstallServer once.' "$TMP_ROOT/status-default.txt" - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ - -c 'let last_change_map = nr2char(96) . "[v" . nr2char(96) . "]"' \ - -c 'if maparg("0", "n") !=# "" || maparg("0", "v") !=# "" || maparg("Y", "n") !=# "" || maparg("Q", "n") !=# "" || maparg("", "n") !=# "" || maparg("//", "v") !=# "" || maparg("gV", "n") !=# "" || maparg("jk", "i") !=# "" || maparg("", "n") !=# "" || maparg("", "i") !=# "" || maparg("", "n") !=# "" || maparg("", "n") !=# "" || maparg("", "n") !=# "" || maparg("", "n") !=# "" || maparg("", "n") !=# "" || maparg("", "c") !=# "" || maparg("", "c") !=# "" || maparg("w!!", "c") !=# "" | cquit | endif' \ - -c 'if has_key(g:plugs, "auto-pairs") || maparg("", "i") =~# "pumvisible" || maparg("", "i") =~# "pumvisible" || maparg("", "i") =~# "asyncomplete#close_popup" || maparg("", "i") =~# "AutoPairs" | cquit | endif' \ - -c 'if maparg("", "t") !=# "" || maparg("", "t") !=# "" || maparg("", "t") !=# "" || maparg("", "t") !=# "" || maparg("", "t") !=# "" | cquit | endif' \ - -c 'if maparg(",/", "v") !~# "escape" || maparg(",v", "n") !=# last_change_map || maparg(",ff", "n") !~# "SmartFiles" | cquit | endif' \ - -c 'qa!' 2>&1 - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ - -c 'let g:chopsticks_enable_jk_escape = 1' \ - -c 'source .vimrc' \ - -c 'if maparg("jk", "i") !~# "" | cquit | endif' \ - -c 'qa!' 2>&1 - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ - -c 'let g:chopsticks_enable_ctrl_s_save = 1' \ - -c 'let g:chopsticks_enable_sudo_save_bang = 1' \ - -c 'let g:chopsticks_enable_completion_keymaps = 1' \ - -c 'source .vimrc' \ - -c 'if maparg("", "n") !~# ":w" || maparg("", "i") !~# ":w" || maparg("w!!", "c") !~# "sudo tee" | cquit | endif' \ - -c 'if maparg("", "i") !~# "pumvisible" || maparg("", "i") !~# "pumvisible" || maparg("", "i") !~# "asyncomplete#close_popup" | cquit | endif' \ - -c 'qa!' 2>&1 - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ - -c 'let g:chopsticks_enable_auto_pairs = 1' \ - -c 'source .vimrc' \ - -c 'if !has_key(g:plugs, "auto-pairs") | cquit | endif' \ - -c 'qa!' 2>&1 - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ - -c 'let g:chopsticks_enable_terminal_keymaps = 1' \ - -c 'source .vimrc' \ - -c 'if has("terminal") && (maparg("", "t") !~# "" || maparg("", "t") !~# "h" || maparg("", "t") !~# "j" || maparg("", "t") !~# "k" || maparg("", "t") !~# "l") | cquit | endif' \ - -c 'qa!' 2>&1 - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ - -c 'if &exrc || &secure | cquit | endif' \ - -c 'qa!' 2>&1 - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ - -c 'let g:chopsticks_enable_exrc = 1' \ - -c 'source .vimrc' \ - -c 'if !&exrc || !&secure | cquit | endif' \ - -c 'qa!' 2>&1 - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ - -c 'if maparg(",F", "n") !=# "" | cquit | endif' \ - -c 'if maparg(",F", "v") !~# "=" | cquit | endif' \ - -c 'qa!' 2>&1 - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ - -c 'let g:chopsticks_enable_reindent_file = 1' \ - -c 'source .vimrc' \ - -c 'if maparg(",F", "n") !~# "gg=G" | cquit | endif' \ - -c 'qa!' 2>&1 - - TERM=xterm-256color XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ - -c 'if g:is_tty || &ttimeoutlen != 10 | cquit | endif' \ - -c 'qa!' 2>&1 - - TERM=linux XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ - -c 'if !g:is_tty || &ttimeoutlen != 50 | cquit | endif' \ - -c 'qa!' 2>&1 - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ - -c 'if !exists("loaded_gzip") || !exists("loaded_logiPat") || !exists("loaded_rrhelper") || !exists("loaded_spellfile_plugin") | cquit | endif' \ - -c 'qa!' 2>&1 - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ - -c 'silent! delcommand LspStatus' \ - -c 'silent! delcommand LspInstallServer' \ - -c 'ChopsticksStatus' \ - -c "redir! > $TMP_ROOT/status-lsp-not-loaded.txt" \ - -c 'silent %print' \ - -c 'redir END' \ - -c 'qa!' 2>&1 - grep -Fq 'OK vim-lsp stack (installed; not loaded yet)' "$TMP_ROOT/status-lsp-not-loaded.txt" - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \ - -c 'let g:chopsticks_profile = "minimal"' \ - -c 'source .vimrc' \ - -c 'ChopsticksStatus' \ - -c "redir! > $TMP_ROOT/status-minimal.txt" \ - -c 'silent %print' \ - -c 'redir END' \ - -c 'qa!' 2>&1 - grep -Fq 'off vim-lsp stack (LSP disabled by profile)' "$TMP_ROOT/status-minimal.txt" - grep -Fq 'off python (LSP disabled by profile)' "$TMP_ROOT/status-minimal.txt" - if grep -Fq 'LSP actions are buffer-local' "$TMP_ROOT/status-minimal.txt"; then - cat "$TMP_ROOT/status-minimal.txt" - exit 1 - fi - - mkdir -p "$TMP_ROOT/missing-home/.vim/autoload" - cp "$HOME/.vim/autoload/plug.vim" "$TMP_ROOT/missing-home/.vim/autoload/plug.vim" - HOME="$TMP_ROOT/missing-home" XDG_CONFIG_HOME="$EMPTY_XDG" \ - vim -u .vimrc -i NONE -es -N \ - -c 'ChopsticksStatus' \ - -c "redir! > $TMP_ROOT/status-missing-plugin.txt" \ - -c 'silent %print' \ - -c 'redir END' \ - -c 'qa!' 2>&1 - grep -Fq 'vim-lsp not installed; run :PlugInstall' "$TMP_ROOT/status-missing-plugin.txt" - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ - -c 'doautocmd User lsp_buffer_enabled' \ - -c 'if maparg("gd", "n") !=# "" || maparg("K", "n") !=# "" || maparg("gi", "n") !=# "" || maparg("gr", "n") !=# "" | cquit | endif' \ - -c 'if maparg(",dd", "n") !~# "lsp-definition" | cquit | endif' \ - -c 'if maparg(",dt", "n") !~# "lsp-type-definition" | cquit | endif' \ - -c 'if maparg(",di", "n") !~# "lsp-implementation" | cquit | endif' \ - -c 'if maparg(",dr", "n") !~# "lsp-references" | cquit | endif' \ - -c 'if maparg(",dk", "n") !~# "lsp-hover" | cquit | endif' \ - -c 'if maparg(",dp", "n") !~# "lsp-previous-diagnostic" | cquit | endif' \ - -c 'if maparg(",dn", "n") !~# "lsp-next-diagnostic" | cquit | endif' \ - -c 'qa!' 2>&1 - - XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \ - -c 'normal ,?' \ - -c "redir! > $TMP_ROOT/cheat-default.txt" \ - -c 'silent %print' \ - -c 'redir END' \ - -c 'qa!' 2>&1 - grep -Fq ':ChopsticksStatus check LSP setup' "$TMP_ROOT/cheat-default.txt" - grep -Fq ',ff files' "$TMP_ROOT/cheat-default.txt" - grep -Fq ',dd definition' "$TMP_ROOT/cheat-default.txt" - grep -Fq ',dk hover docs' "$TMP_ROOT/cheat-default.txt" - grep -Fq ',dp ,dn LSP diagnostics' "$TMP_ROOT/cheat-default.txt" - grep -Fq 'hjkl navigate splits' "$TMP_ROOT/cheat-default.txt" - if grep -Eq 'Ctrl\\+p find file|Ctrl\\+hjkl navigate splits|Ctrl\\+s save|jk exit insert|gd definition|K hover docs|\\[g \\]g LSP diagnostics' "$TMP_ROOT/cheat-default.txt"; then - cat "$TMP_ROOT/cheat-default.txt" - exit 1 - fi - - XDG_CONFIG_HOME="$EMPTY_XDG" 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 - - XDG_CONFIG_HOME="$EMPTY_XDG" 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 - - mkdir -p "$TMP_ROOT/fake-bin" "$TMP_ROOT/c runner" - cat > "$TMP_ROOT/fake-bin/gcc" <<'GCCEOF' -#!/usr/bin/env bash -set -eu -printf '%s\n' "$@" > "$GCC_ARGS" -out="" -while [ "$#" -gt 0 ]; do - if [ "$1" = "-o" ]; then - shift - out="$1" - fi - shift || true -done -test -n "$out" -printf '%s\n' '#!/usr/bin/env bash' 'exit 0' > "$out" -chmod +x "$out" -GCCEOF - chmod +x "$TMP_ROOT/fake-bin/gcc" - c_file="$TMP_ROOT/c runner/main.c" - c_file_real="$(cd "$TMP_ROOT/c runner" && pwd -P)/main.c" - printf '%s\n' 'int main(void) { return 0; }' > "$c_file" - GCC_ARGS="$TMP_ROOT/gcc-args.txt" \ - PATH="$TMP_ROOT/fake-bin:$PATH" \ - XDG_CONFIG_HOME="$EMPTY_XDG" \ - vim -u .vimrc -i NONE -es -N "$c_file" \ - -c 'set filetype=c' \ - -c 'normal ,cr' \ - -c 'qa!' 2>&1 - c_out="$(sed -n '2p' "$TMP_ROOT/gcc-args.txt")" - test -n "$c_out" - test "$c_out" != "/tmp/a.out" - test ! -e "$c_out" - grep -Fxq "$c_file_real" "$TMP_ROOT/gcc-args.txt" - - 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 + quick | shell | docs | installer | bootstrap) + bash "$SCRIPT_DIR/test-quick.sh" "$1" + ;; + vim) + bash "$SCRIPT_DIR/test-vim.sh" ;; - shell) check_shell ;; - docs) check_docs ;; - installer) check_installer_modes ;; - bootstrap) check_bootstrap ;; - vim) check_vim ;; all) - run_group quick - check_vim + bash "$SCRIPT_DIR/test-quick.sh" quick + bash "$SCRIPT_DIR/test-vim.sh" ;; list | --list) list_groups ;; -h | --help) usage ;;