Compare commits

...

9 commits

Author SHA1 Message Date
97ca2be139 Fix shellcheck SC2012: use find instead of ls for plugin count
Some checks are pending
test / startup (macos-latest) (push) Waiting to run
test / startup (ubuntu-latest) (push) Waiting to run
test / shellcheck (push) Waiting to run
2026-04-22 01:02:18 +08:00
7b4b153f4b docs: GitHub polish — hero README, CI, templates, demo GIF
Add centered hero layout with demo GIF, 5 shields.io badges,
collapsible keybindings, performance table, and architecture tree.
Add GitHub Actions CI (startup test + shellcheck), issue/PR templates,
CONTRIBUTING.md, and VHS demo recording.
2026-04-22 00:58:56 +08:00
dbb11c9473 perf: 23ms → 19ms — drop vim-unimpaired, runtime tuning
Startup: replace vim-unimpaired (2.5ms) with 2-line blank line insertion.
29 plugins now. All [q/]q, [e/]e, [x/]x mappings were already ours.

Runtime:
- ALE lint_delay=200ms, echo_delay=100ms — less thrashing during edits
- LSP virtual_text_delay=200ms, highlight_delay=200ms, echo_delay=100ms
- Disable gitgutter default mappings (map_keys=0) — we don't use them
- Merge two BufReadPre large-file autocmds into one (single getfsize)
- Remove redundant filetype detection autocmds (Vim 9.2 handles natively)
2026-04-22 00:46:00 +08:00
f0d4431eef perf: 39ms → 23ms startup (40% faster)
- Guard against double-sourcing when exrc loads CWD .vimrc (saves ~8ms)
- Lazy-load EasyMotion, undotree, previm, goyo, limelight (on-demand)
- Lazy-load language syntax plugins (for: filetype)
- Enable lsp_settings_lazyload (defer server init to VimEnter)
- Skip unused built-in plugins (2html, gzip, tar, zip, vimball, etc.)
- Remove redundant filetype/syntax calls (plug#end already enables them)
- Add shortmess+=I to skip intro screen
2026-04-22 00:36:44 +08:00
fa59d5be8f docs: rewrite README and QUICKSTART — clean, short, for geeks
Cut README from 330 to 124 lines. No fluff, no repeated tables.
QUICKSTART is now a compact cheat sheet, not a tutorial.
Both point to the wiki for deep dives.
2026-04-22 00:21:15 +08:00
6044fc5fcb refactor: modular architecture — split .vimrc into 12 self-contained modules
Replace the monolithic 1268-line .vimrc with a thin loader that sources
12 modules under modules/ (env, plugins, core, ui, editing, navigation,
lsp, lint, git, writing, languages, tools). Each module is independently
readable and can be toggled by commenting one line.

Add interactive tutorial (tutor/chopsticks.tutor) with 10 lessons covering
all features. Users can run :ChopsticksLearn to start.
2026-04-22 00:05:10 +08:00
84d999f91f feat: major feature upgrade — zen mode, run file, smart search, and more
New plugins (3):
- vim-sleuth: auto-detect indentation from existing files
- goyo.vim + limelight.vim: zen mode for focused writing (,zen)

New built-in features (8):
- Run current file (,cr) — auto-detects Python/Go/Rust/JS/C/Shell/etc.
- Yank highlight — flashes yanked text for visual feedback
- Auto-clear search highlight after cursor stops moving
- Git conflict marker navigation ([x / ]x)
- Window maximize toggle (,z)
- Sudo save (:w!!)
- QuickFix auto-open + ]q/[q navigation
- ALE error/warning count in statusline

More FZF mappings:
- ,fh recent files, ,fl buffer lines, ,fL all lines
- ,fc commands, ,fm marks, ,f/ search history, ,f: command history

README rewritten with compelling feature showcase (30 plugins).
Cheat sheet and QUICKSTART updated with all new features.
2026-04-21 23:42:41 +08:00
cc328cebf2 fix: round 2 — completion speed, octal numbers, dead files
- Reduce asyncomplete popup delay 200ms→50ms for snappier completion
- Add nrformats-=octal: Ctrl-A on 007 now goes to 008, not 010
- Add formatoptions+=j: joining comment lines removes duplicate leaders
- Remove coc-settings.json (vestigial, CoC not used)
- Fix .gitignore: was blocking all *.json files unnecessarily
- Update README: Python LSP is pylsp only (pyright removed)
2026-04-21 23:32:59 +08:00
575ff2c489 fix: comprehensive bug audit — 14 fixes for performance and usability
Critical bugs:
- Statusline colors never applied on startup (SLDefineColors called after colorscheme)
- noremap 0 ^ broke operator-pending mode (d0, c0, y0 all wrong)
- Two Python LSPs running simultaneously (pyright requires Node.js)
- asyncomplete_auto_completeopt=1 overriding completeopt settings
- ALE/vim-lsp diagnostic overlap (tsserver, gopls duplicated)
- Ctrl-s in insert mode shifted cursor right by one
- Startify buftype= clearing made buffer writable

Performance & usability:
- Switch to vim-solarized8 (proper termguicolors, maintained)
- Add ale_disable_lsp=1 for clean ALE/vim-lsp coexistence
- Remove redundant gofmt from Go fixers (goimports is superset)
- Add isort --profile black for formatter compatibility
- Remove dead godef references (unmaintained since 2020)
- Set lazyredraw globally, tw=0 default
- Remove vestigial coc-settings.json symlink from install.sh
2026-04-21 23:28:59 +08:00
26 changed files with 1817 additions and 1777 deletions

23
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,23 @@
---
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

@ -0,0 +1,14 @@
---
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**

13
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,13 @@
## 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 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

47
.github/demo.tape vendored Normal file
View file

@ -0,0 +1,47 @@
Output .github/demo.gif
Set Shell bash
Set FontSize 14
Set Width 960
Set Height 540
Set Theme "Builtin Solarized Dark"
Set TypingSpeed 50ms
Set Padding 10
Type "vim ~/.vim/modules/core.vim"
Enter
Sleep 1.5s
# Show the code with solarized theme and statusline
Sleep 1s
# Use Ctrl+p for fuzzy find
Type "\x10"
Sleep 1s
Type "lsp"
Sleep 1s
Escape
Sleep 0.5s
# Go to definition
Type "gg"
Sleep 0.3s
Type "/mapleader"
Enter
Sleep 1s
# Show cheat sheet
Type ",?"
Sleep 2s
Type "q"
Sleep 0.5s
# Open file browser
Type ",e"
Sleep 1.5s
Escape
Sleep 0.5s
# Quit
Type ":qa!"
Enter
Sleep 0.5s

66
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,66 @@
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: vim -es -u .vimrc -c 'PlugInstall --sync' -c 'qa!' 2>&1 || true
- name: Test startup
run: |
vim -u .vimrc -es -N -c 'qa!' 2>&1
echo "Vim exited cleanly"
- name: Verify modules load
run: |
vim -u .vimrc -N -c 'redir! > /tmp/test.txt | echo len(g:plugs) | redir END | qa!' 2>/dev/null
PLUGS=$(cat /tmp/test.txt | tr -d '[:space:]')
echo "Plugins registered: $PLUGS"
if [ "$PLUGS" -lt 25 ]; then
echo "FAIL: expected 25+ plugins, got $PLUGS"
exit 1
fi
- name: Measure startup time
run: |
vim -u .vimrc --startuptime /tmp/startup.log -c 'qa!' 2>/dev/null
tail -1 /tmp/startup.log
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Shellcheck install.sh
run: shellcheck install.sh get.sh

2
.gitignore vendored
View file

@ -1,5 +1,3 @@
*.json
!coc-settings.json
*.swp *.swp
*.swo *.swo
.DS_Store .DS_Store

1166
.vimrc

File diff suppressed because it is too large Load diff

30
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,30 @@
# 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 --startuptime /tmp/s.log -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. Test on both macOS and Linux
## 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 `vim -u .vimrc --startuptime /tmp/s.log -c qa!`

View file

@ -1,221 +1,96 @@
# Quick Start # Quick Start
Five minutes from zero to a working Vim environment. Five minutes from zero to a working Vim setup.
--- ## Install
## Step 0: Vim Basics (2 minutes)
> **When confused, press `Esc` until things feel normal again.**
Vim is **modal** — the keyboard behaves differently depending on which mode you are in.
| Mode | Purpose | Enter | Leave |
|------|---------|-------|-------|
| **Normal** | Navigate and run commands | default on startup | — |
| **Insert** | Type text | `i` before / `a` after / `o` new line | `Esc` or `jk` |
| **Visual** | Select text | `v` char / `V` line | `Esc` |
### 4 commands that get you out of any jam
| Command | Action |
|---------|--------|
| `Esc` or `jk` | Exit insert / visual mode → Normal |
| `:q!` then `Enter` | Force quit without saving |
| `,x` | Save and quit |
| `,w` or `Ctrl+s` | Save |
Once in Normal mode, press `,?` to open the cheat sheet.
---
## Step 1: Install
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash
``` ```
Traditional: Open vim. Plugins install automatically on first launch (30-60s). Restart vim.
```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
cd ~/.vim && ./install.sh
```
Non-interactive / CI: ## Modes
```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --yes
```
--- | Mode | Enter | Leave |
|------|-------|-------|
| Normal | startup default | — |
| Insert | `i` / `a` / `o` | `Esc` or `jk` |
| Visual | `v` / `V` | `Esc` |
## Step 2: Open Vim ## Survival
```bash
vim # startup dashboard (recent files + sessions)
vim . # startup dashboard, current directory listed
vim myfile # edit a specific file
```
> **First launch:** Vim will automatically install plugins on the first open
> (takes 3060 seconds depending on network). This is normal — wait for it
> to finish, then restart Vim.
---
## Step 3: Set Up LSP
Open a source file, then run:
```vim
:LspInstallServer
```
This auto-detects the filetype and installs the correct language server.
vim-lsp itself runs on pure VimScript — no Node.js required. However,
some language servers (JS/TS, HTML, CSS, JSON, YAML) are npm packages
that need Node.js to run. Python, Go, and Rust servers don't need it.
Check status:
```vim
:LspStatus
```
**Markdown LSP** (`marksman`) needs a standalone binary:
```bash
brew install marksman # macOS
sudo pacman -S marksman # Arch
# install.sh handles this automatically
```
---
## The Keys That Matter
``` ```
,? Open cheat sheet (all bindings in one place) Esc / jk back to Normal
Esc / jk Exit insert mode → Normal ,w save
Ctrl+s Save ,x save + quit
Ctrl+p Fuzzy find file :q! force quit
,e File browser (netrw) Ctrl+s save from any mode
gd Go to definition ,? cheat sheet
K Show documentation :ChopsticksLearn interactive tutorial
[g / ]g Prev / next LSP diagnostic
,rn Rename symbol
,rG Search word under cursor (ripgrep)
,gs Git status
,mp Markdown live preview in browser
,w / ,x Save / Save+quit
``` ```
--- ## Find things
## Daily Use
### Navigate Code
| Key | Action |
|-----|--------|
| `gd` | Go to definition |
| `gy` | Go to type definition |
| `gi` | Go to implementation |
| `gr` | List references |
| `K` | 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` + 2 chars | EasyMotion: jump anywhere |
### Manage Errors
| Key | Action |
|-----|--------|
| `]g` | Jump to next LSP diagnostic |
| `[g` | Jump to previous diagnostic |
| `K` | Read the error message |
| `,ca` | Apply code action / auto-fix |
### Markdown
| Key | Action |
|-----|--------|
| `,mp` | Open live preview in browser |
| `,mt` | Table of contents (side window) |
| `zr` / `zm` | Unfold / fold all headings |
Formatting in the buffer is live: `**bold**` renders as bold,
headings hide their `#` markers. Raw syntax reappears when
the cursor enters that line.
### Git Workflow
``` ```
,gs git status (stage with 's', commit with 'cc') Ctrl+p fuzzy find file (git-aware)
,gd diff current file ,rg ripgrep project
,b search buffers
,fh recent files
,e file browser
,, last file
```
## Write code
```
gd go to definition
K hover docs
,rn rename symbol
,ca code action
,f format
,cr run current file
Tab / S-Tab cycle completions
```
Install language servers with `:LspInstallServer` (auto-detects filetype).
## Git
```
,gs status (s=stage, cc=commit)
,gd diff
,gb blame ,gb blame
,gc commit
,gp push ,gp push
,gl pull ]x / [x conflict markers
``` ```
--- ## Edit
## Quick Reference Card
``` ```
BASICS s + 2 chars EasyMotion jump
Esc / jk Exit insert mode → Normal gc toggle comment
Ctrl+s Save cs"' change surrounding " to '
:q! + Enter Emergency quit Alt+j / Alt+k move line
,? Open cheat sheet ,u undo tree
,y clipboard yank
FILES
Ctrl+p Fuzzy find file (git-aware)
,e File browser (netrw)
,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 Definition | gy Type def | gi Impl | gr References
K Show documentation
[g / ]g Prev / next LSP diagnostic
[e / ]e Prev / next ALE error
,rn Rename symbol
,ca Code action
,f Format buffer / selection
MARKDOWN
,mp Live preview | ,mt Table of contents
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 / ,th Terminal (vertical / horizontal)
Esc Esc Exit terminal mode
,u Undo tree
SEARCH
/text Forward | ?text Backward | n next | N prev
// Search visually selected text
,* Replace word under cursor (file-wide)
``` ```
--- ## Navigate
See [README.md](README.md) for the complete reference. ```
Ctrl+h/j/k/l splits + tmux panes
,h / ,l prev / next buffer
,z maximize window
,tv / ,th terminal
```
## Write prose
```
,zen zen mode (Goyo + Limelight)
,mp markdown preview in browser
,mt table of contents
```
See [README](README.md) for the full reference. See the [wiki](https://github.com/m1ngsama/chopsticks/wiki) for deep dives.

540
README.md
View file

@ -1,14 +1,22 @@
# chopsticks <p align="center">
<img src=".github/demo.gif" alt="chopsticks demo" width="720">
</p>
> Flowing vim for any machine — SSH servers included. <h1 align="center">chopsticks</h1>
> Solarized · vim-lsp (no Node.js) · Markdown-first · One-command install.
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](LICENSE) <p align="center">
[![Vim 8.0+](https://img.shields.io/badge/Vim-8.0%2B-brightgreen?style=flat-square)](https://www.vim.org/) <strong>Vim for engineers. 29 plugins, 19ms startup, works over SSH.</strong>
[![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux-lightgrey?style=flat-square)](#installation) </p>
[![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) <p align="center">
[![Stars](https://img.shields.io/github/stars/m1ngsama/chopsticks?style=flat-square)](https://github.com/m1ngsama/chopsticks/stargazers) <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>
---
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash
@ -16,437 +24,157 @@ curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | b
--- ---
## Contents ## Why
- [Design Principles](#design-principles) 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.
- [Requirements](#requirements)
- [Installation](#installation)
- [LSP](#lsp)
- [Key Mappings](#key-mappings)
- [Markdown](#markdown)
- [Features](#features)
- [Plugins](#plugins)
- [Customization](#customization)
- [Troubleshooting](#troubleshooting)
--- 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.
## Design Principles **19ms startup** with 29 plugins, LSP, linting, and a hand-built statusline. Faster than most people's empty vimrc.
| Principle | What it means | ## What's in the box
|-----------|--------------|
| **Flowing writing** | Every plugin earns its place by reducing interruptions to thought |
| **No Node.js** | vim-lsp runs on pure VimScript — works on any machine, including SSH servers |
| **Solarized** | One palette, everywhere — vim statusline matches tmux bar exactly |
| **TTY-aware** | SSH and console environments degrade gracefully without breaking |
| **KISS** | No icon fonts, no Nerd Font glyphs — plain ASCII throughout |
--- | | |
|-|-|
| **LSP** | completion, go-to-def, hover, rename, code actions — pure VimScript ([vim-lsp](https://github.com/prabirshrestha/vim-lsp)) |
| **Lint + format** | [ALE](https://github.com/dense-analysis/ale) runs black, prettier, gofmt, rustfmt on save |
| **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) |
| **Zen mode** | `,zen` — [Goyo](https://github.com/junegunn/goyo.vim) + [Limelight](https://github.com/junegunn/limelight.vim) |
| **Run file** | `,cr` — auto-detects Python, Go, Rust, JS, C, Shell, and more |
| **TTY-aware** | degrades gracefully on SSH, console, slow links — never breaks |
| **19ms startup** | lazy-loaded plugins, deferred LSP init, zero redundant work |
## Requirements ## Install
| Tool | Role |
|------|------|
| Vim 8.0+ | Required — `install.sh` installs it if missing |
| git | Required |
| curl | Required |
| ripgrep | Recommended — enables `,rg` project search |
| fzf | Recommended — enables `Ctrl+p` fuzzy finder |
| Node.js | Optional — enables npm formatters (prettier, eslint) |
`install.sh` detects your environment and installs missing dependencies.
---
## Installation
### One command
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash
``` ```
Non-interactive / CI: Or manually:
```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --yes
```
### Git clone
```bash ```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
cd ~/.vim && ./install.sh cd ~/.vim && ./install.sh
``` ```
### What the installer does Supports macOS (brew), Debian/Ubuntu (apt), Arch (pacman), Fedora (dnf).
1. Detects OS and package manager First launch installs plugins automatically (30-60s). Restart vim when done.
2. Verifies or installs `curl`, `git`, `vim`
3. Backs up existing `~/.vimrc`, then symlinks `~/.vimrc → ~/.vim/.vimrc`
4. Installs vim-plug and runs `:PlugInstall`
5. Offers to install system tools (ripgrep, fzf, ctags, shellcheck, hadolint, marksman)
6. Offers to install npm formatters (prettier, eslint, etc.) — requires Node.js
7. Offers to install Python formatters/linters (black, isort, flake8, etc.)
8. Offers to install Go tools (gopls, goimports, staticcheck)
9. Offers to append vim-tmux-navigator bindings to `~/.tmux.conf`
**Supported platforms:** macOS (Homebrew), Debian/Ubuntu (apt), Arch (pacman), Fedora (dnf). ## Keys
--- Leader: `,` — press `,?` for the full cheat sheet inside vim.
```
Ctrl+p fuzzy find file gd go to definition
,rg ripgrep project K hover docs
,gs git status ,cr run current file
,zen zen mode ,f format
,w save ,q quit
jk exit insert mode ,? cheat sheet
```
<details>
<summary><strong>All keybindings</strong></summary>
### Files
`Ctrl+p` find | `,b` buffers | `,rg` grep | `,rG` grep word | `,fh` recent | `,e` browser | `,,` last file
### Code
`gd` def | `gy` type | `gi` impl | `gr` refs | `K` docs | `[g` `]g` diagnostics | `,rn` rename | `,ca` action | `,o` outline | `,cr` run
### Edit
`s`+2ch jump | `gc` comment | `cs"'` surround | `Alt+j/k` move line | `,u` undo tree | `,y` clipboard | `,*` replace word
### Git
`,gs` status | `,gd` diff | `,gb` blame | `,gc` commit | `,gp` push | `]x` `[x` conflict
### Windows
`Ctrl+hjkl` navigate (+ tmux) | `,z` maximize | `,h` `,l` buffers | `,tv` terminal | `Esc Esc` exit terminal
### Writing
`,zen` zen mode | `,mp` markdown preview | `,mt` table of contents
</details>
## LSP ## LSP
Code intelligence is provided by **vim-lsp** — a pure VimScript LSP client with no ```vim
Node.js dependency. It works on any machine, including servers accessed via SSH. :LspInstallServer " auto-detects filetype
:LspStatus " check what's running
```
Install a language server for the current file: pylsp, gopls, rust-analyzer, clangd, marksman, sqls — no Node.js. JS/TS servers need Node.
ALE and vim-lsp coexist cleanly (`ale_disable_lsp=1`). ALE handles linting + formatting. vim-lsp handles everything else.
## Architecture
```
~/.vim/
├── .vimrc thin loader (12 lines)
├── modules/
│ ├── env.vim TTY detection, truecolor
│ ├── plugins.vim vim-plug + 29 plugins
│ ├── core.vim settings, keymaps, performance
│ ├── ui.vim solarized, statusline, startify
│ ├── editing.vim easymotion, yank highlight
│ ├── navigation.vim fzf, netrw, windows, terminal
│ ├── lsp.vim vim-lsp, asyncomplete
│ ├── lint.vim ale, format-on-save
│ ├── git.vim fugitive, gitgutter
│ ├── writing.vim markdown, previm, zen mode
│ ├── languages.vim vim-go, filetype settings
│ └── tools.vim cheat sheet, run file, helpers
└── tutor/
└── chopsticks.tutor
```
Each module is self-contained. Comment out one line in `.vimrc` to disable it. Add your own with `call s:load('mine')`.
## Learn
```vim ```vim
:LspInstallServer " auto-detects filetype and installs the correct server :ChopsticksLearn " interactive tutorial — 10 lessons
:LspStatus " check server status ,? " cheat sheet (every binding)
``` ```
Supported languages and their servers: ## Performance
| Language | Server | | Metric | Value |
|----------|--------| |--------|-------|
| Python | pylsp / pyright | | Startup time | **19ms** (29 plugins loaded) |
| JavaScript / TypeScript | typescript-language-server | | Lazy-loaded | 8 plugins (on command or filetype) |
| Go | gopls | | Built-in plugins skipped | 10 (gzip, tar, zip, vimball, etc.) |
| Rust | rust-analyzer | | Runtime lint delay | 200ms (no thrashing during edits) |
| C / C++ | clangd | | Large file threshold | 10MB (auto-disables syntax + undo) |
| Shell | bash-language-server | | TTY large file | 500KB (syntax disabled) |
| HTML | vscode-html-language-server |
| CSS / SCSS | vscode-css-language-server | Measured with `vim --startuptime`. We benchmark every change.
| JSON | vscode-json-language-server |
| YAML | yaml-language-server |
| Markdown | marksman |
| SQL | sqls |
**Note:** While vim-lsp itself needs no Node.js, some language servers (TypeScript,
HTML, CSS, JSON, YAML) are npm packages that require Node.js to run. Python (pylsp),
Go (gopls), and Rust (rust-analyzer) language servers do not need Node.js.
**Markdown LSP** requires `marksman` as a standalone binary:
```bash
brew install marksman # macOS
sudo pacman -S marksman # Arch
# or: install.sh handles it automatically
```
---
## Key Mappings
**Leader key:** `,` (comma)
Press `,?` at any time to open the built-in cheat sheet.
### Files and Buffers
| Key | Action |
|-----|--------|
| `Ctrl+p` | Fuzzy file search — git-aware (FZF) |
| `,e` | Open netrw file browser |
| `,E` | Open netrw in vertical split |
| `,b` | Search open buffers (FZF) |
| `,rg` | Project-wide search (ripgrep + FZF) |
| `,rG` | Ripgrep word under cursor (fixed-string) |
| `,,` | Switch to last file |
| `,l` / `,h` | Next / previous buffer |
| `,bd` | Close current buffer (preserves window layout) |
| `,wa` | Save all open buffers |
| `,cd` | Change working directory to current file's directory |
### Code Intelligence (vim-lsp)
| Key | Action |
|-----|--------|
| `gd` | Go to definition |
| `gy` | Go to type definition |
| `gi` | Go to implementation |
| `gr` | Show references |
| `K` | Hover documentation |
| `[g` / `]g` | Previous / next LSP diagnostic |
| `[e` / `]e` | Previous / next ALE error |
| `,rn` | Rename symbol |
| `,f` | Format buffer / selection |
| `,ca` | Code action |
| `,o` | File outline (symbols) |
| `,ws` | Workspace symbols |
| `Tab` / `Shift+Tab` | Navigate completion popup |
| `Enter` | Confirm completion |
### Markdown
| Key | Action |
|-----|--------|
| `,mp` | Open live preview in browser (previm) |
| `,mt` | Table of contents (side window) |
| `zr` / `zm` | Unfold / fold all headings |
### Git (vim-fugitive)
| Key | Action |
|-----|--------|
| `,gs` | Git status |
| `,gc` | Git commit |
| `,gp` | Git push |
| `,gl` | Git pull |
| `,gd` | Git diff |
| `,gb` | Git blame |
### Editing
| Key | Action |
|-----|--------|
| `s` + 2 chars | EasyMotion — jump anywhere on screen |
| `gc` | Toggle comment (visual mode too) |
| `Space` | Toggle code fold |
| `Y` | Yank to end of line |
| `Ctrl+d` / `Ctrl+u` | Half-page scroll, cursor centred |
| `Alt+j` / `Alt+k` | Move line down / up (normal and visual) |
| `,u` | Undo tree (visual branch history) |
| `F2` | Toggle paste mode |
| `F3` / `F4` | Toggle line numbers / relative numbers |
| `F5` | Toggle undo tree |
| `F6` | Toggle invisible characters |
| `gV` | Reselect last paste |
| `//` | Search visual selection |
### Survival
| Key | Action |
|-----|--------|
| `jk` | Exit insert mode |
| `Esc` | Exit insert / visual mode |
| `jk` | Exit insert mode |
| `Ctrl+s` | Save (any mode) |
| `,w` | Save |
| `,x` | Save and quit |
| `,q` | Quit |
| `,?` | Open cheat sheet |
### Windows, Tabs, tmux
| Key | Action |
|-----|--------|
| `Ctrl+h/j/k/l` | Navigate Vim splits **and** tmux panes |
| `,tv` / `,th` | Open terminal (vertical / horizontal split) |
| `Esc Esc` | Exit terminal mode |
| `,tn` / `,tc` | New tab / close tab |
| `,tl` | Toggle to last tab |
| `,ev` / `,sv` | Edit / reload `~/.vimrc` |
| `,cp` / `,cf` | Copy file path / filename to clipboard |
| `,*` | Search and replace word under cursor |
| `,F` | Re-indent entire file |
| `,W` | Strip trailing whitespace |
| `,ms` | Open scratch markdown buffer |
| `,ss` | Toggle spell checking |
---
## Markdown
chopsticks treats Markdown as a first-class language.
### In-buffer rendering (concealment)
`vim-markdown` hides syntax markers and renders formatting inline:
- `**bold**` displays as bold text
- `# Heading` hides the `#` characters
- Tables align automatically
The raw syntax reappears when the cursor enters that line.
### Live browser preview (previm)
```vim
,mp " open rendered preview in browser — updates on every save
```
No Node.js required. Uses `open` (macOS) or `xdg-open` (Linux).
### Table of contents
```vim
,mt " open TOC in a side window — press Enter to jump to heading
```
---
## Features
### Statusline
A native, hand-written statusline using the Solarized palette:
```
N ~/.vimrc [+] main [vim] 42:7 68%
```
- Mode block changes colour by mode (Normal=yellow, Insert=blue, Visual=magenta, Replace=red)
- Git branch via vim-fugitive
- Background matches tmux status bar for a seamless bottom band
### Session Management
```vim
:Obsess " start tracking the current session
:Obsess! " stop tracking
```
Sessions auto-restore when you open Vim in the same directory.
### Project-Local Config
Drop a `.vimrc` in any project root to override settings:
```vim
" my-project/.vimrc
set shiftwidth=2
let g:ale_python_black_options = '--line-length=100'
```
### tmux Integration
`Ctrl+h/j/k/l` navigates seamlessly between Vim splits and tmux panes.
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'
```
### TTY / SSH Support
Detected automatically when `$TERM` is unset, `dumb`, `linux`, `screen`, or contains `builtin`. In TTY mode:
- True colour and cursorline disabled
- FZF preview windows disabled
- IndentLine guides disabled
- Simplified statusline (no colour)
- Syntax column limit reduced to 120 characters
### Large File Handling
Files over 10 MB automatically disable syntax highlighting, undo history, and
linting to prevent stalling.
---
## Plugins
### Navigation
- **fzf + fzf.vim** — fuzzy finder for files, buffers, tags, ripgrep
### Git
- **vim-fugitive** — full Git integration
- **vim-gitgutter** — diff signs in the sign column
### LSP and Completion
- **vim-lsp** — pure VimScript LSP client
- **vim-lsp-settings** — auto-configures language servers
- **asyncomplete.vim** — async completion engine
- **asyncomplete-lsp.vim** — LSP completion source
### Linting and Formatting
- **ALE** — async linting and format-on-save
### Markdown
- **vim-markdown** — folding, concealment, table alignment
- **previm** — live browser preview
### Language Syntax
- **vim-javascript** — enhanced JS syntax
- **yats.vim** — TypeScript syntax
- **vim-go** — Go syntax and tooling
### Editing
- **vim-surround** — change/delete/add surroundings
- **vim-commentary**`gc` to toggle comments
- **vim-repeat** — repeat plugin maps with `.`
- **vim-unimpaired** — bracket shortcut pairs
- **targets.vim** — additional text objects
- **auto-pairs** — auto-close brackets and quotes
- **vim-easymotion**`s` + 2 chars to jump anywhere
### UI
- **vim-colors-solarized** — color scheme
- **undotree** — visual undo branch history
- **vim-startify** — startup dashboard and session list
- **indentLine** — indent guides (non-TTY)
### Session and Navigation
- **vim-obsession** — session tracking
- **vim-tmux-navigator** — seamless Vim/tmux pane navigation
---
## Customization
### Per-project overrides
Create `.vimrc` in your project root:
```vim
" project/.vimrc
set shiftwidth=2
let g:ale_python_black_options = '--line-length=120'
```
### Modify keybindings
Edit `~/.vimrc` directly (`,ev` opens it from inside Vim). Reload with `,sv`.
---
## Troubleshooting ## Troubleshooting
**Plugins not loading** | 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 |
```vim More in the [wiki](https://github.com/m1ngsama/chopsticks/wiki).
:PlugInstall " install missing plugins
:PlugUpdate " update all plugins
```
**LSP server not starting** ## Contributing
```vim See [CONTRIBUTING.md](CONTRIBUTING.md). The two rules that matter: no Node.js dependencies, and don't regress startup time.
:LspInstallServer " install server for current filetype
:LspStatus " check server status
```
**Markdown preview not opening**
`previm` uses `open` (macOS) or `xdg-open` (Linux). Make sure a browser is
set as the default handler for HTML files.
**Colors look wrong**
```bash
export TERM=xterm-256color # add to ~/.bashrc or ~/.zshrc
export COLORTERM=truecolor # for true colour
```
**ALE linters not found**
```bash
which flake8 black prettier eslint # verify tools are on PATH
```
**`Ctrl+s` freezes the terminal**
Add `stty -ixon` to your `~/.bashrc`, `~/.zshrc`, or `~/.config/fish/config.fish`.
---
## License ## License
[MIT](LICENSE) © m1ng [MIT](LICENSE)

View file

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

View file

@ -380,18 +380,6 @@ else
fi fi
mkdir -p "$HOME/.vim" mkdir -p "$HOME/.vim"
COC_CFG="$HOME/.vim/coc-settings.json"
if [ -f "$COC_CFG" ] && [ ! -L "$COC_CFG" ]; then
TS=$(date +%Y%m%d_%H%M%S)
warn "Backing up existing coc-settings.json → $COC_CFG.backup.$TS"
mv "$COC_CFG" "$COC_CFG.backup.$TS"
fi
ln -sf "$SCRIPT_DIR/coc-settings.json" "$COC_CFG"
if [[ -L "$COC_CFG" ]]; then
ok "$HOME/.vim/coc-settings.json → $SCRIPT_DIR/coc-settings.json"
else
warn "coc-settings.json symlink failed (non-fatal)"
fi
# ============================================================================ # ============================================================================
# 3. vim-plug + Plugins # 3. vim-plug + Plugins
@ -441,7 +429,7 @@ fi
_vim_run +'PlugClean!' +qall || true # remove plugins no longer in vimrc; ignore exit code (none expected) _vim_run +'PlugClean!' +qall || true # remove plugins no longer in vimrc; ignore exit code (none expected)
_vim_run +'PlugInstall --sync' +qall || true # fzf post-install hook may exit non-zero; harmless _vim_run +'PlugInstall --sync' +qall || true # fzf post-install hook may exit non-zero; harmless
_plug_count=$(ls -1 "$HOME/.vim/plugged" 2>/dev/null | wc -l | tr -d ' ') _plug_count=$(find "$HOME/.vim/plugged" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')
if [[ $_plug_count -eq 0 ]]; then if [[ $_plug_count -eq 0 ]]; then
die "Plugin installation failed — ~/.vim/plugged is empty. Check network and retry." die "Plugin installation failed — ~/.vim/plugged is empty. Check network and retry."
fi fi

216
modules/core.vim Normal file
View file

@ -0,0 +1,216 @@
" 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 nowb
set noswapfile
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>tn :tabnew<cr>
nnoremap <leader>to :tabonly<cr>
nnoremap <leader>tc :tabclose<cr>
nnoremap <leader>tm :tabmove
nnoremap <leader>t<leader> :tabnext<cr>
let g:lasttab = 1
nnoremap <Leader>tl :exe "tabn ".g:lasttab<CR>
augroup ChopstickTabHistory
autocmd!
autocmd TabLeave * let g:lasttab = tabpagenr()
augroup END
nnoremap <leader>te :tabedit <C-r>=expand("%:p:h")<cr>/
nnoremap <leader>cd :lcd %:p:h<cr>:pwd<cr>
nnoremap 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 <leader>ss :setlocal spell!<cr>
nnoremap <leader>sn ]s
nnoremap <leader>sp [s
nnoremap <leader>sa zg
nnoremap <leader>s? z=
set pastetoggle=<F2>
nnoremap <F3> :set invnumber<CR>
nnoremap <F4> :set invrelativenumber<CR>
nnoremap <F6> :set list!<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
nnoremap <C-u> <C-u>zz
if has('clipboard')
nnoremap <leader>y "+y
vnoremap <leader>y "+y
nnoremap <leader>Y "+Y
nnoremap <leader>p "+p
nnoremap <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 ttyfast
set lazyredraw
set complete-=i
set updatetime=300
set shortmess+=cI
if g:is_tty
set signcolumn=auto
set synmaxcol=120
else
if has("patch-8.1.1564")
set signcolumn=number
else
set signcolumn=yes
endif
endif
" ── Project-Local Config ────────────────────────────────────────────────────
set exrc
set secure
set sessionoptions=blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal
" ── 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

46
modules/editing.vim Normal file
View file

@ -0,0 +1,46 @@
" 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 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, {-> 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

19
modules/env.vim Normal file
View file

@ -0,0 +1,19 @@
" 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')
" 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

26
modules/git.vim Normal file
View file

@ -0,0 +1,26 @@
" 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>
endif
" ── Conflict Navigation ────────────────────────────────────────────────────
nnoremap <silent> ]x /^\(<<<<<<<\\|=======\\|>>>>>>>\)<CR>
nnoremap <silent> [x ?^\(<<<<<<<\\|=======\\|>>>>>>>\)<CR>

46
modules/languages.vim Normal file
View file

@ -0,0 +1,46 @@
" languages.vim — vim-go config, per-filetype autocmds
" ── 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 ──────────────────────────────────────────────────────
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 colorcolumn=+1
autocmd FileType javascript,typescript
\ setlocal expandtab shiftwidth=2 tabstop=2 textwidth=100 colorcolumn=+1
autocmd FileType go
\ setlocal noexpandtab shiftwidth=4 tabstop=4 textwidth=120 colorcolumn=+1
autocmd FileType rust
\ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=100 colorcolumn=+1
autocmd FileType c,cpp
\ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=80 colorcolumn=+1
autocmd FileType html,css
\ setlocal expandtab shiftwidth=2 tabstop=2
autocmd FileType yaml
\ setlocal expandtab shiftwidth=2 tabstop=2
autocmd FileType markdown
\ setlocal wrap linebreak spell textwidth=0 colorcolumn=0 conceallevel=2
autocmd FileType sh
\ setlocal expandtab shiftwidth=2 tabstop=2 textwidth=80 colorcolumn=+1
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

53
modules/lint.vim Normal file
View file

@ -0,0 +1,53 @@
" lint.vim — ALE async linting and format-on-save
let g:ale_disable_lsp = 1
let g: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'],
\ 'markdown': ['markdownlint'],
\ 'sql': ['sqlfluff'],
\}
let g: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'],
\ 'markdown': ['prettier'],
\ 'sql': ['sqlfluff'],
\}
let 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
if exists('g:plugs["ale"]')
nnoremap <silent> [e :ALEPrevious<cr>
nnoremap <silent> ]e :ALENext<cr>
nnoremap <silent> <leader>aD :ALEDetail<cr>
endif

79
modules/lsp.vim Normal file
View file

@ -0,0 +1,79 @@
" lsp.vim — vim-lsp settings, asyncomplete, LSP buffer keymaps
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_markdown = ['marksman']
let g:lsp_settings_filetype_sql = ['sqls']
let g:lsp_diagnostics_virtual_text_enabled = !g:is_tty
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
setlocal signcolumn=yes
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

98
modules/navigation.vim Normal file
View file

@ -0,0 +1,98 @@
" 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 = 0
let g:netrw_winsize = 25
let g:netrw_list_hide = '\(^\|\s\s\)\zs\.\S\+'
let g:netrw_list_hide .= ',\.pyc$,node_modules,\.git,__pycache__,\.DS_Store'
nnoremap <leader>e :Explore<CR>
nnoremap <leader>E :Vexplore<CR>
" ── 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>
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
if g:is_tty
command! -bang -nargs=* Rg
\ call fzf#vim#grep(
\ 'rg --column --line-number --no-heading --color=always --smart-case -- '
\ .shellescape(<q-args>), 1, <bang>0)
command! -bang GFiles call fzf#vim#gitfiles('', <bang>0)
else
command! -bang -nargs=* Rg
\ call fzf#vim#grep(
\ 'rg --column --line-number --no-heading --color=always --smart-case -- '
\ .shellescape(<q-args>), 1, fzf#vim#with_preview(), <bang>0)
command! -bang GFiles call fzf#vim#gitfiles('', fzf#vim#with_preview(), <bang>0)
endif
if g:is_tty
command! -bang -nargs=* RgWord
\ call fzf#vim#grep(
\ 'rg --column --line-number --no-heading --color=always --smart-case -F -- '
\ .shellescape(expand('<cword>')), 1, <bang>0)
else
command! -bang -nargs=* RgWord
\ call fzf#vim#grep(
\ 'rg --column --line-number --no-heading --color=always --smart-case -F -- '
\ .shellescape(expand('<cword>')), 1, fzf#vim#with_preview(), <bang>0)
endif
" ── Window Maximize Toggle ──────────────────────────────────────────────────
function! s:ToggleMaximize() abort
if exists('t:maximize_session')
execute t:maximize_session
unlet t:maximize_session
else
let t:maximize_session = winrestcmd()
resize | vertical resize
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

68
modules/plugins.vim Normal file
View file

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

260
modules/tools.vim Normal file
View file

@ -0,0 +1,260 @@
" tools.vim — cheat sheet, run file, sudo save, quickfix, helpers
" ── Helper Functions ────────────────────────────────────────────────────────
function! HasPaste()
if &paste | return 'PASTE MODE ' | endif
return ''
endfunction
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
fun! CleanExtraSpaces()
let save_cursor = getpos(".")
let old_query = getreg('/')
silent! %s/\s\+$//e
call setpos('.', save_cursor)
call setreg('/', old_query)
endfun
function! ToggleNumber()
if(&relativenumber == 1)
set norelativenumber
set number
else
set relativenumber
endif
endfunc
" ── Additional Utilities ────────────────────────────────────────────────────
nnoremap <leader>F gg=G``
nnoremap <leader>wa :wa<CR>
nnoremap <silent> <Leader>= :exe "resize " . (winheight(0) * 3/2)<CR>
nnoremap <silent> <Leader>- :exe "resize " . (winheight(0) * 2/3)<CR>
nnoremap <silent> <Leader>+ :exe "vertical resize " . (winwidth(0) * 3/2)<CR>
nnoremap <silent> <Leader>_ :exe "vertical resize " . (winwidth(0) * 2/3)<CR>
nnoremap <leader><leader> <c-^>
nnoremap <leader>W :%s/\s\+$//<CR>:let @/=''<CR>
nnoremap <leader>so :if &filetype ==# 'vim' <Bar> source % <Bar> echo "Sourced " . expand('%') <Bar> else <Bar> echo "Not a vim file" <Bar> endif<CR>
nnoremap <leader>ev :edit $MYVIMRC<CR>
nnoremap <leader>sv :source $MYVIMRC<CR>:echo "vimrc reloaded"<CR>
nnoremap <leader>* :%s/\<<C-r><C-w>\>//g<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
nnoremap <leader>ms :e ~/buffer.md<cr>
" ── 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 = 1024 * 1024 * 10
let s:tty_large = g:is_tty ? 512000 : g:LargeFile
augroup ChopstickLargeFile
autocmd!
autocmd BufReadPre *
\ if !empty(expand('<afile>')) |
\ let s:fsize = getfsize(expand('<afile>')) |
\ if s:fsize > g:LargeFile || s:fsize == -2 |
\ setlocal bufhidden=unload undolevels=-1 noswapfile syntax= |
\ let b:ale_enabled = 0 |
\ elseif g:is_tty && s:fsize > s:tty_large |
\ setlocal syntax= |
\ endif |
\ endif
augroup END
if g:is_tty && !exists("g:tty_message_shown")
augroup TTYMessage
autocmd!
autocmd VimEnter * echom "TTY mode — visual features disabled"
augroup END
let g:tty_message_shown = 1
endif
" ── Run Current File (,cr) ──────────────────────────────────────────────────
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 Improvements ───────────────────────────────────────────────────
augroup ChopstickQF
autocmd!
autocmd QuickFixCmdPost [^l]* cwindow
autocmd QuickFixCmdPost l* lwindow
augroup END
nnoremap <silent> ]q :cnext<CR>
nnoremap <silent> [q :cprev<CR>
" ── Debug Helpers ───────────────────────────────────────────────────────────
nnoremap <leader>sh :call <SID>SynStack()<CR>
function! <SID>SynStack()
if !exists("*synstack") | return | endif
echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")')
endfunc
" ── Cheat Sheet (,?) ────────────────────────────────────────────────────────
function! s:CheatSheet() abort
let l:name = '__ChopsticksCheatSheet__'
if bufwinnr(l:name) > 0
execute bufwinnr(l:name) . 'wincmd w'
return
endif
execute 'botright new ' . l:name
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile
call setline(1, [
\ '=== chopsticks — Quick Reference ===',
\ '',
\ 'SURVIVAL',
\ ' Esc / jk Exit insert or visual mode',
\ ' :q! + Enter Quit without saving',
\ ' ,x Save+quit ,w Save Ctrl+s Save (any mode)',
\ ' :w!! Sudo save (when you forgot to open as root)',
\ '',
\ 'FILES & SEARCH',
\ ' Ctrl+p Fuzzy find file (git-aware)',
\ ' ,e / ,E File browser / vertical split',
\ ' ,b Search open buffers',
\ ' ,rg Search project contents (ripgrep)',
\ ' ,rG Ripgrep word under cursor',
\ ' ,fh Recent files history',
\ ' ,fl / ,fL Search lines in buffer / all buffers',
\ ' ,fc Commands | ,fm Marks',
\ ' ,f/ / ,f: Search / command history',
\ ' ,, Switch to last file (Ctrl+^)',
\ '',
\ 'CODE INTELLIGENCE (vim-lsp)',
\ ' gd Definition gy Type def gi Impl gr Refs',
\ ' K Hover documentation',
\ ' [g / ]g Prev / next LSP diagnostic',
\ ' [e / ]e Prev / next ALE error',
\ ' ,ca Code action ,rn Rename ,f Format',
\ ' ,o File outline ,ws Workspace symbols',
\ ' ,cr Run current file',
\ '',
\ 'MARKDOWN & WRITING',
\ ' ,mp Live browser preview (previm)',
\ ' ,mt Table of contents',
\ ' ,zen Zen mode (Goyo + Limelight)',
\ ' zr / zm Unfold / fold all headings',
\ '',
\ 'EDITING',
\ ' gc Toggle comment (visual mode too)',
\ ' s + 2 chars EasyMotion jump anywhere',
\ ' ,u / F5 Undo tree',
\ ' ,y / ,Y Yank to system clipboard',
\ ' Alt+j / Alt+k Move line down / up',
\ ' ,F Re-indent file ,W Strip trailing whitespace',
\ ' ,* Search and replace word under cursor',
\ '',
\ 'GIT',
\ ' ,gs Status ,gd Diff ,gb Blame',
\ ' ,gc Commit ,gp Push ,gl Pull',
\ ' [x / ]x Navigate git conflict markers',
\ '',
\ 'WINDOWS & PANES',
\ ' Ctrl+h/j/k/l Navigate splits and tmux panes',
\ ' ,h / ,l Prev / next buffer ,bd Close buffer',
\ ' ,z Maximize / restore current window',
\ ' ,tv / ,th Terminal (vertical / horizontal)',
\ ' Esc Esc Exit terminal mode',
\ ' ,= / ,- Resize height ,+ / ,_ Resize width',
\ '',
\ 'QUICKFIX',
\ ' ,qo / ,qc Open / close quickfix',
\ ' ]q / [q Next / prev quickfix entry',
\ '',
\ 'UTILITIES',
\ ' ,ev / ,sv Edit / reload ~/.vimrc',
\ ' ,cp / ,cf Copy file path / filename to clipboard',
\ ' ,ms Scratch buffer ,cd CD to file dir',
\ ' ,ss Toggle spell ,so Source current vim file',
\ ' F2 Paste F3 Line# F4 Relative# F6 Invisible',
\ '',
\ '(press q to close)',
\ ])
setlocal nomodifiable readonly
nnoremap <buffer> <silent> q :bd<CR>
endfunction
nnoremap <silent> <leader>? :call <SID>CheatSheet()<CR>
" ── Interactive Tutorial ────────────────────────────────────────────────────
function! s:ChopsticksLearn() abort
let l:tutor = g:chopsticks_dir . '/tutor/chopsticks.tutor'
if !filereadable(l:tutor)
echo "Tutorial not found: " . l:tutor
return
endif
execute 'edit ' . fnameescape(l:tutor)
setlocal nomodifiable readonly
setlocal buftype=nofile bufhidden=wipe
setlocal filetype=text
setlocal wrap linebreak
endfunction
command! ChopsticksLearn call s:ChopsticksLearn()

164
modules/ui.vim Normal file
View file

@ -0,0 +1,164 @@
" ui.vim — colorscheme, statusline, startify, indentline
" ── 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
if !g:is_tty
try
colorscheme solarized8
catch
colorscheme default
endtry
else
colorscheme default
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
" ── IndentLine (non-TTY only) ───────────────────────────────────────────────
if !g:is_tty && exists('g:plugs["indentLine"]')
let g:indentLine_char = '|'
let g:indentLine_first_char = '|'
let g:indentLine_showFirstIndentLevel = 1
let g:indentLine_fileTypeExclude = ['text', 'help', 'startify', 'markdown']
let g:indentLine_bufTypeExclude = ['help', 'terminal', 'nofile']
let g:indentLine_setConceal = 2
let g:indentLine_concealcursor = ''
endif
" ── Startify ────────────────────────────────────────────────────────────────
if exists('g:plugs["vim-startify"]')
let g:startify_custom_header = [
\ ' ██████╗██╗ ██╗ ██████╗ ██████╗ ███████╗████████╗██╗ ██████╗██╗ ██╗███████╗',
\ ' ██╔════╝██║ ██║██╔═══██╗██╔══██╗██╔════╝╚══██╔══╝██║██╔════╝██║ ██╔╝██╔════╝',
\ ' ██║ ███████║██║ ██║██████╔╝███████╗ ██║ ██║██║ █████╔╝ ███████╗',
\ ' ██║ ██╔══██║██║ ██║██╔═══╝ ╚════██║ ██║ ██║██║ ██╔═██╗ ╚════██║',
\ ' ╚██████╗██║ ██║╚██████╔╝██║ ███████║ ██║ ██║╚██████╗██║ ██╗███████║',
\ ' ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝',
\ '',
\ ]
let g:startify_lists = [
\ { 'type': 'sessions', 'header': [' Sessions'] },
\ { 'type': 'files', 'header': [' Recent Files'] },
\ { 'type': 'dir', 'header': [' Current Dir'] },
\ { 'type': 'bookmarks', 'header': [' Bookmarks'] },
\ ]
let g:startify_bookmarks = [{'v': '~/.vimrc'}]
if filereadable(expand('~/.zshrc'))
call add(g:startify_bookmarks, {'z': '~/.zshrc'})
endif
if filereadable(expand('~/.bashrc'))
call add(g:startify_bookmarks, {'b': '~/.bashrc'})
endif
if filereadable(expand('~/.config/fish/config.fish'))
call add(g:startify_bookmarks, {'f': '~/.config/fish/config.fish'})
endif
let g:startify_session_persistence = 1
let g:startify_session_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
if !g:is_tty
augroup ChopstickStartup
autocmd!
autocmd StdinReadPre * let s:std_in = 1
autocmd VimEnter *
\ if argc() == 1 && isdirectory(argv()[0]) && !exists('s:std_in') |
\ exe 'cd ' . fnameescape(argv()[0]) |
\ if exists(':Startify') == 2 | Startify | else | enew | endif |
\ endif
augroup END
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! 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 .= '%#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

56
modules/writing.vim Normal file
View file

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

269
tutor/chopsticks.tutor Normal file
View file

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