Compare commits

..

No commits in common. "main" and "v1.2.0" have entirely different histories.
main ... v1.2.0

28 changed files with 2865 additions and 3181 deletions

View file

@ -1,23 +0,0 @@
---
name: Bug report
about: Something broken or unexpected
labels: bug
---
**What happened**
**What you expected**
**Steps to reproduce**
1.
2.
3.
**Environment**
- OS:
- Vim version (`vim --version | head -1`):
- Terminal:
- SSH: yes / no
- TTY mode (`echo g:is_tty` inside vim):

View file

@ -1,14 +0,0 @@
---
name: Feature request
about: Suggest a plugin, mapping, or improvement
labels: enhancement
---
**What problem does this solve?**
**What does the solution look like?**
**Alternatives you considered**

View file

@ -1,13 +0,0 @@
## What
<!-- One sentence: what does this PR do? -->
## Why
<!-- Why is this change needed? -->
## Test
- [ ] `vim --startuptime` shows no regression
- [ ] Tested on macOS / Linux
- [ ] `,?` cheat sheet still accurate

BIN
.github/demo.gif vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 990 KiB

63
.github/demo.tape vendored
View file

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

View file

@ -1,75 +0,0 @@
name: test
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
startup:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Install Vim
run: |
if [ "$(uname)" = "Darwin" ]; then
brew install vim
else
sudo apt-get update && sudo apt-get install -y vim
fi
- name: Install vim-plug
run: |
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
- name: Symlink config
run: |
ln -sf "$PWD" ~/.vim/chopsticks-src
ln -sf "$PWD/.vimrc" ~/.vimrc
mkdir -p ~/.vim/modules
cp modules/*.vim ~/.vim/modules/
- name: Install plugins
run: |
set +e
vim -i NONE -es -u .vimrc -N -c 'PlugInstall --sync' -c 'qa!' 2>&1
plug_status=$?
set -e
if [ "$plug_status" -ne 0 ]; then
echo "PlugInstall exited with $plug_status; validating plugin directories"
fi
for plugin in fzf fzf.vim vim-fugitive vim-gitgutter ale vim-lsp vim-lsp-settings asyncomplete.vim asyncomplete-lsp.vim vim-markdown; do
test -d "$HOME/.vim/plugged/$plugin" || {
echo "FAIL: missing plugin $plugin"
exit 1
}
done
- name: Run Vim smoke tests
run: scripts/test.sh vim
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check test runner CLI
run: |
scripts/test.sh --help
scripts/test.sh list
- name: Run shell and installer tests
run: scripts/test.sh shell installer bootstrap
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install markdownlint
run: npm install -g markdownlint-cli
- name: Lint Markdown
run: scripts/test.sh docs

6
.gitignore vendored
View file

@ -1,8 +1,6 @@
*.json
!coc-settings.json
*.swp
*.swo
.DS_Store
Session.vim
autoload/
plugged/
.swap/
.undo/

View file

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

1517
.vimrc

File diff suppressed because it is too large Load diff

View file

@ -1,159 +1,211 @@
# Changelog
## Unreleased
All notable changes to chopsticks are documented here.
---
## [1.2.0] - 2026-04-09
Installer robustness overhaul and one-command bootstrap.
### Added
- `~/.config/chopsticks.vim` local pre-load config for profile and user choices
- `g:chopsticks_enable_markdown_preview` to control Previm independently
- `g:chopsticks_profile` with `minimal`, `engineer`, and `full` profiles
- `.markdownlint.json` aligned with the project's README/changelog style
- `:ChopsticksStatus` diagnostic command — checks system tools, LSP servers, linters, formatters
- `,af` toggle format-on-save (ALE `fix_on_save`)
- `,gL` git log graph (last 20 commits)
- `,gC` FZF git commits search, `,gB` buffer commits
- Interactive installer profile selection for `minimal`, `engineer`, and `full`
- `install.sh --profile=minimal|engineer|full` for scripted profile selection
- `install.sh --dry-run` to show the resolved profile/config path without writes
- `install.sh --configure-only` to update local profile config without reinstalling
- `get.sh --dry-run` for safe bootstrap previews before clone/update/install
- `CHOPSTICKS_DEST=/absolute/path` to test or install the bootstrap target elsewhere
- `scripts/test.sh` local test runner reused by GitHub Actions
- `scripts/test.sh quick`, `--help`, and `list` for easier local test discovery
- **`get.sh`** — one-command bootstrap: `curl -fsSL .../get.sh | bash`
- Installs `git` if missing (apt / pacman / dnf / brew)
- Clones repo to `~/.vim`; `git pull` if already present
- `exec bash install.sh </dev/tty` — interactive prompts work correctly even when piped from curl
- **Network connectivity check** — warns early if `github.com` is unreachable
- **`curl` preflight** — detects missing curl, auto-installs or dies with clear instructions
- **`git` preflight** — same as curl
- **`vim` auto-install** — attempts `pkg_install` before dying if vim is not found
- **`sudo` availability check** — authenticates once upfront; `--yes` mode skips sudo gracefully with a warning
- **macOS Homebrew installer** — offers to install Homebrew when `brew` is missing on macOS
- **Node.js via nvm** — when Node.js is missing, offers to install nvm + Node.js LTS automatically
- **Python 3 installer** — offers to install python3 via package manager when missing
- **`safe_download()`** helper — verifies downloads are non-empty and not HTML error pages (guards against GitHub 404 / rate-limit pages being silently treated as binaries)
- **`pkg_install()`** helper — unified cross-platform install (brew / apt / pacman / dnf)
- **`arch_github()` / `arch_linux_x64()`** helpers — normalize `uname -m` including `aarch64``arm64`
- **`trap on_error ERR`** — catches unexpected failures, shows line number and debug command
- **`trap EXIT`** — cleans up temp files (`/tmp/chopsticks-hadolint`, `/tmp/chopsticks-marksman`)
- **Symlink verification** — confirms `[[ -L ]]` after `ln -sf`
- **vim-plug fallback** — if curl download fails, falls back to `git clone`; verifies file is non-empty
- **`vim +PlugInstall` error handling** — warns on non-zero exit instead of silent continue
- **Screen-dark notice** — informs user before each Vim fullscreen step (PlugInstall, CocInstall)
### Changed
- `set -e``set -eo pipefail` — pipeline failures now propagate correctly
- `ask()` now reads from `/dev/tty` with a test-open check (`{ true </dev/tty; }`) — interactive prompts work under `curl | bash` and non-interactive SSH sessions fall back to "no" safely
- Binary downloads (hadolint, marksman) use named temp files and `safe_download()` instead of bare curl
- Arch architecture detection handles `aarch64` in addition to `arm64`
- System tools section checks `HAS_SUDO` before running apt / pacman / dnf commands
---
## [1.1.1] - 2026-04-09
Systematic absorption of best practices from amix/vimrc, tpope/vim-sensible,
ThePrimeagen, skwp/YADR, and spf13-vim — settings and mappings that appear
consistently across all top global configs but were missing here.
### Added
- **`set ttimeoutlen=10`** — eliminates the ~500ms ESC lag in terminal Vim; separates
keycode timeout from leader-key timeout (`timeoutlen` unchanged at 500ms)
- **`set display+=lastline`** — shows truncated long lines instead of replacing them with `@@@`
- **`set complete-=i`** — `Ctrl+n/p` no longer scans all included files; completion is instant
- **`set wildignorecase`** — case-insensitive filename completion in wildmenu and `:find`
- **`set path+=**`** — recursive `:find` across the project; wildignore excludes
`node_modules`, `__pycache__`, `dist`, `build`, `.git`
- **`set sessionoptions`** — removes `options` from saved sessions (prevents stale plugin
settings from contaminating restored sessions)
- **`set listchars`** — defines visible whitespace characters; TTY uses ASCII symbols,
modern terminals use Unicode (tab `→`, trail `·`, extends `▸`)
- **`F6`** — toggle visible whitespace on/off
- **`formatoptions-=cro`** on `BufEnter` — disables automatic comment-leader insertion
when pressing Enter or `o/O`; runs on BufEnter to override filetype plugins
- **`InsertLeave * set nopaste`** — auto-disables paste mode on leaving insert, preventing
permanently broken auto-indent
- **`colorcolumn=+1`** for all languages via `textwidth`:
Python 88, Go 120, JS/TS 100, Rust 100, Shell 80 (Markdown disabled)
- **`vnoremap J/K`** with `gv=gv` — move selected lines down/up and re-indent (ThePrimeagen)
- **`gV`** — re-select last pasted text (`\`[v\`]` — spf13, YADR)
- **`cnoremap <C-p>/<C-n>`** — navigate command-line history matching typed prefix (amix, spf13)
- **`<leader>e :Explore`** — open built-in Netrw file browser; works on any Vim without plugins
- **`<leader>cd`** — change window-local CWD to current file's directory (was `<leader>wd`)
- **`<leader>sv`** — reloads vimrc and echoes confirmation
### Changed
- `<leader>wd` renamed to `<leader>cd`; now uses `lcd` (window-local) instead of `cd` (global)
- `wildignore` expanded with `*/node_modules/*`, `*/__pycache__/*`, `*/dist/*`, `*/build/*`
---
## [1.1.0] - 2026-04-09
Ergonomics and automation overhaul: community-standard keybindings, seamless
tmux integration, an in-Vim cheat sheet, a beginner onboarding section, and
several correctness fixes from a systematic review.
### Added
- **`jk``Esc`** in insert mode — ergonomic escape without reaching for the key
- **`Ctrl+s` save** in normal and insert mode (add `stty -ixon` to shell rc to enable
in terminals that use XON/XOFF flow control)
- **`//` visual search** — search for visually selected text using `\V` very-nomagic escaping
- **`<leader>p` / `<leader>P`** — paste from system clipboard after/before cursor
- **`<leader>rG`** — ripgrep word under cursor with `-F` (literal, not regex)
- **`<leader>u`** — leader-key alias for UndoTree (complements `F5`)
- **`<leader>tt`** — leader-key alias for Tagbar (complements `F8`)
- **`,?` in-Vim cheat sheet** — opens a read-only buffer covering modes, survival
commands, search, code intelligence, git, and clipboard; press `q` to close
- **vim-tmux-navigator** plugin — `Ctrl+h/j/k/l` navigates seamlessly across Vim
splits and tmux panes without a prefix key
- **`install.sh` tmux step** — detects tmux and optionally appends the four
navigator `bind-key` lines to `~/.tmux.conf`; warns about `C-l`/screen-clear tradeoff
- **`install.sh` survival guide** — post-install output now shows the 4 essential
commands for first-time Vim users, plus the `stty -ixon` advisory
- **QUICKSTART.md Step 0** — new first section explaining Vim modes (Normal/Insert/Visual)
and 4 survival commands; makes the guide usable by users who have never opened Vim
- **`let b:ale_enabled = 0`** in `LargeFileSettings()` — ALE no longer spawns
linter subprocesses for files over 10 MB
### Changed
- **ALE lint triggers**`ale_lint_on_text_changed` changed from `'never'` to `'normal'`;
`ale_lint_on_insert_leave` and `ale_lint_on_enter` changed from `0` to `1` — diagnostics
now refresh on buffer enter and after edits settle in normal mode
- **`<C-h/j/k/l>` manual maps removed** — vim-tmux-navigator owns these keys at
plugin load time; the previous hand-rolled `<C-W>` maps were unreachable dead code
- **`<leader>pp` paste-mode toggle removed** — functionally identical to the existing
`F2` pastetoggle; its presence caused a 500 ms delay on every `<leader>p` paste
### Fixed
- `g:loaded_logipat` typo → `g:loaded_logiPat` — logiPat was loading fully (0.478ms wasted)
- `get.sh` now refuses to update an existing `~/.vim` git repo unless its
origin is chopsticks
- Large file protection now stays active after filetype and syntax autocommands
- `g:ale_fix_on_save = 0` in local config is now respected
- Local config now respects absolute `XDG_CONFIG_HOME` instead of hardcoding
`~/.config`
- **ALE navigation direction reversed**`[e` now correctly calls `ALEPrevious`
and `]e` calls `ALENext`, matching the vim-unimpaired `[`/`]` convention
- **`<leader>rG` regex metacharacter bug** — without `-F`, characters like `.` `*`
`(` in the cursor word were treated as regex, producing incorrect matches
### Changed
---
- `,?` cheat sheet is now profile-aware and hides LSP/ALE/preview/UndoTree keys
when those features are disabled
- Module reload/source paths now use `fnameescape()` so installs in paths with
spaces are handled correctly
- CI now verifies path-safe module loading, the local config hook, and
minimal-profile cheat sheet output
- Markdown now opens in quiet writing mode by default: no real-time markdownlint,
no Marksman LSP, no spell noise, no conceal, no sign column, and no realtime preview
- Native `s` is no longer shadowed by EasyMotion; use `,S` for the two-character jump
- `,w` now uses a normal `:write` instead of forced `:write!`
- Swap files are enabled again and stored under `~/.vim/.swap` for crash recovery
- Installer defaults are slimmer: only core search tools stay selected by default;
language and lint suites are opt-in
- `:ChopsticksStatus` now respects disabled LSP/lint profiles instead of reporting
intentionally disabled tools as missing
- `,sv` now clears the load guard before sourcing `$MYVIMRC`
- CI now verifies key plugin directories, Markdown quiet defaults, markdownlint,
and an explicit startup-time threshold
- Installer plugin validation now checks every plugin required by the active profile
- The optional tool menu now hides LSP/lint suites in `minimal` and selects
Marksman by default in `full`
- tmux integration is written as a managed block so future installer runs can
update it without appending duplicate bindings
- Installer cleanup now restores the cursor after interrupted checkbox menus
- Bootstrap dry-run now refuses unrelated existing git repos before any writes
- CI now shares shell, installer, bootstrap, docs, and Vim smoke checks with
the local test runner
- CI now checks the test runner help and group-list commands
- Skip 2 more built-in plugins: openPlugin, manpager (10 → 12 total)
- Remove deprecated `set ttyfast` (no-op since Vim 8)
- Add `grepprg=rg --vimgrep``:grep` now uses ripgrep + quickfix
- Add `diffopt` with histogram algorithm and indent-heuristic
- Consolidate FZF Rg/RgWord/GFiles commands (DRY refactor)
- vim-tmux-navigator: conditional load (only inside tmux), fallback `Ctrl+hjkl` mappings outside
- Add `Ctrl+hjkl` window navigation fallback when tmux-navigator not loaded
## [1.0.0] - 2026-03-29
## 2.1.0 — 2025-04-22
First stable release. Full-stack engineering environment out of the box — automatic
installation, tiered LSP, TTY fallback, and coverage for 14 languages.
### Added
- Cheat sheet (`,?`) — vertical sidebar, one key per line, section headers
- Previm markdown preview restored (lazy-loaded, `,mp`)
- `:LspInstallServer` added to cheat sheet
- **Arch Linux support** in `install.sh` — pacman branch for all system tools
- **`hadolint`** added to system tools installation (Dockerfile linting)
- **`staticcheck`** added to Go tools (replaces archived `golint`)
- **`yamllint`** added to pip tools (YAML linting)
- **`coc-settings.json`** — configures `marksman` as Markdown LSP for CoC via
`languageserver` entry; symlinked automatically by `install.sh`
- **pip3 bootstrap** in `install.sh` — auto-installs pip3 when python3 is present
but pip3 is absent (common on Ubuntu minimal images)
- **9 named augroups** in `.vimrc` — all loose `autocmd` statements now wrapped
with `autocmd!` to prevent doubling on `:source $MYVIMRC`:
`ChopstickTabHistory`, `ChopstickResize`, `ChopstickStdin`, `CocHighlight`,
`ChopstickCleanup`, `ChopstickFiletype`, `ChopstickTTYLargeFile`,
`ChopstickWhichKey`, `ChopstickStartify`
- **TTY-safe plugin install**`vim +PlugInstall +qall </dev/null` prevents
Vim from blocking in non-interactive/piped environments
### Changed
- SQL tooling unified to **`sqlfluff`** (pip) — `sqlfmt` removed from npm section
- Go linter changed from `golint` (archived 2023) to **`staticcheck`**
- Markdown LSP changed from broken `coc-marksman` (npm) to **`marksman`** binary
configured via `coc-settings.json`
- Plugin count: 25 (restored previm, dropped 5 bloat plugins)
- QUICKSTART updated — removed stale references, improved first-launch guidance
### Fixed
- **vim-go startup hang** on Arch Linux — removed `:GoUpdateBinaries` post-install
hook; set `g:go_gopls_enabled = 0` to prevent conflict with `coc-go`
- **E495 errors** (`<afile>` in special buffers) — all `<afile>` usages guarded
with `!empty(expand('<afile>'))` and `empty(&buftype)` checks
- **`g:go_def_mode` conflict** — now conditional: uses `gopls` when CoC active,
`godef` when vim-lsp active (avoids error when gopls is disabled)
- **vim startup UX** — NERDTree + Startify layout for `vim .` and bare `vim`
- **`coc-marksman` silent failure** — package does not exist on npm; replaced with
native `languageserver` configuration in `coc-settings.json`
- **CoC startup warning** in no-node environments — `g:coc_start_at_startup = 0`
and `g:coc_disable_startup_warning = 1` set when `g:use_coc = 0`
## 2.0.0 — 2025-04-21
---
## [0.9.0] - 2026-02-21
### Added
- Sidebar toggle (`,e` / `,E`) — left-side netrw with `topleft vertical`, winfixwidth, proper toggle
- Enriched statusline — SLMode, SLGit, SLAle, SLFlags
- Toggle feedback — F2/F3/F4/F6/`,ss` echo current state
- `vim .` layout — netrw left + Startify right (removed in later refactor)
- Interactive tutorial (`:ChopsticksLearn` — removed in later release)
### Removed (Unix minimalism refactor)
- **565 lines** of dead code and bloat
- 5 plugins: Goyo, Limelight, vim-obsession, indentLine, vim-unimpaired
- `modules/writing.vim` — folded into `languages.vim`
- `tutor/chopsticks.tutor` — removed (269 lines)
- Tab management keybindings (8 mappings), spell nav bindings (4 mappings)
- Dead functions: HasPaste(), CleanExtraSpaces(), ToggleNumber(), SynStack
- TTY welcome message, `,so`, `,ms`, `,sh` mappings
- **Full-stack language coverage** — LSP, lint, and format for: Python,
JavaScript, TypeScript, Go, Rust, Shell, YAML, HTML, CSS/SCSS, Less,
JSON, Markdown, SQL, Dockerfile
- **`install.sh` overhaul** — automated installation of system tools, npm tools,
pip tools, Go tools, and CoC language server extensions with platform detection
and interactive prompts; `--yes` flag for non-interactive mode
- **vim-startify** startup screen with dynamic header (version, cwd, git branch)
- **vim-which-key** keybinding popup on `,` + 500ms pause
- **Startup layout**`vim .` opens NERDTree left + Startify right; bare `vim`
opens Startify with NERDTree alongside
- **Session management** via vim-obsession + vim-prosession
- **Large file handling** — syntax and undo disabled for files > 10 MB
- **Project-local config**`.vimrc` in project root auto-loaded via `set exrc`
- **Persistent undo**`~/.vim/.undo/` with `undolevels=1000`
### Changed
- Tiered LSP backend: CoC (Node.js) preferred, vim-lsp (no Node.js) as fallback
- All CoC and vim-lsp keybindings unified (`gd`, `K`, `[g`/`]g`, `,rn`, `,ca`)
- ALE `fix_on_save` disabled when vim-lsp active (prevents double-format)
- NERDTree autocmd wrapped in `augroup NERDTreeAutoClose`
- CI plugin threshold lowered to 20
- README: hero layout with demo GIF, badges, architecture diagram
- vim-markdown settings absorbed from writing.vim into languages.vim
### Fixed
- Multiple leader key conflicts resolved (`,ad`, `,cd`, `,cp`, `,sp`, `,t`)
- CtrlP removed (redundant with FZF)
- Duplicate `set` options cleaned up
- `<leader>A` dead mapping (no alternate-file plugin) removed
## 1.3.0 — 2025-04-20
---
### Changed
## [0.1.0] - 2024
- Startup: 39ms → 19ms (51% faster)
- Dropped vim-unimpaired for performance
- Runtime tuning across modules
## 1.2.0 — 2025-04-19
### Added
- Hero README with demo GIF, CI badges
- GitHub Actions CI (startup test on macOS + Ubuntu, shellcheck)
- Issue/PR templates
### Changed
- Documentation rewrite — clean, short, for engineers
## 1.1.0 — 2025-04-18
### Added
- 12-module architecture (env → plugins → core → ui → editing → navigation → lsp → lint → git → writing → languages → tools)
- Zen mode (Goyo + Limelight)
- Run file (`,cr`) with auto filetype detection
- Smart search (SmartFiles, Rg, RgWord)
- EasyMotion, yank highlight, undo tree
- Robust installer (`get.sh`) with preflight checks
### Changed
- `.vimrc` split into 12 self-contained modules
- Comprehensive bug audit (14 fixes)
## 1.0.0 — 2025-04-16
### Added
- Initial Vim configuration — migrated from Neovim
- vim-plug plugin manager
- vim-lsp + asyncomplete (pure VimScript LSP)
- ALE linting + format-on-save
- FZF fuzzy finder
- Fugitive + GitGutter
- Solarized colorscheme
- TTY detection and graceful degradation
- Platform installer (macOS, Debian, Arch, Fedora)
Initial release — base Vim configuration with vim-plug, basic plugins, and
TTY/non-TTY detection.

View file

@ -1,44 +0,0 @@
# Contributing
## Rules
1. **No Node.js dependencies.** The LSP engine is pure VimScript. Some language servers need Node — that's fine. The config itself must not.
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.
## 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
## Local tests
```bash
scripts/test.sh --help
scripts/test.sh quick
scripts/test.sh vim
```
`scripts/test.sh quick` runs shell, docs, installer, and bootstrap checks without requiring Vim plugins.
`scripts/test.sh vim` expects plugins to be installed under `~/.vim/plugged`.
Use `STARTUP_LIMIT_MS=150 scripts/test.sh vim` to match CI's startup threshold.
## Reporting bugs
Open an issue. Include:
- OS and Vim version
- Whether you're on SSH/TTY
- Steps to reproduce
## Code style
- Named augroups with `autocmd!`
- No comments explaining _what_ — only _why_
- `exists('g:plugs["..."]')` guards for plugin-dependent config
- Test with `scripts/test.sh vim`

View file

@ -1,120 +1,295 @@
# Quick Start
Five minutes from zero to a working Vim setup.
Five minutes from zero to a working Vim engineering environment.
## Install
> **New to Vim?** Read Step 0 first — it takes 2 minutes and prevents the most
> common beginner frustration. Already know how Vim modes work? [Skip to Step 1](#step-1-install).
---
## Step 0: Vim Basics
> **When confused, press `Esc` until things feel normal again — then keep reading.**
Vim is **modal**: the keyboard behaves differently depending on which mode you are in.
Most people get stuck because they try to type text while in Normal mode.
### The Three Modes
| Mode | Purpose | How to enter | How to leave |
|------|---------|--------------|--------------|
| **Normal** | Navigate and run commands | Startup default | — (you're already here) |
| **Insert** | Type text | `i` before cursor, `a` after, `o` new line below | `Esc` or `jk` |
| **Visual** | Select text | `v` char-by-char, `V` whole lines | `Esc` |
### 4 Survival Commands
Learn these before anything else. They will get you out of every stuck situation.
| Command | Action |
|---------|--------|
| `Esc` or `jk` | Exit insert/visual mode — return to Normal |
| `:q!` then `Enter` | Force quit without saving (emergency exit) |
| `,x` | Save and quit |
| `,w` or `Ctrl+s` | Save the file |
Once in Normal mode, press `,?` to open a cheat sheet covering everything else.
---
## Step 1: Install
**One command — works on macOS and Linux:**
```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --profile=minimal
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --dry-run --profile=full
```
Open vim. First launch auto-installs plugins — **wait 30-60s, don't close vim**. Restart when done.
This clones the repo to `~/.vim` and runs the full installer. Interactive prompts
let you choose which optional tools to install (ripgrep, Node.js, Python tools, etc.).
Default profile is `engineer`. Interactive installs ask for a profile first;
`--profile=minimal`, `--profile=engineer`, or `--profile=full` selects it
without prompting. You can later put `let g:chopsticks_profile = 'minimal'` in
`${XDG_CONFIG_HOME:-~/.config}/chopsticks.vim` for a smaller core-only setup,
or use `full` for the heavier Markdown/LSP feedback.
The installer automatically handles missing dependencies — it will offer to install
`git`, Homebrew (macOS), or Node.js via nvm if they are not found.
To switch later without reinstalling anything:
**Traditional install:**
```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
cd ~/.vim && ./install.sh
```
**Non-interactive (CI / server / scripting):**
```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --yes
```
---
## Step 2: Open Vim
```bash
cd ~/.vim && ./install.sh --configure-only --profile=full
vim
```
## Modes
The startup screen (vim-startify) shows recent files and sessions.
Press `Ctrl+p` to find a file, or just type a path.
| Mode | Enter | Leave |
| ------ | --------------- | ------------- |
| Normal | startup default | — |
| Insert | `i` / `a` / `o` | `Esc` or `jk` |
| Visual | `v` / `V` | `Esc` |
## Survival
```
Esc / jk back to Normal
,w save
,x save + quit
:q! force quit
Ctrl+s save from any mode
,? cheat sheet (toggle sidebar)
To open a project:
```bash
vim . # NERDTree on left, Startify on right
vim myfile # opens file directly
```
## Find things
---
```
Ctrl+p fuzzy find file (git-aware)
,rg ripgrep project
,b search buffers
,fh recent files
,e file browser
,, last file
## Step 3: Set Up LSP (pick your path)
### Path A: With Node.js (CoC — full LSP)
```bash
node --version # must be >= 14.14
```
## Write code
Inside Vim, install language servers for your stack:
```
gd go to definition
K hover docs
,rn rename symbol
,ca code action
,f format
,cr run current file
Tab / S-Tab cycle completions
```vim
:CocInstall coc-pyright coc-tsserver coc-go coc-rust-analyzer
```
**First time in a new language?** Run `:LspInstallServer` — it auto-detects filetype and installs the right server. Do this once per language.
Or let `install.sh` do it — it asks during setup.
## Git
### Path B: Without Node.js (vim-lsp — no dependencies)
```
,gs status (s=stage, cc=commit)
,gd diff
,gb blame
,gp push
]x / [x conflict markers
Open a source file, then run:
```vim
:LspInstallServer
```
## Edit
This auto-detects and installs the correct language server for the current filetype.
---
## The 12 Keys That Matter
```
,S + 2 chars EasyMotion jump
gc toggle comment
cs"' change surrounding " to '
Alt+j / Alt+k move line
,u undo tree
,y clipboard yank
, (pause 500ms) Show all keybindings (which-key)
,? Open cheat sheet inside Vim
Esc / jk Exit insert mode → Normal (memorize this)
Ctrl+s Save (works in normal and insert mode)
Ctrl+p Fuzzy find file
Ctrl+n Toggle file tree
gd Go to definition
K Show documentation
[g / ]g Prev / next LSP diagnostic
,rn Rename symbol
,rG Search word under cursor (ripgrep)
,gs Git status
,w / ,x Save / Save+quit
```
## Navigate
---
## Daily Use
### Navigate Code
| Key | Action |
|-----|--------|
| `gd` | Go to definition |
| `gy` | Go to type definition |
| `gi` | Go to implementation |
| `gr` | List all references |
| `K` | Show docs for symbol under cursor |
| `Ctrl+o` | Jump back |
| `Ctrl+i` | Jump forward |
### Edit Code
| Key | Action |
|-----|--------|
| `Tab` | Select next completion item |
| `Enter` | Confirm completion |
| `gc` | Toggle comment (visual mode too) |
| `cs"'` | Change surrounding `"` to `'` |
| `ds(` | Delete surrounding `(` |
| `s`+2ch | EasyMotion: jump anywhere |
### Manage Errors
| Key | Action |
|-----|--------|
| `]g` | Jump to next diagnostic |
| `[g` | Jump to previous diagnostic |
| `K` | Read the error message |
| `,ca` | Apply code action / auto-fix |
### Git Workflow
```
Ctrl+h/j/k/l splits + tmux panes
,h / ,l prev / next buffer
,z maximize window
,tv / ,th terminal
,gs git status (stage with 's', commit with 'cc')
,gd diff current file
,gb blame current file
,gc commit
,gp push
,gl pull
```
## Markdown
---
```
,mp preview in browser
,mt table of contents
## Language Workflows
### Python
```bash
# tools installed by install.sh; or manually:
pip install black flake8 pylint isort
```
Markdown is quiet by default: no real-time lint, no spell noise, no concealed
syntax. Enable the heavier Markdown tools only when you want them.
Auto-formats with `black` + `isort` on save. Lint errors show as `X`/`!` in the sign column.
## Health check
### JavaScript / TypeScript
```
:ChopsticksStatus see what's installed and what's missing
```bash
npm install -g prettier eslint typescript
```
The `,?` cheat sheet follows your active profile, so `minimal` users only see
keys for features that are actually loaded.
Auto-formats with `prettier` on save.
See [README](README.md) for the full reference. See the [wiki](https://github.com/m1ngsama/chopsticks/wiki) for deep dives.
### Go
```bash
# tools installed by install.sh; or manually:
go install golang.org/x/tools/gopls@latest
go install golang.org/x/tools/cmd/goimports@latest
go install honnef.co/go/tools/cmd/staticcheck@latest
```
`gofmt` + `goimports` run on save automatically.
### Markdown
Install `marksman` for LSP support (completions, link checking):
```bash
brew install marksman # macOS
sudo pacman -S marksman # Arch
# or: ./install.sh (handles it automatically)
```
---
## Customize
Edit config live:
```vim
,ev " opens ~/.vimrc in Vim
,sv " reloads config without restarting
```
Per-project settings: create `.vimrc` in your project root.
```vim
" project/.vimrc
set shiftwidth=2
let g:ale_python_black_options = '--line-length=100'
```
Change color scheme in `~/.vimrc`:
```vim
colorscheme dracula " or: gruvbox, solarized, onedark
```
---
## Quick Reference Card
```
BASICS (learn these first)
Esc / jk Exit insert mode → Normal
Ctrl+s Save (normal + insert mode)
:q! + Enter Emergency quit without saving
,? Open cheat sheet
FILES
Ctrl+n File tree toggle
Ctrl+p Fuzzy find file (git-aware)
,b Search open buffers
,rg Search file contents (ripgrep)
,rG Ripgrep word under cursor
,w Save | ,q Quit | ,x Save+quit
,wa Save all buffers
,, Switch to last file
CODE
gd Go to definition
K Show documentation
[g / ]g Prev/next LSP diagnostic
[e / ]e Prev/next ALE error
,rn Rename symbol
,ca Code action / auto-fix
,f Format selection | ,F Format whole file
GIT
,gs Status | ,gd Diff | ,gb Blame
,gc Commit | ,gp Push | ,gl Pull
WINDOWS / PANES
Ctrl+h/j/k/l Move between Vim windows or tmux panes
,h / ,l Prev / next buffer
,tv Open terminal (vertical)
,th Open terminal (horizontal)
Esc Exit terminal mode
,u Undo tree | ,tt Tag browser
SEARCH & REPLACE
/text Search forward | ?text backward
// Search for visually selected text
,* Replace word under cursor (file-wide)
CLIPBOARD
,y / ,Y Yank / yank line to system clipboard
,p / ,P Paste from system clipboard (after / before)
```
---
See [README.md](README.md) for the complete reference.

690
README.md
View file

@ -1,231 +1,611 @@
<p align="center">
<img src=".github/demo.gif" alt="chopsticks demo" width="720">
</p>
# chopsticks
<h1 align="center">chopsticks</h1>
> A batteries-included Vim configuration for full-stack engineering.
> Tiered LSP · 14 languages · TTY-aware · Zero icon fonts · One-command install.
<p align="center">
<strong>Vim for engineers. ~25 plugins, works over SSH.</strong>
</p>
<p align="center">
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square" alt="MIT License"></a>
<a href="https://www.vim.org/"><img src="https://img.shields.io/badge/Vim-8.0%2B-brightgreen?style=flat-square" alt="Vim 8.0+"></a>
<a href="#install"><img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux-lightgrey?style=flat-square" alt="Platform"></a>
<a href="https://github.com/m1ngsama/chopsticks/actions"><img src="https://img.shields.io/github/actions/workflow/status/m1ngsama/chopsticks/test.yml?style=flat-square&label=tests" alt="Tests"></a>
<a href="https://github.com/m1ngsama/chopsticks/releases"><img src="https://img.shields.io/github/v/release/m1ngsama/chopsticks?style=flat-square&color=orange" alt="Release"></a>
</p>
---
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](LICENSE)
[![Vim 8.0+](https://img.shields.io/badge/Vim-8.0%2B-brightgreen?style=flat-square)](https://www.vim.org/)
[![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux-lightgrey?style=flat-square)](#installation)
[![Release](https://img.shields.io/github/v/release/m1ngsama/chopsticks?style=flat-square&label=release&color=orange)](https://github.com/m1ngsama/chopsticks/releases)
[![Last Commit](https://img.shields.io/github/last-commit/m1ngsama/chopsticks?style=flat-square)](https://github.com/m1ngsama/chopsticks/commits/main)
[![Stars](https://img.shields.io/github/stars/m1ngsama/chopsticks?style=flat-square)](https://github.com/m1ngsama/chopsticks/stargazers)
[![Issues](https://img.shields.io/github/issues/m1ngsama/chopsticks?style=flat-square)](https://github.com/m1ngsama/chopsticks/issues)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen?style=flat-square)](https://github.com/m1ngsama/chopsticks/pulls)
[![Plugins](https://img.shields.io/badge/plugins-30%2B-blueviolet?style=flat-square)](#plugins)
[![Languages](https://img.shields.io/badge/languages-14-informational?style=flat-square)](#language-support)
```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash
```
> **New to Vim?** Read [Step 0 in QUICKSTART.md](QUICKSTART.md#step-0-vim-basics) first —
> a 2-minute intro to modes and the 4 commands that get you out of any jam.
---
## Why
## Contents
You SSH into a server. You need to edit code. You want LSP, fuzzy find, git integration, format-on-save — not a 20-minute setup.
- [Design Principles](#design-principles)
- [Requirements](#requirements)
- [Installation](#installation)
- [LSP: Tiered Backend](#lsp-tiered-backend)
- [Key Mappings](#key-mappings)
- [Features](#features)
- [Language Support](#language-support)
- [Plugins](#plugins)
- [Customization](#customization)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
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.
---
**2425 plugins** (tmux-navigator loads only inside tmux), LSP, linting, and a hand-built statusline. No bloat, no decorations, just tools.
## Design Principles
## What's in the box
| Principle | What it means |
|-----------|--------------|
| **KISS** | No icon fonts, no Nerd Font glyphs — plain ASCII everywhere |
| **Tiered LSP** | CoC (full) when Node.js is available; vim-lsp (pure VimScript) otherwise |
| **TTY-aware** | Automatically detects SSH/console environments and degrades gracefully |
| **Engineering-first** | Git workflow, persistent sessions, project-local config, large-file safety |
| **Batteries included** | `install.sh` handles vim-plug, plugins, system tools, and language servers |
| Feature | Description |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **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 |
| **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 |
---
## Install
## Requirements
| Tool | Minimum | Role |
|------|---------|------|
| Vim | **8.0+** | Required — `install.sh` installs it if missing |
| git | any | Required — `install.sh` installs it if missing |
| curl | any | Required — `install.sh` installs it if missing |
| Node.js | 14.14+ | Optional — enables CoC LSP; `install.sh` offers nvm install |
| ripgrep | any | Optional — enables `,rg` / `,rG` project search |
| fzf | any | Optional — enables `Ctrl+p` fuzzy finder |
| ctags | any | Optional — enables `,tt` tag browser |
| tmux | 1.8+ | Optional — enables seamless pane navigation |
`install.sh` detects your environment and installs missing dependencies automatically.
On macOS it will offer to install Homebrew if not present. On any platform it will
offer to install Node.js via nvm if missing.
---
## Installation
### One command (recommended)
```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --profile=minimal
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --dry-run --profile=full
```
Or manually:
This bootstrap script clones the repo to `~/.vim`, then runs the full installer.
It works correctly even when piped from curl — interactive prompts use `/dev/tty`.
For non-interactive or CI environments:
```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --yes
```
### Traditional (git clone)
```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
cd ~/.vim && ./install.sh --profile=engineer
cd ~/.vim && ./install.sh
```
Supports macOS (brew), Debian/Ubuntu (apt), Arch (pacman), Fedora (dnf).
Set `CHOPSTICKS_DEST=/absolute/path` before running `get.sh` to install
somewhere other than `~/.vim`.
### What the installer does
First launch installs plugins automatically (30-60s). Restart vim when done.
Use `./install.sh --dry-run --profile=full` to inspect the resolved profile and
config path without changing files. Use `./install.sh --configure-only
--profile=minimal` to switch profiles without reinstalling plugins or tools.
1. **Preflight** — checks network, detects OS and package manager, verifies or installs `curl`, `git`, and `vim`
2. **sudo** — authenticates once upfront; gracefully skips system packages when unavailable
3. **macOS** — offers to install Homebrew if `brew` is not found
4. **Node.js** — offers to install via nvm if not found (falls back to vim-lsp if declined)
5. **Python** — offers to install Python 3 if missing; bootstraps pip3 if only python3 is present
6. **Symlinks** — backs up any existing `~/.vimrc` with a timestamp, then symlinks `~/.vimrc → ~/.vim/.vimrc`
7. **Plugins** — installs vim-plug and runs `:PlugInstall` (with a progress notice during the black-screen period)
8. **System tools** — ripgrep, fzf, ctags, shellcheck, hadolint, marksman (verified downloads)
9. **Language tools** — npm formatters, pip formatters/linters, Go tools
10. **CoC extensions** — all language servers in one step
11. **tmux** — optionally appends vim-tmux-navigator bindings to `~/.tmux.conf`
## Profiles
**Supported platforms:** macOS (Homebrew), Debian/Ubuntu (apt), Arch Linux (pacman), Fedora (dnf).
Default profile: `engineer`. Interactive installs ask for this profile before
plugins are installed; `--profile=minimal`, `--profile=engineer`, or
`--profile=full` selects it without prompting. `--yes` keeps the existing local
profile or uses `engineer`.
### Manual
```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
ln -sf ~/.vim/.vimrc ~/.vimrc
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
vim +PlugInstall +qall </dev/null
```
---
## LSP: Tiered Backend
Code intelligence is provided by one of two backends, chosen automatically at startup:
| Condition | Backend | Capabilities |
|-----------|---------|-------------|
| Vim 8.0.1453+ **and** Node.js 14.14+ | **CoC** | Full LSP, snippets, extension ecosystem |
| Vim 8.0+ (no Node.js) | **vim-lsp** | LSP via language server binaries, asyncomplete |
| Any Vim | **ALE** | Async linting + auto-fix (always active, both backends) |
Both CoC and vim-lsp expose the same key mappings so switching backends is transparent.
### With Node.js (CoC)
Install language server extensions from inside Vim — or let `install.sh` do it automatically:
```vim
" Put this in ${XDG_CONFIG_HOME:-~/.config}/chopsticks.vim.
let g:chopsticks_profile = 'minimal' " core navigation/editing/git/markdown
let g:chopsticks_profile = 'engineer' " default: LSP, ALE, syntax extras
let g:chopsticks_profile = 'full' " engineer + heavier Markdown feedback
:CocInstall coc-pyright " Python
:CocInstall coc-tsserver " JavaScript / TypeScript
:CocInstall coc-go " Go
:CocInstall coc-rust-analyzer " Rust
:CocInstall coc-json coc-yaml " JSON, YAML
:CocInstall coc-html coc-css " HTML, CSS/SCSS
:CocInstall coc-sh " Shell
:CocInstall coc-sql " SQL
```
`minimal` avoids LSP, ALE, completion plugins, extra language syntax plugins,
Startify, UndoTree, and browser Markdown preview. `full` keeps those and opts
into Markdown lint, format, spell, conceal, Marksman, and LSP virtual text.
**Markdown LSP** uses `marksman` as an external binary (not a CoC extension):
Project updates leave `~/.config/chopsticks.vim` alone, so put local choices
there instead of editing the managed `.vimrc`. The `,?` cheat sheet follows the
active profile and only shows keys for enabled features.
## Keys
Leader: `,`
```
Ctrl+p fuzzy find file gd go to definition
,rg ripgrep project K 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
```bash
brew install marksman # macOS
sudo pacman -S marksman # Arch
# or: ./install.sh handles it automatically
```
<details>
<summary><strong>All keybindings</strong></summary>
### Without Node.js (vim-lsp)
### Files
Open a source file, then run:
`Ctrl+p` find | `,b` buffers | `,rg` grep | `,rG` grep word | `,fh` recent | `,fl` lines | `,e` browser | `,E` browser (file dir) | `,,` last file
```vim
:LspInstallServer " detects filetype and installs the correct server
:LspStatus " check server status
```
### Code
Supported: Python, Go, Rust, TypeScript/JavaScript, Shell, HTML, CSS/SCSS, JSON, YAML, Markdown, SQL.
`gd` def | `gy` type | `gi` impl | `gr` refs | `K` docs | `[g` `]g` diagnostics | `[e` `]e` ALE errors | `,rn` rename | `,ca` action | `,o` outline | `,cr` run
---
### Edit
## Key Mappings
`,S`+2ch jump | `gc` comment | `cs"'` surround | `Alt+j/k` move line | `,u` undo tree | `,y` clipboard | `,*` replace word | `,F` re-indent | `,W` strip whitespace | `[<Space>` `]<Space>` blank lines
**Leader key:** `,` (comma)
Press `,` and wait 500 ms to see an interactive guide to all bindings (vim-which-key).
Press `,?` to open the built-in cheat sheet at any time.
### Survival
| Key | Action |
|-----|--------|
| `jk` | Exit insert mode → Normal (ergonomic Escape) |
| `Esc` | Exit insert / visual mode (standard) |
| `Ctrl+s` | Save file (normal and insert mode) |
| `,w` | Save file |
| `,x` | Save and quit |
| `,q` | Quit |
| `,?` | Open cheat sheet |
> **`Ctrl+s` note:** some terminals freeze on `Ctrl+s` (XON/XOFF). Add `stty -ixon`
> to your `~/.bashrc` / `~/.zshrc` to disable this permanently.
### Files and Buffers
| Key | Action |
|-----|--------|
| `Ctrl+p` | Fuzzy file search — git-aware (FZF) |
| `Ctrl+n` | Toggle file tree (NERDTree) |
| `,n` | Reveal current file in NERDTree |
| `,b` | Search open buffers (FZF) |
| `,rg` | Project-wide search (ripgrep + FZF) |
| `,rG` | Ripgrep for word under cursor (literal match) |
| `,rt` | Search tags (FZF) |
| `,gF` | Search git-tracked files (FZF) |
| `,l` | Next buffer |
| `,h` | Previous buffer |
| `,bd` | Close current buffer (preserves window layout) |
| `,,` | Switch to last file |
### Windows, Tabs, and tmux
| Key | Action |
|-----|--------|
| `Ctrl+h/j/k/l` | Navigate between Vim splits **and** tmux panes |
| `,=` | Increase window height |
| `,-` | Decrease window height |
| `,+` | Increase window width |
| `,_` | Decrease window width |
| `,tn` | New tab |
| `,tc` | Close tab |
| `,tl` | Toggle to last tab |
| `,tv` | Open terminal (vertical split) |
| `,th` | Open terminal (horizontal split) |
| `Esc Esc` | Exit terminal mode |
### Code Intelligence (CoC / vim-lsp)
| Key | Action |
|-----|--------|
| `gd` | Go to definition |
| `gy` | Go to type definition |
| `gi` | Go to implementation |
| `gr` | Show all references |
| `K` | Hover documentation |
| `[g` | Previous diagnostic |
| `]g` | Next diagnostic |
| `,rn` | Rename symbol |
| `,f` | Format selection |
| `,F` | Format whole file |
| `,ca` | Code action (cursor position) |
| `,o` | File outline (symbols) |
| `,ws` | Workspace symbols |
| `,cD` | Diagnostics list |
| `,qf` | Quick-fix current line (CoC) |
| `Tab` | Next completion item |
| `Shift+Tab` | Previous completion item |
| `Enter` | Confirm completion |
Text objects (CoC): `if`/`af` (function inner/around), `ic`/`ac` (class inner/around).
### Linting (ALE — always active)
| Key | Action |
|-----|--------|
| `[e` | Previous error / warning |
| `]e` | Next error / warning |
| `,aD` | Show error detail |
| `,ad` | Full diagnostics list |
Signs in the gutter: `X` = error, `!` = warning.
### Git (vim-fugitive)
| Key | Action |
|-----|--------|
| `,gs` | Git status (stage with `s`, commit with `cc`) |
| `,gc` | Git commit |
| `,gp` | Git push |
| `,gl` | Git pull |
| `,gd` | Git diff |
| `,gb` | Git blame |
### Search and Replace
| Key | Action |
|-----|--------|
| `n` / `N` | Next / previous match (cursor centered) |
| `//` | Search for visually selected text |
| `,*` | Search and replace word under cursor (file-wide) |
| `,rG` | Ripgrep word under cursor across project |
| `,<CR>` | Clear search highlight |
### Clipboard
| Key | Action |
|-----|--------|
| `,y` | Yank to system clipboard |
| `,Y` | Yank line to system clipboard |
| `,p` | Paste from system clipboard (after cursor) |
| `,P` | Paste from system clipboard (before cursor) |
### Editing and Navigation
| Key | Action |
|-----|--------|
| `s` + 2 chars | EasyMotion — jump anywhere on screen |
| `Space` | Toggle code fold |
| `Y` | Yank to end of line (consistent with `D`, `C`) |
| `Ctrl+d/u` | Half-page scroll (cursor stays centered) |
| `>` / `<` | Indent / dedent (keeps visual selection) |
| `Alt+j/k` | Move current line down / up |
| `0` | Jump to first non-blank character |
| `[q` / `]q` | Previous / next quickfix entry (vim-unimpaired) |
| `,u` | Toggle undo tree (visual branch history) |
| `,tt` | Toggle tagbar (code structure) |
| `F2` | Toggle paste mode |
| `F3` | Toggle absolute line numbers |
| `F4` | Toggle relative line numbers |
### Config and Utilities
| Key | Action |
|-----|--------|
| `,ev` | Edit `~/.vimrc` |
| `,sv` | Reload `~/.vimrc` |
| `,wa` | Save all open buffers |
| `,wd` | Change working directory to current file's location |
| `,cp` | Copy absolute file path to clipboard |
| `,cf` | Copy filename to clipboard |
| `,qo` / `,qc` | Open / close quickfix list |
---
## Features
### Startup Dashboard
Running `vim` (no arguments) opens a full-screen Startify dashboard showing recent
files, sessions, and bookmarks. Running `vim .` opens NERDTree on the left with
the dashboard on the right.
### Keybinding Guide
Press `,` and pause for 500 ms. A popup (vim-which-key) lists all leader bindings
organized into groups. No need to memorize everything upfront.
### Built-in Cheat Sheet
Press `,?` to open an inline reference covering modes, survival commands, search,
code intelligence, git, and clipboard — without leaving Vim.
### Session Management
```vim
:Obsess " start tracking the current session
:Obsess! " stop tracking
```
Sessions are stored in `~/.vim/sessions/` and automatically restored by vim-prosession
the next time you open Vim in the same directory.
### Project-Local Config
Drop a `.vimrc` in any project root to override settings for that project:
```vim
" project/.vimrc
set shiftwidth=2
let g:ale_python_black_options = '--line-length=100'
```
Loaded automatically via `set exrc`. Restricted to safe options via `set secure`.
### tmux Integration
`Ctrl+h/j/k/l` navigates seamlessly between Vim splits and tmux panes — no prefix
key, no mode switch.
**Vim side:** handled by vim-tmux-navigator (installed automatically).
**tmux side:** add to `~/.tmux.conf` (or let `install.sh` append it):
```tmux
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\S+\/)?g?(view|n?vim?x?)(diff)?$'"
bind-key -n 'C-h' if-shell "$is_vim" 'send-keys C-h' 'select-pane -L'
bind-key -n 'C-j' if-shell "$is_vim" 'send-keys C-j' 'select-pane -D'
bind-key -n 'C-k' if-shell "$is_vim" 'send-keys C-k' 'select-pane -U'
bind-key -n 'C-l' if-shell "$is_vim" 'send-keys C-l' 'select-pane -R'
```
Then reload: `tmux source-file ~/.tmux.conf`
> **Note:** the `C-l` binding replaces the terminal's screen-clear shortcut inside
> tmux. To restore it, add `bind C-l send-keys 'C-l'` — then use `prefix + C-l`.
### Large File Handling
Files over 10 MB automatically disable syntax highlighting, undo history, and
linting to prevent Vim from stalling.
### TTY / Console Support
Detected automatically when `$TERM` is `linux` or `screen`. In TTY mode:
- True color and cursorline disabled
- Powerline separators replaced with plain ASCII
- FZF preview windows disabled
- IndentLine guides disabled
- Syntax column limit reduced to 120 characters
- Simpler built-in status line used instead of airline
---
## Language Support
| Language | Indent | Formatter | Linter | LSP |
|----------|--------|-----------|--------|-----|
| Python | 4 sp | black, isort | flake8, pylint | coc-pyright |
| JavaScript | 2 sp | prettier | eslint | coc-tsserver |
| TypeScript | 2 sp | prettier | eslint, tsserver | coc-tsserver |
| Go | tab | gofmt, goimports | staticcheck | coc-go |
| Rust | 4 sp | rustfmt | cargo | coc-rust-analyzer |
| Shell | 2 sp | — | shellcheck | coc-sh |
| YAML | 2 sp | prettier | yamllint | coc-yaml |
| HTML | 2 sp | prettier | — | coc-html |
| CSS / SCSS | 2 sp | prettier | stylelint | coc-css |
| Less | 2 sp | prettier | — | — |
| JSON | 2 sp | prettier | — | coc-json |
| Markdown | 2 sp | prettier | markdownlint | marksman |
| SQL | 4 sp | sqlfluff | sqlfluff | — |
| Dockerfile | 2 sp | — | hadolint | — |
`install.sh` installs all formatters and linters automatically.
ALE runs them asynchronously; format-on-save is active for all supported languages.
---
## Plugins
### Navigation
- **NERDTree** — file tree explorer
- **fzf + fzf.vim** — fuzzy finder for files, buffers, tags, and ripgrep
### Git
- **vim-fugitive** — full Git integration inside Vim
- **vim-gitgutter** — diff signs in the sign column
`,gs` status | `,gd` diff | `,gb` blame | `,gc` commit | `,gp` push | `,gl` pull | `,gL` log graph | `,gC` FZF commits | `,gB` buffer commits | `]x` `[x` conflict
### LSP and Completion
- **coc.nvim** — full LSP + completion via Node.js (recommended)
- **vim-lsp** — pure VimScript LSP client (Node.js-free fallback)
- **vim-lsp-settings** — auto-configures language servers for vim-lsp
- **asyncomplete.vim** — async completion engine (vim-lsp mode)
### Windows
### Linting
- **ALE** — asynchronous lint engine, always active regardless of LSP backend
`Ctrl+hjkl` navigate (+ tmux) | `,z` maximize | `,h` `,l` buffers | `,bd` close buffer | `,=` `,` resize | `,tv` `,th` terminal | `Esc Esc` exit terminal
### UI
- **vim-airline** — status line and tabline
- **vim-startify** — startup dashboard with session management
- **vim-which-key** — keybinding hint popup on leader pause
- **indentLine** — indent guide lines (non-TTY only)
- **undotree** — visual undo branch history
- **tagbar** — code structure sidebar via ctags
### Markdown
### Editing
- **vim-surround** — change surrounding quotes, brackets, and tags
- **vim-commentary**`gc` to toggle comments
- **auto-pairs** — auto-close brackets and quotes
- **vim-easymotion** — jump anywhere on screen with 2 keystrokes (`s`)
- **vim-unimpaired** — bracket shortcut pairs (`[q`/`]q`, etc.)
- **targets.vim** — additional text objects
- **vim-snippets** — snippet library (used with CoC)
- **vim-tmux-navigator** — seamless `Ctrl+h/j/k/l` across Vim and tmux
`,mp` preview in browser | `,mt` table of contents
### Language Packs
- **vim-polyglot** — syntax for 100+ languages
- **vim-go** — Go syntax and tooling (LSP handled by coc-go)
### Toggle
### Session
- **vim-obsession** — continuous session saving
- **vim-prosession** — project-level session management
`F2` paste | `F3` line numbers | `F4` relative numbers | `F6` invisible chars | `,ss` spell check | `,af` format on save
### Color Schemes
- **gruvbox** (default), **dracula**, **solarized**, **onedark**
### Utilities
---
`,cp` copy full path | `,cf` copy filename | `,ev` edit vimrc | `,sv` reload vimrc | `,wa` save all | `:ChopsticksStatus` diagnostics
## Customization
</details>
### Change the color scheme
## LSP
In `~/.vimrc`, find and update the `colorscheme` line:
```vim
:LspInstallServer " auto-detects filetype
:LspStatus " check what's running
:ChopsticksStatus " see all tools + LSP + linters at a glance
colorscheme dracula " options: gruvbox, solarized, onedark
```
pylsp, gopls, rust-analyzer, clangd, sqls — no Node.js. JS/TS servers need Node.
Markdown LSP (`marksman`) is opt-in so prose buffers stay quiet by default.
True color is enabled automatically when `$COLORTERM=truecolor`. Falls back to
256-color, then 16-color in TTY.
ALE and vim-lsp coexist cleanly (`ale_disable_lsp=1`). ALE handles linting + formatting. vim-lsp handles everything else.
### Per-project overrides
## Markdown
Markdown opens in writing mode: wrapped text, no spell noise, no concealed
syntax, no sign column, no real-time markdownlint, and no Marksman diagnostics.
The explicit commands still work:
Create `.vimrc` in your project root. Anything placed here overrides the global
config for that directory:
```vim
,mp " preview in browser
,mt " table of contents
" my-project/.vimrc
set shiftwidth=2
let g:ale_python_black_options = '--line-length=120'
```
Opt into heavier Markdown tooling from your own vimrc before loading
chopsticks:
### Modify keybindings
```vim
let g:chopsticks_markdown_lint = 1
let g:chopsticks_markdown_format_on_save = 1
let g:chopsticks_markdown_lsp = 1
let g:chopsticks_markdown_spell = 1
let g:chopsticks_markdown_conceal = 1
let g:previm_enable_realtime = 1
```
Edit `~/.vimrc` directly (`,ev` opens it from inside Vim). Reload with `,sv`.
For Markdown LSP, install or select `marksman` first.
## Architecture
```
~/.vim/
├── .vimrc thin loader
├── modules/
│ ├── env.vim TTY detection, truecolor, skip built-in plugins
│ ├── plugins.vim vim-plug + 2425 plugins
│ ├── core.vim settings, keymaps, performance
│ ├── ui.vim solarized, statusline, startify
│ ├── editing.vim easymotion, yank highlight, blank lines
│ ├── navigation.vim fzf, netrw sidebar, windows, terminal
│ ├── lsp.vim vim-lsp, asyncomplete
│ ├── 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
```
Each module is self-contained. Comment out one line in `.vimrc` to disable it. Add your own with `call s:load('mine')`.
## Performance
| Metric | Value |
| ------------------------ | ------------------------------------------- |
| Lazy-loaded | 7 plugins (on command or filetype) |
| Built-in plugins skipped | 12 (gzip, tar, zip, vimball, logiPat, etc.) |
| Large file threshold | 10MB (auto-disables syntax + undo) |
| TTY large file | 500KB (syntax disabled) |
---
## Troubleshooting
| Problem | Fix |
| ------------------- | --------------------------------------------- |
| 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 |
| Everything slow | Large file? Auto-disabled >10MB |
| What's installed? | `:ChopsticksStatus` shows tools, LSP, linters |
**Plugins not loading**
More in the [wiki](https://github.com/m1ngsama/chopsticks/wiki).
```vim
:PlugInstall " install any missing plugins
:PlugUpdate " update all plugins
```
**CoC not working**
```bash
node --version # must be 14.14+
```
Inside Vim: `:CocInfo` for diagnostics, `:CocInstall <extension>` to add a language server.
**vim-lsp server not starting**
```vim
:LspInstallServer " install the correct server for the current filetype
:LspStatus " check server status
```
**Markdown LSP not starting**
`marksman` must be installed as a standalone binary (not a CoC extension):
```bash
brew install marksman # macOS
sudo pacman -S marksman # Arch
# or: ./install.sh handles it automatically
```
**Colors look wrong**
```bash
export TERM=xterm-256color # add to ~/.bashrc or ~/.zshrc
```
For true color: `export COLORTERM=truecolor`.
**ALE linters not found**
```bash
which flake8 black prettier eslint # verify tools are on PATH
```
If tools were installed with `pip install --user` or `npm install -g`, make sure
the respective bin directories are on `$PATH`.
**`Ctrl+s` freezes the terminal**
Add `stty -ixon` to your `~/.bashrc`, `~/.zshrc`, or `~/.config/fish/config.fish`.
This disables XON/XOFF flow control permanently.
---
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md). The two rules that matter: no Node.js dependencies, and don't regress startup time.
Bug reports and pull requests are welcome. Please follow these guidelines:
### Reporting a bug
1. Search [existing issues](https://github.com/m1ngsama/chopsticks/issues) before opening a new one.
2. Include your Vim version (`vim --version`), OS, and a minimal reproduction.
3. If the bug is plugin-specific, check whether it reproduces with a minimal config
(`vim -u NONE`) or only with chopsticks loaded.
### Proposing a change
1. Open an issue first to discuss the change, especially for non-trivial additions.
2. Keep the scope focused — one feature or fix per PR.
3. Follow existing conventions: augroups for autocmds, TTY guards for visual features,
conditional plugin loading where appropriate.
4. Update `CHANGELOG.md` with a summary of the change.
### Scope
Chopsticks is an opinionated configuration. Changes should align with the design
principles above — in particular, KISS (no icon fonts, minimal dependencies) and
TTY-compatibility. Neovim-only features and Lua configs are out of scope.
---
## Acknowledgements
Inspired by [amix/vimrc](https://github.com/amix/vimrc).
Built with [vim-plug](https://github.com/junegunn/vim-plug),
[coc.nvim](https://github.com/neoclide/coc.nvim),
[vim-lsp](https://github.com/prabirshrestha/vim-lsp),
and the broader Vim plugin community.
---
## Changelog
See [CHANGELOG.md](CHANGELOG.md).
## License
[MIT](LICENSE)
[MIT](LICENSE) © m1ng

13
coc-settings.json Normal file
View file

@ -0,0 +1,13 @@
{
"languageserver": {
"marksman": {
"command": "marksman",
"args": ["server"],
"filetypes": ["markdown"],
"rootPatterns": [".git", ".marksman.toml"]
}
},
"coc.preferences.formatOnSaveFiletypes": [
"markdown"
]
}

92
get.sh Executable file → Normal file
View file

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

1173
install.sh

File diff suppressed because it is too large Load diff

View file

@ -1,212 +0,0 @@
" core.vim — general settings, basic keymaps, performance, indentation
" filetype/syntax already enabled by plug#end() in plugins.vim
set number
set relativenumber
if !g:is_tty
set cursorline
endif
set nobackup
set scrolloff=10
set nowrap
set incsearch
set ignorecase
set smartcase
set showcmd
set showmode
set hlsearch
set history=1000
set wildmenu
set wildmode=list:longest
set wildignorecase
set wildignore=*.docx,*.jpg,*.png,*.gif,*.pdf,*.pyc,*.exe,*.flv,*.img,*.xlsx
set wildignore+=*/node_modules/*,*/.git/*,*/__pycache__/*,*/dist/*,*/build/*
set mouse=a
set encoding=utf-8
set foldmethod=indent
set foldlevel=99
set splitbelow
set splitright
set backspace=indent,eol,start
set nrformats-=octal
set autoread
set cmdheight=1
set hidden
set whichwrap+=<,>,h,l
set magic
set showmatch
set mat=2
set noerrorbells
set novisualbell
set t_vb=
set ttimeout
set ttimeoutlen=10
if $COLORTERM ==# 'gnome-terminal'
set t_Co=256
endif
if has("gui_running")
set guioptions-=T
set guioptions-=e
set t_Co=256
set guitablabel=%M\ %t
endif
set display+=lastline
set ffs=unix,dos,mac
set writebackup
if has('unix')
let s:swap_dir = expand(get(g:, 'chopsticks_swap_dir', '~/.vim/.swap'))
let &directory = s:swap_dir . '//,/tmp//'
silent! call mkdir(s:swap_dir, 'p', 0700)
endif
set swapfile
if has('persistent_undo')
set undofile
let &undodir = expand('~/.vim/.undo')
silent! call mkdir(&undodir, 'p', 0700)
endif
" ── Text, Tab and Indent ────────────────────────────────────────────────────
if g:is_tty
set listchars=tab:>-,trail:.,extends:>,precedes:<,nbsp:_
else
set listchars=tab:→\ ,trail,extends:▸,precedes:◂,nbsp
endif
set expandtab
set smarttab
set shiftwidth=4
set tabstop=4
set lbr
set tw=0
set autoindent
set smartindent
" ── Leader ──────────────────────────────────────────────────────────────────
let mapleader = ","
" ── Basic Keymaps ───────────────────────────────────────────────────────────
nnoremap <leader>w :w<cr>
nnoremap <leader>q :q<cr>
nnoremap <leader>x :x<cr>
nnoremap <silent> <leader><cr> :noh<cr>
nnoremap <leader>bd :Bclose<cr>
nnoremap <leader>ba :bufdo bd<cr>
nnoremap <leader>l :bnext<cr>
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 <M-j> :m .+1<CR>==
nnoremap <M-k> :m .-2<CR>==
vnoremap <M-j> :m '>+1<CR>gv=gv
vnoremap <M-k> :m '<-2<CR>gv=gv
nnoremap <silent> <leader>ss :setlocal spell!<CR>:echo 'Spell: ' . (&spell ? 'ON' : 'OFF')<CR>
nnoremap <silent> <F2> :set paste!<CR>:echo 'Paste: ' . (&paste ? 'ON' : 'OFF')<CR>
nnoremap <silent> <F3> :set invnumber<CR>:echo 'Line numbers: ' . (&number ? 'ON' : 'OFF')<CR>
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>
vnoremap < <gv
vnoremap > >gv
nnoremap n nzzzv
nnoremap N Nzzzv
vnoremap // y/\V<C-r>=escape(@",'/\')<CR><CR>
nnoremap <silent> <C-s> :w<CR>
inoremap <silent> <C-s> <C-o>:w<CR>
nnoremap <C-d> <C-d>zz
vnoremap <C-d> <C-d>zz
nnoremap <C-u> <C-u>zz
vnoremap <C-u> <C-u>zz
if has('clipboard')
nnoremap <leader>y "+y
vnoremap <leader>y "+y
nnoremap <leader>Y "+Y
nnoremap <leader>p "+p
vnoremap <leader>p "+p
nnoremap <leader>P "+P
vnoremap <leader>P "+P
endif
nnoremap <leader>qo :copen<CR>
nnoremap <leader>qc :cclose<CR>
augroup ChopstickResize
autocmd!
autocmd VimResized * wincmd =
augroup END
" ── Performance ─────────────────────────────────────────────────────────────
set synmaxcol=200
set lazyredraw
set complete-=i
if executable('rg')
set grepprg=rg\ --vimgrep\ --smart-case
set grepformat=%f:%l:%c:%m
endif
set updatetime=300
set shortmess+=cI
if g:is_tty
set signcolumn=auto
set synmaxcol=120
endif
" non-TTY signcolumn is set in ui.vim (=yes, fixed-width to prevent jitter)
" ── Project-Local Config ────────────────────────────────────────────────────
set exrc
set secure
set sessionoptions=blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal
if has("patch-8.1.0360")
set diffopt=filler,internal,context:3,algorithm:histogram,indent-heuristic
endif
" ── Format Options ──────────────────────────────────────────────────────────
augroup ChopstickFormatOptions
autocmd!
autocmd FileType * setlocal formatoptions-=c formatoptions-=r formatoptions-=o formatoptions+=j
augroup END
augroup ChopstickPaste
autocmd!
autocmd InsertLeave * set nopaste
augroup END
set timeoutlen=500

View file

@ -1,69 +0,0 @@
" editing.vim — EasyMotion, yank highlight, search auto-clear, undotree
" ── EasyMotion ──────────────────────────────────────────────────────────────
let g:EasyMotion_do_mapping = 0
let g:EasyMotion_smartcase = 1
if exists('g:plugs["vim-easymotion"]')
nmap <Leader>S <Plug>(easymotion-overwin-f2)
nmap <Leader>j <Plug>(easymotion-j)
nmap <Leader>k <Plug>(easymotion-k)
endif
" ── UndoTree ────────────────────────────────────────────────────────────────
if exists('g:plugs["undotree"]')
nnoremap <F5> :UndotreeToggle<CR>
nnoremap <leader>u :UndotreeToggle<CR>
endif
" ── Yank Highlight ──────────────────────────────────────────────────────────
if exists('##TextYankPost') && has('timers')
function! s:YankHighlight() abort
if v:event.operator !=# 'y' | return | endif
let l:m = matchadd('IncSearch',
\ printf('\%%>%dl\%%<%dl', line("'[") - 1, line("']") + 1))
call timer_start(150, {-> execute('silent! call matchdelete(' . l:m . ')')})
endfunction
augroup ChopstickYankHL
autocmd!
autocmd TextYankPost * call s:YankHighlight()
augroup END
endif
" ── Blank Line Insertion (replaces vim-unimpaired) ──────────────────────────
nnoremap <silent> [<Space> :<C-u>put! =repeat(nr2char(10), v:count1)<CR>'[
nnoremap <silent> ]<Space> :<C-u>put =repeat(nr2char(10), v:count1)<CR>
" ── Auto-Clear Search Highlight ─────────────────────────────────────────────
augroup ChopstickSearchHL
autocmd!
autocmd CursorHold * if get(v:, 'hlsearch', 0) | let v:hlsearch = 0 | endif
augroup END
" ── Overlength Highlight (only chars past textwidth, not the whole column) ─
function! s:OverLengthApply() abort
if exists('w:overlength_match')
silent! call matchdelete(w:overlength_match)
unlet w:overlength_match
endif
if &textwidth <= 0 || &buftype !=# '' | return | endif
let w:overlength_match = matchadd('OverLength', '\%>' . &textwidth . 'v.\+', -1)
endfunction
function! s:OverLengthDefineHL() abort
hi default OverLength ctermbg=52 ctermfg=NONE guibg=#3a1f1f guifg=NONE
endfunction
augroup ChopstickOverLength
autocmd!
autocmd ColorScheme * call s:OverLengthDefineHL()
autocmd OptionSet textwidth call s:OverLengthApply()
autocmd BufWinEnter,WinEnter,FileType * call s:OverLengthApply()
augroup END
call s:OverLengthDefineHL()

View file

@ -1,53 +0,0 @@
" env.vim — environment detection (must load first)
set nocompatible
let g:is_tty = empty($TERM) || $TERM ==# 'dumb' || $TERM =~# 'linux'
\ || $TERM =~# 'screen' || &term =~# 'builtin'
let g:has_true_color = ($COLORTERM ==# 'truecolor' || $COLORTERM ==# '24bit')
let g:chopsticks_profile = get(g:, 'chopsticks_profile', 'engineer')
if index(['minimal', 'engineer', 'full'], g:chopsticks_profile) < 0
let g:chopsticks_profile = 'engineer'
endif
let s:profile_full = g:chopsticks_profile ==# 'full'
let s:profile_minimal = g:chopsticks_profile ==# 'minimal'
let g:chopsticks_enable_lsp = get(g:, 'chopsticks_enable_lsp',
\ !s:profile_minimal)
let g:chopsticks_enable_lint = get(g:, 'chopsticks_enable_lint',
\ !s:profile_minimal)
let g:chopsticks_enable_extra_languages = get(g:,
\ 'chopsticks_enable_extra_languages', !s:profile_minimal)
let g:chopsticks_enable_ui_extras = get(g:, 'chopsticks_enable_ui_extras',
\ !s:profile_minimal)
let g:chopsticks_enable_markdown_preview = get(g:,
\ 'chopsticks_enable_markdown_preview', !s:profile_minimal)
let g:chopsticks_markdown_lint = get(g:, 'chopsticks_markdown_lint',
\ s:profile_full)
let g:chopsticks_markdown_format_on_save = get(g:,
\ 'chopsticks_markdown_format_on_save', s:profile_full)
let g:chopsticks_markdown_lsp = get(g:, 'chopsticks_markdown_lsp',
\ s:profile_full)
let g:chopsticks_markdown_spell = get(g:, 'chopsticks_markdown_spell',
\ s:profile_full)
let g:chopsticks_markdown_conceal = get(g:, 'chopsticks_markdown_conceal',
\ s:profile_full)
let g:chopsticks_lsp_virtual_text = get(g:, 'chopsticks_lsp_virtual_text',
\ s:profile_full && !g:is_tty)
" Skip built-in plugins we never use
let g:loaded_2html_plugin = 1
let g:loaded_getscriptPlugin = 1
let g:loaded_gzip = 1
let g:loaded_logiPat = 1
let g:loaded_rrhelper = 1
let g:loaded_tarPlugin = 1
let g:loaded_vimballPlugin = 1
let g:loaded_zipPlugin = 1
let g:loaded_tutor_mode_plugin = 1
let g:loaded_spellfile_plugin = 1
let g:loaded_openPlugin = 1
let g:loaded_manpager_plugin = 1

View file

@ -1,27 +0,0 @@
" git.vim — Fugitive mappings, GitGutter config, conflict navigation
" ── GitGutter ───────────────────────────────────────────────────────────────
let g:gitgutter_map_keys = 0
let g:gitgutter_sign_added = '+'
let g:gitgutter_sign_modified = '~'
let g:gitgutter_sign_removed = '-'
let g:gitgutter_sign_removed_first_line = '^'
let g:gitgutter_sign_modified_removed = '~'
" ── Fugitive ────────────────────────────────────────────────────────────────
if exists('g:plugs["vim-fugitive"]')
nnoremap <leader>gs :Git status<CR>
nnoremap <leader>gc :Git commit<CR>
nnoremap <leader>gp :Git push<CR>
nnoremap <leader>gl :Git pull<CR>
nnoremap <leader>gd :Gdiffsplit<CR>
nnoremap <leader>gb :Git blame<CR>
nnoremap <leader>gL :Git log --oneline --graph -20<CR>
endif
" ── Conflict Navigation ────────────────────────────────────────────────────
nnoremap <silent> ]x /^\(<<<<<<<\\|=======\\|>>>>>>>\)<CR>
nnoremap <silent> [x ?^\(<<<<<<<\\|=======\\|>>>>>>>\)<CR>

View file

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

View file

@ -1,70 +0,0 @@
" lint.vim — ALE async linting and format-on-save
if !g:chopsticks_enable_lint
finish
endif
let g:ale_disable_lsp = 1
let s:ale_linters = {
\ 'python': ['flake8', 'pylint'],
\ 'javascript': ['eslint'],
\ 'typescript': ['eslint'],
\ 'go': ['staticcheck'],
\ 'rust': ['cargo'],
\ 'c': ['cc'],
\ 'sh': ['shellcheck'],
\ 'yaml': ['yamllint'],
\ 'dockerfile': ['hadolint'],
\ 'css': ['stylelint'],
\ 'scss': ['stylelint'],
\ 'sql': ['sqlfluff'],
\}
if g:chopsticks_markdown_lint
let s:ale_linters.markdown = ['markdownlint']
endif
let g:ale_linters = s:ale_linters
let s:ale_fixers = {
\ '*': ['remove_trailing_lines', 'trim_whitespace'],
\ 'python': ['black', 'isort'],
\ 'javascript': ['prettier', 'eslint'],
\ 'typescript': ['prettier', 'eslint'],
\ 'go': ['goimports'],
\ 'rust': ['rustfmt'],
\ 'c': ['clang-format'],
\ 'json': ['prettier'],
\ 'yaml': ['prettier'],
\ 'html': ['prettier'],
\ 'css': ['prettier'],
\ 'scss': ['prettier'],
\ 'less': ['prettier'],
\ 'sql': ['sqlfluff'],
\}
if g:chopsticks_markdown_format_on_save
let s:ale_fixers.markdown = ['prettier']
endif
let g:ale_fixers = s:ale_fixers
let g:ale_fix_on_save = get(g:, 'ale_fix_on_save', 1)
let g:ale_python_isort_options = '--profile black'
let g:ale_sign_error = 'X'
let g:ale_sign_warning = '!'
let g:ale_lint_on_text_changed = 'normal'
let g:ale_lint_on_insert_leave = 1
let g:ale_lint_on_enter = 1
let g:ale_lint_delay = 200
let g:ale_echo_delay = 100
let g:ale_virtualtext_cursor = get(g:, 'ale_virtualtext_cursor', 'disabled')
if exists('g:plugs["ale"]')
nnoremap <silent> [e :ALEPrevious<cr>
nnoremap <silent> ]e :ALENext<cr>
nnoremap <silent> <leader>aD :ALEDetail<cr>
nnoremap <silent> <leader>af :let g:ale_fix_on_save = !g:ale_fix_on_save
\ <bar> echo 'Format on save: ' . (g:ale_fix_on_save ? 'ON' : 'OFF')<cr>
endif

View file

@ -1,88 +0,0 @@
" lsp.vim — vim-lsp settings, asyncomplete, LSP buffer keymaps
if !g:chopsticks_enable_lsp
finish
endif
let g:lsp_settings_lazyload = 1
let g:lsp_settings_filetype_python = ['pylsp']
let g:lsp_settings_filetype_go = ['gopls']
let g:lsp_settings_filetype_rust = ['rust-analyzer']
let g:lsp_settings_filetype_typescript = ['typescript-language-server']
let g:lsp_settings_filetype_javascript = ['typescript-language-server']
let g:lsp_settings_filetype_c = ['clangd']
let g:lsp_settings_filetype_sh = ['bash-language-server']
let g:lsp_settings_filetype_html = ['vscode-html-language-server']
let g:lsp_settings_filetype_css = ['vscode-css-language-server']
let g:lsp_settings_filetype_scss = ['vscode-css-language-server']
let g:lsp_settings_filetype_json = ['vscode-json-language-server']
let g:lsp_settings_filetype_yaml = ['yaml-language-server']
let g:lsp_settings_filetype_sql = ['sqls']
if g:chopsticks_markdown_lsp
let g:lsp_settings_filetype_markdown = ['marksman']
endif
let g:lsp_diagnostics_virtual_text_enabled = g:chopsticks_lsp_virtual_text
let g:lsp_diagnostics_virtual_text_delay = 200
let g:lsp_diagnostics_highlights_enabled = !g:is_tty
let g:lsp_document_highlight_enabled = !g:is_tty
let g:lsp_document_highlight_delay = 200
let g:lsp_signs_enabled = 1
let g:lsp_diagnostics_echo_cursor = 1
let g:lsp_diagnostics_echo_delay = 100
let g:lsp_completion_documentation_enabled = 1
let g:lsp_signs_error = {'text': 'X'}
let g:lsp_signs_warning = {'text': '!'}
let g:lsp_signs_information = {'text': 'i'}
let g:lsp_signs_hint = {'text': '>'}
" ── Completion ──────────────────────────────────────────────────────────────
if has('patch-8.1.1517')
set completeopt=menuone,noinsert,noselect,popup
else
set completeopt=menuone,noinsert,noselect
endif
set pumheight=15
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>"
" ── Buffer Keymaps ──────────────────────────────────────────────────────────
function! s:on_lsp_buffer_enabled() abort
setlocal omnifunc=lsp#complete
if !g:is_tty && &filetype !=# 'markdown'
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> K <plug>(lsp-hover)
nmap <buffer> <leader>rn <plug>(lsp-rename)
nmap <buffer> <leader>ca <plug>(lsp-code-action)
nmap <buffer> <leader>f <plug>(lsp-document-format)
xmap <buffer> <leader>f <plug>(lsp-document-range-format)
nmap <buffer> <leader>o <plug>(lsp-document-symbol-search)
nmap <buffer> <leader>ws <plug>(lsp-workspace-symbol-search)
nmap <buffer> <leader>cD <plug>(lsp-document-diagnostics)
endfunction
augroup lsp_install
autocmd!
autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END

View file

@ -1,123 +0,0 @@
" navigation.vim — FZF, netrw, buffer/window management, terminal
" ── netrw (built-in file browser) ───────────────────────────────────────────
let g:netrw_liststyle = 3
let g:netrw_banner = 0
let g:netrw_browse_split = 4
let g:netrw_winsize = 25
let g:netrw_altv = 1
let g:netrw_list_hide = '\(^\|\s\s\)\zs\.\S\+'
let g:netrw_list_hide .= ',\.pyc$,node_modules,\.git,__pycache__,\.DS_Store'
function! s:ToggleSidebar(...) abort
let l:dir = a:0 ? a:1 : getcwd()
if getbufvar(winbufnr(1), '&filetype') ==# 'netrw' && getwinvar(1, '&winfixwidth')
let l:cur = winnr()
1wincmd w
close
if l:cur > 1
execute (l:cur - 1) . 'wincmd w'
endif
return
endif
execute 'topleft vertical 30new'
execute 'Explore ' . fnameescape(l:dir)
setlocal winfixwidth
setlocal bufhidden=wipe
wincmd p
endfunction
nnoremap <silent> <leader>e :call <SID>ToggleSidebar()<CR>
nnoremap <silent> <leader>E :call <SID>ToggleSidebar(expand('%:p:h'))<CR>
augroup ChopstickNetrw
autocmd!
autocmd FileType netrw setlocal bufhidden=wipe
augroup END
" ── FZF ─────────────────────────────────────────────────────────────────────
function! s:SmartFiles() abort
if isdirectory('.git') || finddir('.git', '.;') !=# ''
GFiles
else
Files
endif
endfunction
if exists('g:plugs["fzf.vim"]')
nnoremap <C-p> :call <SID>SmartFiles()<CR>
nnoremap <leader>b :Buffers<CR>
nnoremap <leader>rg :Rg<CR>
nnoremap <leader>rG :RgWord<CR>
nnoremap <leader>rt :Tags<CR>
nnoremap <leader>gF :GFiles<CR>
nnoremap <leader>fh :History<CR>
nnoremap <leader>fc :Commands<CR>
nnoremap <leader>fm :Marks<CR>
nnoremap <leader>fl :BLines<CR>
nnoremap <leader>fL :Lines<CR>
nnoremap <leader>f/ :History/<CR>
nnoremap <leader>f: :History:<CR>
nnoremap <leader>gC :Commits<CR>
nnoremap <leader>gB :BCommits<CR>
endif
let g:fzf_layout = { 'down': '40%' }
if g:is_tty
let g:fzf_preview_window = []
else
let g:fzf_preview_window = ['right:50%', 'ctrl-/']
endif
function! s:Preview() abort
return g:is_tty ? {} : fzf#vim#with_preview()
endfunction
command! -bang -nargs=* Rg
\ call fzf#vim#grep(
\ 'rg --column --line-number --no-heading --color=always --smart-case -- '
\ .shellescape(<q-args>), 1, s:Preview(), <bang>0)
command! -bang -nargs=* RgWord
\ call fzf#vim#grep(
\ 'rg --column --line-number --no-heading --color=always --smart-case -F -- '
\ .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
if exists('t:maximize_session')
execute t:maximize_session
unlet t:maximize_session
echo 'Window: restored'
else
let t:maximize_session = winrestcmd()
resize | vertical resize
echo 'Window: MAXIMIZED'
endif
endfunction
nnoremap <silent> <leader>z :call <SID>ToggleMaximize()<CR>
" ── Terminal ────────────────────────────────────────────────────────────────
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
endif

View file

@ -1,66 +0,0 @@
" plugins.vim — vim-plug declarations
let data_dir = has('nvim') ? stdpath('data') . '/site' : '~/.vim'
if empty(glob(data_dir . '/autoload/plug.vim'))
silent execute '!curl -fLo '.data_dir.'/autoload/plug.vim --create-dirs '
\ . 'https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
augroup PlugBootstrap
autocmd!
autocmd VimEnter * PlugInstall --sync | source $MYVIMRC
augroup END
endif
call plug#begin('~/.vim/plugged')
" ── Navigation & Search ──────────────────────────────────────────────────────
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'
" ── Git ──────────────────────────────────────────────────────────────────────
Plug 'tpope/vim-fugitive'
Plug 'airblade/vim-gitgutter'
" ── Editing ──────────────────────────────────────────────────────────────────
Plug 'tpope/vim-surround'
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_lint
" ── Linting & Formatting ────────────────────────────────────────────────
Plug 'dense-analysis/ale'
endif
if g:chopsticks_enable_lsp
" ── LSP + Completion ─────────────────────────────────────────────────────
Plug 'prabirshrestha/vim-lsp'
Plug 'mattn/vim-lsp-settings'
Plug 'prabirshrestha/asyncomplete.vim'
Plug 'prabirshrestha/asyncomplete-lsp.vim'
endif
" ── Language Syntax ──────────────────────────────────────────────────────────
Plug 'preservim/vim-markdown', { 'for': 'markdown' }
if g:chopsticks_enable_markdown_preview
Plug 'previm/previm', { 'on': 'PrevimOpen' }
endif
if g:chopsticks_enable_extra_languages
Plug 'pangloss/vim-javascript', { 'for': ['javascript', 'javascript.jsx'] }
Plug 'HerringtonDarkholme/yats.vim', { 'for': ['typescript', 'typescript.tsx'] }
Plug 'fatih/vim-go', { 'for': 'go' }
endif
" ── UI ───────────────────────────────────────────────────────────────────────
if g:chopsticks_enable_ui_extras
Plug 'mbbill/undotree', { 'on': 'UndotreeToggle' }
Plug 'mhinz/vim-startify'
endif
Plug 'lifepillar/vim-solarized8'
if !empty($TMUX)
Plug 'christoomey/vim-tmux-navigator'
endif
call plug#end()

View file

@ -1,387 +0,0 @@
" tools.vim — run file, sudo save, quickfix, helpers
" ── Buffer Close ───────────────────────────────────────────────────────────
command! Bclose call <SID>BufcloseCloseIt()
function! <SID>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 ──────────────────────────────────────────────────────────────
nnoremap <leader>F gg=G``
vnoremap <leader>F =
nnoremap <leader>wa :wa<CR>
nnoremap <silent> <Leader>= :exe "resize " . (winheight(0) * 3/2)<CR>
nnoremap <silent> <Leader>- :exe "resize " . (winheight(0) * 2/3)<CR>
nnoremap <leader><leader> <c-^>
nnoremap <leader>W :%s/\s\+$//<CR>:let @/=''<CR>
vnoremap <leader>W :s/\s\+$//<CR>:let @/=''<CR>gv
nnoremap <leader>ev :edit $MYVIMRC<CR>
nnoremap <leader>sv :unlet! g:chopsticks_loaded<CR>:execute 'source ' . fnameescape($MYVIMRC)<CR>:echo "vimrc reloaded"<CR>
nnoremap <leader>* :%s/\<<C-r><C-w>\>//g<Left><Left>
vnoremap <leader>* :s///g<Left><Left><Left>
if has('clipboard')
nnoremap <leader>cp :let @+ = expand("%:p")<CR>:echo "Copied: " . expand("%:p")<CR>
nnoremap <leader>cf :let @+ = expand("%:t")<CR>:echo "Copied: " . expand("%:t")<CR>
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('<afile>')) |
\ call s:MkNonExDir(expand('<afile>'), +expand('<abuf>')) |
\ 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('<afile>'))
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' | execute '!gcc -o /tmp/a.out ' . l:file . ' && /tmp/a.out'
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 <leader>cr :call <SID>RunFile()<CR>
" ── Sudo Save ───────────────────────────────────────────────────────────────
cnoremap w!! w !sudo tee > /dev/null %
" ── QuickFix ────────────────────────────────────────────────────────────────
augroup ChopstickQF
autocmd!
autocmd QuickFixCmdPost [^l]* cwindow
autocmd QuickFixCmdPost l* lwindow
augroup END
nnoremap <silent> ]q :cnext<CR>
nnoremap <silent> [q :cprev<CR>
" ── 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:LspCheck(ft, server) abort
if !get(g:, 'chopsticks_enable_lsp', 1)
return s:Off(a:ft, 'LSP disabled by profile')
endif
if !exists('*lsp#get_server_names')
return ' -- ' . a:ft . ' (vim-lsp not loaded)'
endif
let l:dir = expand('~/.local/share/vim-lsp-settings/servers/' . a:server)
if isdirectory(l:dir)
return ' OK ' . a:ft . ' (' . a:server . ')'
endif
return ' -- ' . a:ft . ' (:LspInstallServer in a ' . a:ft . ' file)'
endfunction
function! s:ChopsticksStatus() abort
let l:lines = []
call add(l:lines, 'chopsticks status')
call add(l:lines, repeat('─', 50))
call add(l:lines, '')
call add(l:lines, '── system tools ──')
call add(l:lines, s:Check('fzf', 'fzf'))
call add(l:lines, s:Check('ripgrep', 'rg'))
call add(l:lines, s:Check('git', 'git'))
call add(l:lines, s:Check('curl', 'curl'))
call add(l:lines, s:Check('node', 'node'))
call add(l:lines, s:Check('python3', 'python3'))
call add(l:lines, s:Check('go', 'go'))
call add(l:lines, '')
call add(l:lines, '── lsp servers ── (:LspInstallServer to install)')
call add(l:lines, s:LspCheck('python', 'pylsp'))
call add(l:lines, s:LspCheck('go', 'gopls'))
call add(l:lines, s:LspCheck('rust', 'rust-analyzer'))
call add(l:lines, s:LspCheck('typescript', 'typescript-language-server'))
call add(l:lines, s:LspCheck('c/c++', 'clangd'))
call add(l:lines, s:LspCheck('bash', 'bash-language-server'))
call add(l:lines, s:LspCheck('html', 'vscode-html-language-server'))
call add(l:lines, s:LspCheck('json', 'vscode-json-language-server'))
call add(l:lines, s:LspCheck('yaml', 'yaml-language-server'))
call add(l:lines, s:LspCheck('markdown', 'marksman'))
call add(l:lines, s:LspCheck('sql', 'sqls'))
call add(l:lines, '')
call add(l:lines, '── linters ──')
if get(g:, 'chopsticks_enable_lint', 1)
call add(l:lines, s:Check('flake8 (python)', 'flake8'))
call add(l:lines, s:Check('pylint (python)', 'pylint'))
call add(l:lines, s:Check('eslint (js/ts)', 'eslint'))
call add(l:lines, s:Check('staticcheck (go)', 'staticcheck'))
call add(l:lines, s:Check('shellcheck (sh)', 'shellcheck'))
call add(l:lines, s:Check('yamllint (yaml)', 'yamllint'))
call add(l:lines, s:Check('hadolint (docker)', 'hadolint'))
if get(g:, 'chopsticks_markdown_lint', 0)
call add(l:lines, s:Check('markdownlint (md)', 'markdownlint'))
else
call add(l:lines, s:Off('markdownlint (md)', 'disabled by default'))
endif
else
call add(l:lines, s:Off('ALE linters', 'lint disabled by profile'))
endif
call add(l:lines, '')
call add(l:lines, '── formatters ── (format-on-save is ' . (get(g:, 'ale_fix_on_save', 0) ? 'ON' : 'OFF') . ')')
if get(g:, 'chopsticks_enable_lint', 1)
call add(l:lines, s:Check('black (python)', 'black'))
call add(l:lines, s:Check('isort (python)', 'isort'))
call add(l:lines, s:Check('prettier (js/ts/json)', 'prettier'))
if get(g:, 'chopsticks_markdown_format_on_save', 0)
call add(l:lines, s:Check('prettier (md)', 'prettier'))
else
call add(l:lines, s:Off('prettier (md)', 'disabled by default'))
endif
call add(l:lines, s:Check('goimports (go)', 'goimports'))
call add(l:lines, s:Check('rustfmt (rust)', 'rustfmt'))
call add(l:lines, s:Check('clang-format (c)', 'clang-format'))
else
call add(l:lines, s:Off('ALE formatters', 'lint disabled by profile'))
endif
call add(l:lines, '')
let l:ok = len(filter(copy(l:lines), 'v:val =~# " OK "'))
let l:miss = len(filter(copy(l:lines), 'v:val =~# " -- "'))
call add(l:lines, repeat('─', 50))
call add(l:lines, ' ' . l:ok . ' ready, ' . l:miss . ' missing')
call add(l:lines, '')
call add(l:lines, ' Install missing tools with ./install.sh')
if get(g:, 'chopsticks_enable_lsp', 1)
call add(l:lines, ' Install LSP servers with :LspInstallServer')
endif
let l:name = '__ChopsticksStatus__'
if bufwinnr(l:name) > 0
execute bufwinnr(l:name) . 'wincmd w | bd'
endif
execute 'botright new ' . l:name
resize 45
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile
setlocal nowrap nonumber norelativenumber signcolumn=no
call setline(1, l:lines)
setlocal nomodifiable readonly
nnoremap <buffer> <silent> q :bd<CR>
endfunction
command! ChopsticksStatus call s:ChopsticksStatus()
" ── Cheat Sheet (,?) ────────────────────────────────────────────────────────
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 ──────────────────',
\ ' Ctrl+p find file',
\ ' ,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, [
\ ' gd definition',
\ ' gy type definition',
\ ' gi implementation',
\ ' gr references',
\ ' K hover docs',
\ ' ,rn rename',
\ ' ,ca code action',
\ ' ,f format',
\ ' ,o outline',
\ ' [g ]g LSP diagnostics',
\ ' :LspInstallServer setup LSP',
\ ])
endif
call add(l:lines, ' ,cr run file')
if l:has_previm
call add(l:lines, ' ,mp markdown preview')
endif
call add(l:lines, ' ,mt table of contents')
if l:has_lint
call extend(l:lines, [
\ ' [e ]e ALE errors',
\ ' ,af format on save',
\ ])
endif
call extend(l:lines, [
\ '',
\ ' ── edit ──────────────────',
\ ' gc comment',
\ ' ,S+2ch easymotion jump',
\ ' cs"'' surround',
\ ])
if l:has_undotree
call add(l:lines, ' ,u undo tree')
endif
call extend(l:lines, [
\ ' ,y ,p clipboard y/p (v)',
\ ' Alt+j/k move line (v)',
\ ' ,* replace word (v)',
\ ' ,F re-indent (v)',
\ ' ,W strip trailing (v)',
\ '',
\ ' ── git ───────────────────',
\ ' ,gs status',
\ ' ,gd diff',
\ ' ,gb blame',
\ ' ,gc commit',
\ ' ,gp push',
\ ' ,gl pull',
\ ' ,gL log graph',
\ ' ,gC FZF commits',
\ ' [x ]x conflict markers',
\ '',
\ ' ── windows ───────────────',
\ ' Ctrl+hjkl navigate splits',
\ ' ,h ,l prev / next buf',
\ ' ,bd close buffer',
\ ' ,z maximize toggle',
\ ' ,= ,- resize height',
\ ' ,tv ,th terminal v / h',
\ ' ]q [q next / prev qf',
\ ' ,qo ,qc open / close qf',
\ '',
\ ' ── toggle ────────────────',
\ ' F2 paste mode',
\ ' F3 line numbers',
\ ' F4 relative numbers',
\ ' F6 invisible chars',
\ ' ,ss spell check',
\ '',
\ ' ── survival ──────────────',
\ ' ,w save',
\ ' ,q quit',
\ ' ,x save + quit',
\ ' Ctrl+s save (any mode)',
\ ' jk exit insert',
\ ' :w!! sudo save',
\ ' ,ev edit vimrc',
\ ' ,sv reload vimrc',
\ ' :ChopsticksStatus health',
\ ])
execute 'vertical botright new ' . l:name
vertical resize 42
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile
setlocal nowrap nonumber norelativenumber signcolumn=no
setlocal winfixwidth
call setline(1, l:lines)
setlocal nomodifiable readonly
nnoremap <buffer> <silent> q :bd<CR>
nnoremap <buffer> <silent> <leader>? :bd<CR>
endfunction
nnoremap <silent> <leader>? :call <SID>CheatSheet()<CR>

View file

@ -1,226 +0,0 @@
" ui.vim — colorscheme, statusline, startify
" ── Colorscheme (Solarized Dark — matches tmux palette) ────────────────────
if g:has_true_color && has('termguicolors') && !g:is_tty
let &t_8f = "\<Esc>[38;2;%lu;%lu;%lum"
let &t_8b = "\<Esc>[48;2;%lu;%lu;%lum"
set termguicolors
endif
set background=dark
function! s:WarnSolarized8Missing(...) abort
echohl WarningMsg
echom 'chopsticks: solarized8 not installed — run :PlugInstall'
echohl None
endfunction
if !g:is_tty
try
colorscheme solarized8
catch /^Vim\%((\a\+)\)\=:E185/
colorscheme default
if has('timers')
call timer_start(500, function('s:WarnSolarized8Missing'))
else
augroup ChopstickColorschemeWarn
autocmd!
autocmd VimEnter * call s:WarnSolarized8Missing()
augroup END
endif
endtry
else
colorscheme default
endif
" ── Window separators, fillchars, cursorline visibility ────────────────────
if !g:is_tty
set fillchars+=vert:│,eob:\
endif
function! s:UIPolish() abort
hi VertSplit ctermbg=234 ctermfg=240 guibg=#002b36 guifg=#586e75 cterm=NONE gui=NONE
hi CursorLine ctermbg=235 guibg=#0c4452 cterm=NONE gui=NONE
hi CursorLineNr ctermbg=235 ctermfg=136 guibg=#0c4452 guifg=#b58900 cterm=bold gui=bold
hi SignColumn ctermbg=234 guibg=#002b36
endfunction
augroup ChopstickUIPolish
autocmd!
autocmd ColorScheme * call s:UIPolish()
augroup END
if !g:is_tty | call s:UIPolish() | endif
if has("gui_running")
if has("gui_gtk2") || has("gui_gtk3")
set guifont=Hack\ 12,Source\ Code\ Pro\ 12,Monospace\ 12
elseif has("gui_win32")
set guifont=Consolas:h11:cANSI
endif
endif
" ── Startify ────────────────────────────────────────────────────────────────
if exists('g:plugs["vim-startify"]')
let g:startify_lists = [
\ { 'type': 'sessions', 'header': [' Sessions'] },
\ { 'type': 'files', 'header': [' Recent Files'] },
\ { 'type': 'dir', 'header': [' Current Dir'] },
\ ]
let g:startify_bookmarks = [{'v': '~/.vimrc'}]
let g:startify_session_persistence = 1
let g:startify_session_autoload = 1
let g:startify_change_to_vcs_root = 1
let g:startify_fortune_use_unicode = 0
let g:startify_enable_special = 0
let g:startify_files_number = 8
let g:startify_padding_left = 4
function! s:SetupDirView() abort
if argc() != 1 || !isdirectory(argv()[0]) || exists('s:std_in')
return
endif
let l:dir = fnameescape(argv()[0])
execute 'cd ' . l:dir
vertical rightbelow vnew
if exists(':Startify') == 2
Startify
else
enew
endif
wincmd h
vertical resize 30
setlocal winfixwidth
wincmd l
endfunction
if !g:is_tty
augroup ChopstickStartup
autocmd!
autocmd StdinReadPre * let s:std_in = 1
autocmd VimEnter * nested call <SID>SetupDirView()
augroup END
endif
endif
" ── Status Line (native — Solarized palette, seamless with tmux bar) ───────
set laststatus=2
set noshowmode
function! s:SLDefineColors() abort
hi SLNormal ctermbg=136 ctermfg=234 cterm=bold guibg=#b58900 guifg=#002b36 gui=bold
hi SLInsert ctermbg=33 ctermfg=234 cterm=bold guibg=#268bd2 guifg=#002b36 gui=bold
hi SLVisual ctermbg=125 ctermfg=234 cterm=bold guibg=#d33682 guifg=#002b36 gui=bold
hi SLReplace ctermbg=160 ctermfg=234 cterm=bold guibg=#dc322f guifg=#002b36 gui=bold
hi SLCommand ctermbg=37 ctermfg=234 cterm=bold guibg=#2aa198 guifg=#002b36 gui=bold
hi SLBody ctermbg=235 ctermfg=245 cterm=none guibg=#073642 guifg=#93a1a1
hi SLFlag ctermbg=235 ctermfg=136 cterm=none guibg=#073642 guifg=#b58900
hi SLRight ctermbg=235 ctermfg=240 cterm=none guibg=#073642 guifg=#586e75
hi SLGit ctermbg=235 ctermfg=37 cterm=none guibg=#073642 guifg=#2aa198
hi SLFtype ctermbg=235 ctermfg=244 cterm=none guibg=#073642 guifg=#839496
endfunction
augroup SLColors
autocmd!
autocmd ColorScheme * call s:SLDefineColors()
augroup END
call s:SLDefineColors()
function! SLMode() abort
let l:m = mode()
if l:m ==# 'n' | return [' N ', 'SLNormal' ]
elseif l:m ==# 'i' | return [' I ', 'SLInsert' ]
elseif l:m =~# '[vV]' || l:m ==# "\<C-v>" | return [' V ', 'SLVisual' ]
elseif l:m ==# 'R' | return [' R ', 'SLReplace']
elseif l:m ==# 'c' | return [' C ', 'SLCommand']
elseif l:m ==# 't' | return [' T ', 'SLInsert' ]
else | return [' ' . l:m . ' ', 'SLNormal']
endif
endfunction
function! SLGit() abort
if !exists('*FugitiveHead') | return '' | endif
let l:b = FugitiveHead()
return empty(l:b) ? '' : ' ' . l:b . ' '
endfunction
function! SLAle() abort
if !exists('*ale#statusline#Count') | return '' | endif
let l:c = ale#statusline#Count(bufnr(''))
let l:e = l:c.error + l:c.style_error
let l:w = l:c.warning + l:c.style_warning
if l:e == 0 && l:w == 0 | return '' | endif
return printf(' E:%d W:%d ', l:e, l:w)
endfunction
function! SLFlags() abort
let l:f = ''
if &paste | let l:f .= ' PASTE' | endif
if &spell | let l:f .= ' SPELL' | endif
return empty(l:f) ? '' : l:f . ' '
endfunction
function! SLBuild() abort
let [l:label, l:hl] = SLMode()
let l:s = '%#' . l:hl . '#' . l:label
let l:s .= '%#SLBody# %f '
let l:s .= '%#SLFlag#%m%r'
let l:s .= '%#SLFlag#' . SLFlags()
let l:s .= '%#SLBody#%='
let l:s .= '%#SLFlag#' . SLAle()
let l:s .= '%#SLGit#' . SLGit()
let l:s .= '%#SLFtype# %y '
let l:s .= '%#SLRight# %l:%c %P '
return l:s
endfunction
set statusline=%!SLBuild()
if g:is_tty
set statusline=%f\ %h%w%m%r\ %=%(%l,%c%V\ %=\ %P%)
endif
" ── Tabline (native — shows listed buffers when >1, else hidden) ───────────
function! s:TLDefineColors() abort
hi TabLine ctermbg=234 ctermfg=244 cterm=none guibg=#002b36 guifg=#839496 gui=none
hi TabLineSel ctermbg=235 ctermfg=136 cterm=bold guibg=#073642 guifg=#b58900 gui=bold
hi TabLineFill ctermbg=234 ctermfg=240 cterm=none guibg=#002b36 guifg=#586e75 gui=none
endfunction
augroup TLColors
autocmd!
autocmd ColorScheme * call s:TLDefineColors()
augroup END
call s:TLDefineColors()
function! TLBuild() abort
let l:s = ''
let l:cur = bufnr('%')
for l:b in range(1, bufnr('$'))
if !buflisted(l:b) || getbufvar(l:b, '&buftype') !=# '' | continue | endif
let l:hl = (l:b == l:cur) ? '%#TabLineSel#' : '%#TabLine#'
let l:name = bufname(l:b)
let l:name = empty(l:name) ? '[No Name]' : fnamemodify(l:name, ':t')
let l:mod = getbufvar(l:b, '&modified') ? ' +' : ''
let l:s .= l:hl . ' ' . l:b . ' ' . l:name . l:mod . ' '
endfor
let l:s .= '%#TabLineFill#%='
return l:s
endfunction
if !g:is_tty
set showtabline=1
set tabline=%!TLBuild()
endif
" ── SignColumn: always reserve a column so width never jitters ─────────────
if !g:is_tty
set signcolumn=yes
endif

View file

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