Compare commits

...

23 commits

Author SHA1 Message Date
8e6dcc6e97
Fix opt-in keymap install messaging (#61)
Some checks failed
test / startup (macos-latest) (push) Has been cancelled
test / startup (ubuntu-latest) (push) Has been cancelled
test / shellcheck (push) Has been cancelled
test / docs (push) Has been cancelled
2026-05-13 18:28:31 +08:00
2514d2e803
Fix noninteractive plugin installation (#59) 2026-05-13 18:22:12 +08:00
b2835d3cae
Make terminal keymaps opt-in (#57) 2026-05-13 17:24:45 +08:00
23f6ed98ec
Make auto-pairs opt-in (#55) 2026-05-13 17:18:01 +08:00
043e8e4a58
Make completion keymaps opt-in (#53) 2026-05-13 17:08:21 +08:00
8f89939a57
Make sudo-save mapping opt-in (#51) 2026-05-13 15:47:59 +08:00
ea8c3054e1
Move visual selection search to leader mapping (#49) 2026-05-13 15:01:45 +08:00
2aa28304ca
Restore command-line Ctrl-P and Ctrl-N (#47) 2026-05-13 14:50:27 +08:00
ff0de570de
Move last-change selection to leader mapping (#45) 2026-05-13 14:44:58 +08:00
fd081a2397
Make Ctrl-S save opt-in (#42)
* Make Ctrl-S save opt in

* Avoid hanging CI Vim installation

* Keep Homebrew Vim on macOS CI
2026-05-13 14:32:50 +08:00
76aa3efd72
Make jk escape opt in (#40) 2026-05-13 14:05:22 +08:00
f278e9f3b6
Restore native Ctrl window keys (#38) 2026-05-13 13:56:28 +08:00
d03f7129d8
Move file search to leader mapping (#36) 2026-05-13 13:50:27 +08:00
e1c2ea3888
Restore native Space motion behavior (#34) 2026-05-13 13:39:59 +08:00
861e18b86c
Restore native Q behavior (#32) 2026-05-13 13:32:35 +08:00
8e2505bfca
Restore native Y yank behavior (#30) 2026-05-13 13:28:21 +08:00
49aae12415
Restore native zero motion (#28) 2026-05-13 13:24:04 +08:00
2f7d488c8c
Move LSP navigation to native-first mappings (#26) 2026-05-13 13:18:08 +08:00
5544b74545
Improve first-time LSP setup guidance (#25) 2026-05-13 12:45:53 +08:00
c98d04200c
Document native-first keymap policy (#23) 2026-05-13 12:39:40 +08:00
9fde301a2c
Use temp output for C file runner (#21) 2026-05-13 12:39:08 +08:00
097c8abcd7
Report LSP status without autoload false negatives (#20) 2026-05-13 12:38:32 +08:00
1c54077487
Make bootstrap dry-run avoid git installation (#19) 2026-05-13 12:37:46 +08:00
14 changed files with 396 additions and 115 deletions

View file

@ -16,11 +16,15 @@ jobs:
- uses: actions/checkout@v4
- name: Install Vim
timeout-minutes: 5
run: |
if [ "$(uname)" = "Darwin" ]; then
brew install vim
elif command -v vim >/dev/null 2>&1; then
vim --version | head -1
else
sudo apt-get update && sudo apt-get install -y vim
sudo apt-get update
sudo apt-get install -y vim
fi
- name: Install vim-plug

2
.vimrc
View file

@ -11,6 +11,8 @@ endif
if exists('g:chopsticks_loaded') | finish | endif
let g:chopsticks_loaded = 1
let g:surround_no_insert_mappings = get(g:, 'surround_no_insert_mappings', 1)
function! s:load(mod) abort
execute 'source ' . fnameescape(g:chopsticks_dir . '/modules/' . a:mod . '.vim')
endfunction

View file

@ -5,16 +5,18 @@
1. **No Node.js dependencies.** The LSP engine is pure VimScript. Some language servers need Node — that's fine. The config itself must not.
2. **Startup matters.** Run `vim -u .vimrc -i NONE --startuptime /tmp/s.log -es -N -c qa!` before and after. If your change adds >1ms, it needs a good reason.
3. **Works on TTY.** Test over SSH. If it breaks in a terminal without true color, fix it or gate it behind `g:is_tty`.
4. **One module, one concern.** Don't put git config in lsp.vim.
4. **Native-first keymaps.** Enhance Vim's native behavior instead of replacing it. Do not override built-in motions, operators, text objects, or help-oriented keys for discoverability alone; prefer leader-prefixed or otherwise non-conflicting ergonomic mappings.
5. **One module, one concern.** Don't put git config in lsp.vim.
## Adding a plugin
1. Add the `Plug` line to `modules/plugins.vim`
2. If it's not needed at startup, lazy-load it: `Plug 'foo/bar', { 'on': 'FooCommand' }`
3. Put config in the appropriate module
4. Update the cheat sheet in `modules/tools.vim` if you add keybindings
5. Run `scripts/test.sh vim` locally after installing plugins
6. Test on both macOS and Linux when changing terminal or package-manager behavior
4. Check new mappings against native Vim behavior before adding them
5. Update the cheat sheet in `modules/tools.vim` if you add keybindings
6. Run `scripts/test.sh vim` locally after installing plugins
7. Test on both macOS and Linux when changing terminal or package-manager behavior
## Local tests

View file

@ -29,17 +29,16 @@ cd ~/.vim && ./install.sh --configure-only --profile=full
| Mode | Enter | Leave |
| ------ | --------------- | ------------- |
| Normal | startup default | — |
| Insert | `i` / `a` / `o` | `Esc` or `jk` |
| Insert | `i` / `a` / `o` | `Esc` |
| Visual | `v` / `V` | `Esc` |
## Survival
```
Esc / jk back to Normal
Esc back to Normal
,w save
,x save + quit
:q! force quit
Ctrl+s save from any mode
,? cheat sheet (toggle sidebar)
```
@ -57,8 +56,8 @@ Ctrl+p fuzzy find file (git-aware)
## Write code
```
gd go to definition
K hover docs
,dd go to definition
,dk hover docs
,rn rename symbol
,ca code action
,f format
@ -92,7 +91,7 @@ Alt+j / Alt+k move line
## Navigate
```
Ctrl+h/j/k/l splits + tmux panes
<C-w>h/j/k/l splits
,h / ,l prev / next buffer
,z maximize window
,tv / ,th terminal

View file

@ -81,6 +81,12 @@ 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_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
let g:chopsticks_enable_completion_keymaps = 1 " optional: Tab/Enter completion
let g:chopsticks_enable_auto_pairs = 1 " optional: automatic pair insertion
let g:chopsticks_enable_terminal_keymaps = 1 " optional: terminal Esc/Ctrl navigation
```
`minimal` avoids LSP, ALE, completion plugins, extra language syntax plugins,
@ -96,12 +102,12 @@ active profile and only shows keys for enabled features.
Leader: `,`
```
Ctrl+p fuzzy find file gd go to definition
,rg ripgrep project K hover docs
,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
jk exit insert mode ,? cheat sheet
Esc exit insert mode ,? cheat sheet
```
<details>
@ -109,11 +115,11 @@ jk exit insert mode ,? cheat sheet
### Files
`Ctrl+p` find | `,b` buffers | `,rg` grep | `,rG` grep word | `,fh` recent | `,fl` lines | `,e` browser | `,E` browser (file dir) | `,,` last file
`,ff` find | `,b` buffers | `,rg` grep | `,rG` grep word | `,fh` recent | `,fl` lines | `,e` browser | `,E` browser (file dir) | `,,` last file
### Code
`gd` def | `gy` type | `gi` impl | `gr` refs | `K` docs | `[g` `]g` diagnostics | `[e` `]e` ALE errors | `,rn` rename | `,ca` action | `,o` outline | `,cr` run
`,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
### Edit
@ -125,7 +131,7 @@ jk exit insert mode ,? cheat sheet
### Windows
`Ctrl+hjkl` navigate (+ tmux) | `,z` maximize | `,h` `,l` buffers | `,bd` close buffer | `,=` `,` resize | `,tv` `,th` terminal | `Esc Esc` exit terminal
`<C-w>hjkl` navigate | `,z` maximize | `,h` `,l` buffers | `,bd` close buffer | `,=` `,` resize | `,tv` `,th` terminal
### Markdown
@ -216,7 +222,7 @@ Each module is self-contained. Comment out one line in `.vimrc` to disable it. A
| Plugins not loading | `:PlugInstall` then `:PlugUpdate` |
| LSP not starting | `:LspInstallServer` for current filetype |
| Colors wrong | `export COLORTERM=truecolor` in shell rc |
| `Ctrl+s` freezes | `stty -ixon` in shell rc |
| Optional `Ctrl+s` freezes | `stty -ixon` in shell rc |
| Everything slow | Large file? Auto-disabled >10MB |
| What's installed? | `:ChopsticksStatus` shows tools, LSP, linters |

47
get.sh
View file

@ -79,16 +79,45 @@ echo " Dest: $DEST"
# ── git ───────────────────────────────────────────────────────────────────────
step "Checking for git"
if ! command -v git >/dev/null 2>&1; then
warn "git not found — attempting to install"
if command -v apt-get >/dev/null 2>&1; then sudo apt-get install -y git >/dev/null 2>&1
elif command -v pacman >/dev/null 2>&1; then sudo pacman -S --noconfirm git >/dev/null 2>&1
elif command -v dnf >/dev/null 2>&1; then sudo dnf install -y git >/dev/null 2>&1
elif command -v brew >/dev/null 2>&1; then brew install git >/dev/null 2>&1
else die "git is required. Install it manually then re-run."; fi
command -v git >/dev/null 2>&1 || die "git install failed. Try: sudo apt install git"
HAS_GIT=0
command -v git >/dev/null 2>&1 && HAS_GIT=1
if [[ $HAS_GIT -eq 0 ]]; then
if [[ $DRY_RUN -eq 1 ]]; then
warn "git not found — would need to install git before a real install"
else
warn "git not found — attempting to install"
if command -v apt-get >/dev/null 2>&1; then sudo apt-get install -y git >/dev/null 2>&1
elif command -v pacman >/dev/null 2>&1; then sudo pacman -S --noconfirm git >/dev/null 2>&1
elif command -v dnf >/dev/null 2>&1; then sudo dnf install -y git >/dev/null 2>&1
elif command -v brew >/dev/null 2>&1; then brew install git >/dev/null 2>&1
else die "git is required. Install it manually then re-run."; fi
command -v git >/dev/null 2>&1 || die "git install failed. Try: sudo apt install git"
HAS_GIT=1
fi
fi
if [[ $HAS_GIT -eq 1 ]]; then
ok "git $(git --version | awk '{print $3}')"
elif [[ $DRY_RUN -eq 1 ]]; then
info "Would require: git"
else
die "git is required. Install it manually then re-run."
fi
if [[ $DRY_RUN -eq 1 && $HAS_GIT -eq 0 ]]; then
step "Setting up $DEST"
if [[ -d "$DEST/.git" ]]; then
info "Would inspect existing git repo at $DEST"
info "Would update it only if its origin is $REPO"
elif [[ -d "$DEST" ]]; then
die "$DEST exists but git is unavailable, so dry-run cannot verify whether it is chopsticks.
Install git and re-run dry-run for the full safety check."
else
info "Would clone $REPO to $DEST"
fi
info "Would run: bash install.sh ${INSTALLER_ARGS[*]:-(no installer options)}"
exit 0
fi
ok "git $(git --version | awk '{print $3}')"
# ── Clone or update ───────────────────────────────────────────────────────────
step "Setting up $DEST"

View file

@ -493,10 +493,21 @@ fi
# vim
[ -f "$SCRIPT_DIR/.vimrc" ] || die ".vimrc not found in $SCRIPT_DIR — is this the chopsticks repo?"
if ! command -v vim >/dev/null 2>&1; then
resolve_vim() {
if [[ $OS == "macos" ]]; then
for vim_path in /opt/homebrew/bin/vim /usr/local/bin/vim; do
[[ -x "$vim_path" ]] && { echo "$vim_path"; return; }
done
fi
command -v vim 2>/dev/null || true
}
VIM_BIN="$(resolve_vim)"
if [[ -z "$VIM_BIN" ]]; then
warn "vim not found — attempting to install"
if pkg_install vim vim vim vim 2>/dev/null; then
ok "vim installed"
VIM_BIN="$(resolve_vim)"
else
die "vim not found and could not be installed automatically.
Ubuntu/Debian: sudo apt install vim
@ -505,8 +516,9 @@ if ! command -v vim >/dev/null 2>&1; then
macOS: brew install vim"
fi
fi
ok "Found: $(vim --version | head -n1)"
vim --version | grep -q 'Vi IMproved 8\|Vi IMproved 9' || \
[[ -n "$VIM_BIN" ]] || die "vim installed but not found in PATH"
ok "Found: $("$VIM_BIN" --version | head -n1) ($VIM_BIN)"
"$VIM_BIN" --version | grep -q 'Vi IMproved 8\|Vi IMproved 9' || \
warn "Vim 8.0+ recommended for full async/LSP support — some features may not work"
# Node.js (optional — vim-lsp needs no Node.js; only npm formatters do)
@ -596,14 +608,15 @@ fi
step "Installing Vim plugins"
_vim_run() {
if { true </dev/tty; } 2>/dev/null; then
local vim_cmd="$1" batch_script
if [[ -t 1 ]] && { true </dev/tty; } 2>/dev/null; then
# Interactive terminal: vim uses alternate screen; user sees progress
vim "$@" </dev/tty
"$VIM_BIN" -u "$SCRIPT_DIR/.vimrc" +"$vim_cmd" +qall </dev/tty
else
# No TTY (SSH batch, CI): do NOT redirect stdin (causes "Error reading input" exit)
# or stdout (breaks async job callbacks — partial install).
# Redirect only stderr; escape sequences appear on stdout but installation succeeds.
vim --not-a-term "$@" 2>/dev/null
# Batch/agent/CI: avoid vim-plug's screen UI, which may try to read input.
batch_script="$_TMPDIR/vim-run.vim"
printf '%s\nqa!\n' "$vim_cmd" > "$batch_script"
"$VIM_BIN" -u "$SCRIPT_DIR/.vimrc" -i NONE -n -es -N -S "$batch_script"
fi
}
@ -628,7 +641,7 @@ qa!
VIMEOF
CHOPSTICKS_REQUIRED_PLUGINS="$required_file" \
vim -u "$SCRIPT_DIR/.vimrc" -i NONE -es -N -S "$verify_script" >/dev/null 2>&1 || return 1
"$VIM_BIN" -u "$SCRIPT_DIR/.vimrc" -i NONE -es -N -S "$verify_script" >/dev/null 2>&1 || return 1
[[ -s "$required_file" ]] || return 1
while IFS= read -r dir; do
@ -646,8 +659,8 @@ VIMEOF
if [[ -d "$HOME/.vim/plugged" ]] && [[ -n "$(find "$HOME/.vim/plugged" -mindepth 1 -maxdepth 1 2>/dev/null)" ]]; then
warn "PlugClean: removing plugins not listed in .vimrc from ~/.vim/plugged"
fi
_vim_run +'PlugClean!' +qall || true # remove plugins no longer in vimrc; ignore exit code (none expected)
_vim_run +'PlugInstall --sync' +qall || true # fzf post-install hook may exit non-zero; harmless
_vim_run 'PlugClean!' || true # remove plugins no longer in vimrc; ignore exit code (none expected)
_vim_run 'PlugInstall --sync' || true # fzf post-install hook may exit non-zero; harmless
verify_plugins || die "Plugin installation failed — retry with a stable network connection."
@ -1062,14 +1075,18 @@ fi
# ============================================================================
step "LSP language servers"
info "vim-lsp installs language servers on demand — no action needed here."
info ""
info "To install a server: open a source file in Vim and run:"
info " :LspInstallServer"
info ""
info "Supported: Python, JS/TS, Go, Rust, C/C++, Shell, HTML, CSS, JSON, YAML, Markdown, SQL"
info ""
info "For Markdown LSP (marksman), the installer already handled it above."
if [[ $CONFIG_PROFILE == "minimal" ]]; then
skip "LSP disabled by minimal profile"
else
info "vim-lsp installs language servers on demand — no action needed here."
info ""
info "To install a server: open a source file in Vim and run:"
info " :LspInstallServer"
info ""
info "Supported: Python, JS/TS, Go, Rust, C/C++, Shell, HTML, CSS, JSON, YAML, Markdown, SQL"
info ""
info "For Markdown LSP (marksman), the installer already handled it above."
fi
# ============================================================================
# Summary
@ -1109,13 +1126,18 @@ 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}Esc${NC} or ${CYAN}jk${NC} Exit insert mode → back to Normal"
echo -e " ${CYAN}Esc${NC} Exit insert mode → back to Normal"
echo -e " ${CYAN}:q!${NC} + Enter Emergency quit without saving"
echo -e " ${CYAN},x${NC} Save and quit"
echo -e " ${CYAN},?${NC} Open cheat sheet"
echo -e " ${CYAN}:LspInstallServer${NC} Install LSP for current filetype"
echo ""
echo -e "${YELLOW}[!]${NC} Ctrl+s is mapped to save in Vim."
echo " If it freezes your terminal, add this to ~/.bashrc or ~/.zshrc:"
echo -e " ${CYAN}stty -ixon${NC}"
if [[ $CONFIG_PROFILE != "minimal" ]]; then
echo -e " ${CYAN}:LspInstallServer${NC} Install LSP for current filetype"
fi
if [[ -f "$LOCAL_CONFIG" ]] && \
grep -Eq '^[[:space:]]*let[[:space:]]+g:chopsticks_enable_ctrl_s_save[[:space:]]*=[[:space:]]*1([[:space:]]|$)' "$LOCAL_CONFIG"; then
echo ""
echo -e "${YELLOW}[!]${NC} Ctrl+s is mapped to save in Vim."
echo " If it freezes your terminal, add this to ~/.bashrc or ~/.zshrc:"
echo -e " ${CYAN}stty -ixon${NC}"
fi
echo ""

View file

@ -108,12 +108,7 @@ nnoremap <leader>h :bprevious<cr>
nnoremap <leader>cd :lcd %:p:h<cr>:pwd<cr>
nnoremap 0 ^
vnoremap 0 ^
nnoremap gV `[v`]
cnoremap <C-p> <Up>
cnoremap <C-n> <Down>
nnoremap <leader>v `[v`]
nnoremap <M-j> :m .+1<CR>==
nnoremap <M-k> :m .-2<CR>==
@ -127,12 +122,9 @@ nnoremap <silent> <F3> :set invnumber<CR>:echo 'Line numbers: ' . (&number ? 'ON
nnoremap <silent> <F4> :set invrelativenumber<CR>:echo 'Relative numbers: ' . (&relativenumber ? 'ON' : 'OFF')<CR>
nnoremap <silent> <F6> :set list!<CR>:echo 'List chars: ' . (&list ? 'ON' : 'OFF')<CR>
nnoremap <space> za
nnoremap Y y$
nnoremap Q <nop>
inoremap jk <Esc>
if get(g:, 'chopsticks_enable_jk_escape', 0)
inoremap jk <Esc>
endif
vnoremap < <gv
vnoremap > >gv
@ -140,10 +132,12 @@ vnoremap > >gv
nnoremap n nzzzv
nnoremap N Nzzzv
vnoremap // y/\V<C-r>=escape(@",'/\')<CR><CR>
vnoremap <leader>/ y/\V<C-r>=escape(@",'/\')<CR><CR>
nnoremap <silent> <C-s> :w<CR>
inoremap <silent> <C-s> <C-o>:w<CR>
if get(g:, 'chopsticks_enable_ctrl_s_save', 0)
nnoremap <silent> <C-s> :w<CR>
inoremap <silent> <C-s> <C-o>:w<CR>
endif
nnoremap <C-d> <C-d>zz
vnoremap <C-d> <C-d>zz

View file

@ -24,6 +24,10 @@ let g:chopsticks_enable_ui_extras = get(g:, 'chopsticks_enable_ui_extras',
\ !s:profile_minimal)
let g:chopsticks_enable_markdown_preview = get(g:,
\ 'chopsticks_enable_markdown_preview', !s:profile_minimal)
let g:chopsticks_enable_auto_pairs = get(g:,
\ 'chopsticks_enable_auto_pairs', 0)
let g:chopsticks_enable_terminal_keymaps = get(g:,
\ 'chopsticks_enable_terminal_keymaps', 0)
let g:chopsticks_markdown_lint = get(g:, 'chopsticks_markdown_lint',
\ s:profile_full)

View file

@ -51,9 +51,11 @@ let g:asyncomplete_auto_popup = 1
let g:asyncomplete_auto_completeopt = 0
let g:asyncomplete_popup_delay = 50
inoremap <expr> <Tab> pumvisible() ? "\<C-n>" : "\<Tab>"
inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>"
inoremap <expr> <CR> pumvisible() ? asyncomplete#close_popup() : "\<CR>"
if get(g:, 'chopsticks_enable_completion_keymaps', 0)
inoremap <expr> <Tab> pumvisible() ? "\<C-n>" : "\<Tab>"
inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>"
inoremap <expr> <CR> pumvisible() ? asyncomplete#close_popup() : "\<CR>"
endif
" ── Buffer Keymaps ──────────────────────────────────────────────────────────
@ -63,14 +65,14 @@ function! s:on_lsp_buffer_enabled() abort
setlocal signcolumn=yes
endif
nmap <buffer> gd <plug>(lsp-definition)
nmap <buffer> gy <plug>(lsp-type-definition)
nmap <buffer> gi <plug>(lsp-implementation)
nmap <buffer> gr <plug>(lsp-references)
nmap <buffer> [g <plug>(lsp-previous-diagnostic)
nmap <buffer> ]g <plug>(lsp-next-diagnostic)
nmap <buffer> <leader>dd <plug>(lsp-definition)
nmap <buffer> <leader>dt <plug>(lsp-type-definition)
nmap <buffer> <leader>di <plug>(lsp-implementation)
nmap <buffer> <leader>dr <plug>(lsp-references)
nmap <buffer> <leader>dp <plug>(lsp-previous-diagnostic)
nmap <buffer> <leader>dn <plug>(lsp-next-diagnostic)
nmap <buffer> K <plug>(lsp-hover)
nmap <buffer> <leader>dk <plug>(lsp-hover)
nmap <buffer> <leader>rn <plug>(lsp-rename)
nmap <buffer> <leader>ca <plug>(lsp-code-action)

View file

@ -47,7 +47,7 @@ function! s:SmartFiles() abort
endfunction
if exists('g:plugs["fzf.vim"]')
nnoremap <C-p> :call <SID>SmartFiles()<CR>
nnoremap <leader>ff :call <SID>SmartFiles()<CR>
nnoremap <leader>b :Buffers<CR>
nnoremap <leader>rg :Rg<CR>
nnoremap <leader>rG :RgWord<CR>
@ -86,15 +86,6 @@ command! -bang -nargs=* RgWord
\ .shellescape(expand('<cword>')), 1, s:Preview(), <bang>0)
command! -bang -nargs=? GFiles call fzf#vim#gitfiles(<q-args>, s:Preview(), <bang>0)
" ── Window Navigation ───────────────────────────────────────────────────────
if empty($TMUX)
nnoremap <C-h> <C-w>h
nnoremap <C-j> <C-w>j
nnoremap <C-k> <C-w>k
nnoremap <C-l> <C-w>l
endif
" ── Window Maximize Toggle ──────────────────────────────────────────────────
function! s:ToggleMaximize() abort
@ -115,9 +106,11 @@ nnoremap <silent> <leader>z :call <SID>ToggleMaximize()<CR>
if has('terminal')
nnoremap <leader>tv :terminal<CR>
nnoremap <leader>th :terminal ++rows=10<CR>
tnoremap <Esc><Esc> <C-\><C-n>
tnoremap <C-h> <C-\><C-n><C-w>h
tnoremap <C-j> <C-\><C-n><C-w>j
tnoremap <C-k> <C-\><C-n><C-w>k
tnoremap <C-l> <C-\><C-n><C-w>l
if g:chopsticks_enable_terminal_keymaps
tnoremap <Esc><Esc> <C-\><C-n>
tnoremap <C-h> <C-\><C-n><C-w>h
tnoremap <C-j> <C-\><C-n><C-w>j
tnoremap <C-k> <C-\><C-n><C-w>k
tnoremap <C-l> <C-\><C-n><C-w>l
endif
endif

View file

@ -26,9 +26,12 @@ Plug 'tpope/vim-commentary'
Plug 'tpope/vim-repeat'
Plug 'tpope/vim-sleuth'
Plug 'wellle/targets.vim'
Plug 'jiangmiao/auto-pairs'
Plug 'easymotion/vim-easymotion', { 'on': '<Plug>(easymotion' }
if g:chopsticks_enable_auto_pairs
Plug 'jiangmiao/auto-pairs'
endif
if g:chopsticks_enable_lint
" ── Linting & Formatting ────────────────────────────────────────────────
Plug 'dense-analysis/ale'

View file

@ -113,7 +113,11 @@ function! s:RunFile() abort
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' | execute '!gcc -o /tmp/a.out ' . l:file . ' && /tmp/a.out'
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
@ -124,7 +128,9 @@ nnoremap <leader>cr :call <SID>RunFile()<CR>
" ── Sudo Save ───────────────────────────────────────────────────────────────
cnoremap w!! w !sudo tee > /dev/null %
if get(g:, 'chopsticks_enable_sudo_save_bang', 0)
cnoremap w!! w !sudo tee > /dev/null %
endif
" ── QuickFix ────────────────────────────────────────────────────────────────
@ -147,12 +153,58 @@ function! s:Off(name, reason) abort
return ' off ' . a:name . ' (' . a:reason . ')'
endfunction
function! s:LspCheck(ft, server) abort
if !get(g:, 'chopsticks_enable_lsp', 1)
return s:Off(a:ft, 'LSP disabled by profile')
function! s:PlugDir(name) abort
if !exists('g:plugs') || !has_key(g:plugs, a:name)
return ''
endif
if !exists('*lsp#get_server_names')
return ' -- ' . a:ft . ' (vim-lsp not loaded)'
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)
@ -178,6 +230,11 @@ function! s:ChopsticksStatus() abort
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'))
@ -271,7 +328,7 @@ function! s:CheatSheet() abort
\ ' ─────────────────────────────',
\ '',
\ ' ── files ──────────────────',
\ ' Ctrl+p find file',
\ ' ,ff files',
\ ' ,b buffers',
\ ' ,rg grep project',
\ ' ,rG grep word',
@ -288,17 +345,18 @@ function! s:CheatSheet() abort
if l:has_lsp
call extend(l:lines, [
\ ' gd definition',
\ ' gy type definition',
\ ' gi implementation',
\ ' gr references',
\ ' K hover docs',
\ ' ,dd definition',
\ ' ,dt type definition',
\ ' ,di implementation',
\ ' ,dr references',
\ ' ,dk hover docs',
\ ' ,rn rename',
\ ' ,ca code action',
\ ' ,f format',
\ ' ,o outline',
\ ' [g ]g LSP diagnostics',
\ ' ,dp ,dn LSP diagnostics',
\ ' :LspInstallServer setup LSP',
\ ' :ChopsticksStatus check LSP setup',
\ ])
endif
@ -346,7 +404,7 @@ function! s:CheatSheet() abort
\ ' [x ]x conflict markers',
\ '',
\ ' ── windows ───────────────',
\ ' Ctrl+hjkl navigate splits',
\ ' <C-w>hjkl navigate splits',
\ ' ,h ,l prev / next buf',
\ ' ,bd close buffer',
\ ' ,z maximize toggle',
@ -366,9 +424,7 @@ function! s:CheatSheet() abort
\ ' ,w save',
\ ' ,q quit',
\ ' ,x save + quit',
\ ' Ctrl+s save (any mode)',
\ ' jk exit insert',
\ ' :w!! sudo save',
\ ' Esc exit insert',
\ ' ,ev edit vimrc',
\ ' ,sv reload vimrc',
\ ' :ChopsticksStatus health',

View file

@ -94,6 +94,22 @@ check_bootstrap() {
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
@ -152,7 +168,7 @@ check_vim() {
vim -u NONE -i NONE -es -N \
-c 'let g:chopsticks_profile = "minimal"' \
-c 'source .vimrc' \
-c 'if has_key(g:plugs, "ale") || has_key(g:plugs, "vim-lsp") || has_key(g:plugs, "vim-lsp-settings") || has_key(g:plugs, "asyncomplete.vim") | cquit | endif' \
-c '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"
@ -160,16 +176,131 @@ check_vim() {
vim -u NONE -i NONE -es -N \
-c "let g:chopsticks_local_config = '$TMP_ROOT/local/config.vim'" \
-c 'source .vimrc' \
-c 'if g:chopsticks_profile !=# "minimal" || has_key(g:plugs, "ale") || has_key(g:plugs, "vim-lsp") | cquit | endif' \
-c '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") | cquit | endif' \
-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("<Space>", "n") !=# "" || maparg("//", "v") !=# "" || maparg("gV", "n") !=# "" || maparg("jk", "i") !=# "" || maparg("<C-s>", "n") !=# "" || maparg("<C-s>", "i") !=# "" || maparg("<C-h>", "n") !=# "" || maparg("<C-j>", "n") !=# "" || maparg("<C-k>", "n") !=# "" || maparg("<C-l>", "n") !=# "" || maparg("<C-p>", "n") !=# "" || maparg("<C-p>", "c") !=# "" || maparg("<C-n>", "c") !=# "" || maparg("w!!", "c") !=# "" | cquit | endif' \
-c 'if has_key(g:plugs, "auto-pairs") || maparg("<Tab>", "i") =~# "pumvisible" || maparg("<S-Tab>", "i") =~# "pumvisible" || maparg("<CR>", "i") =~# "asyncomplete#close_popup" || maparg("<CR>", "i") =~# "AutoPairs" | cquit | endif' \
-c 'if maparg("<Esc><Esc>", "t") !=# "" || maparg("<C-h>", "t") !=# "" || maparg("<C-j>", "t") !=# "" || maparg("<C-k>", "t") !=# "" || maparg("<C-l>", "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") !~# "<Esc>" | 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("<C-s>", "n") !~# ":w" || maparg("<C-s>", "i") !~# ":w" || maparg("w!!", "c") !~# "sudo tee" | cquit | endif' \
-c 'if maparg("<Tab>", "i") !~# "pumvisible" || maparg("<S-Tab>", "i") !~# "pumvisible" || maparg("<CR>", "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("<Esc><Esc>", "t") !~# "<C-\\\\><C-N>" || maparg("<C-h>", "t") !~# "<C-W>h" || maparg("<C-j>", "t") !~# "<C-W>j" || maparg("<C-k>", "t") !~# "<C-W>k" || maparg("<C-l>", "t") !~# "<C-W>l") | 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"
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 '<C-w>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
vim -u NONE -i NONE -es -N \
-c 'let g:chopsticks_profile = "minimal"' \
-c 'source .vimrc' \
@ -207,6 +338,40 @@ check_vim() {
-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"