mirror of
https://oauth2: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@github.com/m1ngsama/chopsticks.git
synced 2026-06-26 05:54:37 +08:00
Compare commits
12 commits
e5412e464e
...
3507945b78
| Author | SHA1 | Date | |
|---|---|---|---|
| 3507945b78 | |||
| 16c1974e1a | |||
| d3a137a36e | |||
| a6a101a286 | |||
| fd29dd160e | |||
| 0b1c8d94d7 | |||
| a4ea56525f | |||
| 87719ebdd3 | |||
| c5f84a700d | |||
| 3f967af8e4 | |||
| f38bdf6e57 | |||
| d56ca80da7 |
41 changed files with 2742 additions and 1021 deletions
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -10,4 +10,4 @@
|
|||
|
||||
- [ ] `vim --startuptime` shows no regression
|
||||
- [ ] Tested on macOS / Linux
|
||||
- [ ] `,?` cheat sheet still accurate
|
||||
- [ ] `SPC ?` cheat sheet still accurate
|
||||
|
|
|
|||
14
.github/demo-project/middleware.py
vendored
Normal file
14
.github/demo-project/middleware.py
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"""Small helpers for the README demo server."""
|
||||
|
||||
|
||||
def apply_cors(headers):
|
||||
"""Return response headers with permissive demo CORS."""
|
||||
next_headers = dict(headers)
|
||||
next_headers["Access-Control-Allow-Origin"] = "*"
|
||||
next_headers["Access-Control-Allow-Headers"] = "Content-Type"
|
||||
return next_headers
|
||||
|
||||
|
||||
def log_request(path):
|
||||
"""Format a deterministic request log line."""
|
||||
return f"GET {path}"
|
||||
20
.github/demo-project/report.py
vendored
Normal file
20
.github/demo-project/report.py
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
"""Small runnable report for the README demo."""
|
||||
|
||||
import json
|
||||
|
||||
from routes import get_users, health_check
|
||||
|
||||
|
||||
def build_summary():
|
||||
"""Collect a deterministic status payload."""
|
||||
users = get_users()
|
||||
return {
|
||||
"service": "demo-api",
|
||||
"status": health_check()["status"],
|
||||
"active_users": users["total"],
|
||||
"roles": sorted({user["role"] for user in users["users"]}),
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(json.dumps(build_summary(), indent=2))
|
||||
31
.github/demo-project/routes.py
vendored
Normal file
31
.github/demo-project/routes.py
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
"""URL route definitions and handler functions."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def health_check():
|
||||
"""Return server health status."""
|
||||
return {
|
||||
"status": "ok",
|
||||
"uptime": "3d 14h 22m",
|
||||
"version": "1.2.0",
|
||||
}
|
||||
|
||||
|
||||
def get_users():
|
||||
"""Return list of active users."""
|
||||
return {
|
||||
"users": [
|
||||
{"id": 1, "name": "alice", "role": "admin"},
|
||||
{"id": 2, "name": "bob", "role": "engineer"},
|
||||
{"id": 3, "name": "carol", "role": "engineer"},
|
||||
],
|
||||
"total": 3,
|
||||
"generated_at": datetime(2026, 5, 21, 12, 0, 0).isoformat(),
|
||||
}
|
||||
|
||||
|
||||
ROUTES = {
|
||||
"/health": health_check,
|
||||
"/users": get_users,
|
||||
}
|
||||
58
.github/demo-project/server.py
vendored
Normal file
58
.github/demo-project/server.py
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
"""Lightweight HTTP server with routing and middleware."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
from middleware import apply_cors, log_request
|
||||
from routes import ROUTES
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def dispatch(path):
|
||||
"""Route request to the matching handler."""
|
||||
logger.info(log_request(path))
|
||||
handler = ROUTES.get(path)
|
||||
if handler:
|
||||
data = handler()
|
||||
return 200, json.dumps(data)
|
||||
return 404, json.dumps({"error": "not found"})
|
||||
|
||||
|
||||
def respond(status, body):
|
||||
"""Format an HTTP response with CORS headers."""
|
||||
headers = apply_cors({})
|
||||
headers["Content-Type"] = "application/json"
|
||||
logger.info("%d (%d bytes)", status, len(body))
|
||||
return {"status": status, "headers": headers, "body": body}
|
||||
|
||||
|
||||
class DemoHandler(BaseHTTPRequestHandler):
|
||||
"""Tiny request handler for the README GIF."""
|
||||
|
||||
def do_GET(self):
|
||||
status, body = dispatch(self.path)
|
||||
response = respond(status, body)
|
||||
self.send_response(response["status"])
|
||||
for name, value in response["headers"].items():
|
||||
self.send_header(name, value)
|
||||
self.end_headers()
|
||||
self.wfile.write(response["body"].encode("utf-8"))
|
||||
|
||||
def log_message(self, *_args):
|
||||
return
|
||||
|
||||
|
||||
def main():
|
||||
"""Start the server."""
|
||||
port = int(os.environ.get("CHOPSTICKS_DEMO_PORT", "8080"))
|
||||
print(f"[{datetime.now():%H:%M:%S}] server running on :{port}")
|
||||
print(f"routes: {', '.join(ROUTES.keys())}")
|
||||
HTTPServer(("127.0.0.1", port), DemoHandler).serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
.github/demo.gif
vendored
BIN
.github/demo.gif
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 990 KiB After Width: | Height: | Size: 1.1 MiB |
98
.github/demo.tape
vendored
98
.github/demo.tape
vendored
|
|
@ -1,8 +1,14 @@
|
|||
# chopsticks demo — "what can I do with this?"
|
||||
# chopsticks demo — for people who already know Vim:
|
||||
# show the project workflow layer Vim does not assemble by default.
|
||||
# project loop: visible jump → files → run → grep → git → active key help.
|
||||
# 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
|
||||
Require bash
|
||||
Require vim
|
||||
Require python3
|
||||
Require git
|
||||
Set Shell bash
|
||||
Set FontSize 14
|
||||
Set Width 1080
|
||||
|
|
@ -11,53 +17,83 @@ Set Theme "Builtin Solarized Dark"
|
|||
Set TypingSpeed 50ms
|
||||
Set Padding 10
|
||||
|
||||
# ── 0. Start API server in background ──────────────────────────────────────
|
||||
Type "cd /tmp/demo-project"
|
||||
# ── 0. Prepare deterministic fixture and local config ─────────────────────
|
||||
Hide
|
||||
Type "export CHOPSTICKS_ROOT=$(git rev-parse --show-toplevel)"
|
||||
Enter
|
||||
Type "export XDG_CONFIG_HOME=$(mktemp -d /tmp/chopsticks-demo-xdg-XXXXXX)"
|
||||
Enter
|
||||
Type "export CHOPSTICKS_DEMO_PROJECT=$(mktemp -d /tmp/chopsticks-demo-project-XXXXXX)"
|
||||
Enter
|
||||
Type `cp "$CHOPSTICKS_ROOT/.github/demo-project/"*.py "$CHOPSTICKS_DEMO_PROJECT/"`
|
||||
Enter
|
||||
Type "cd $CHOPSTICKS_DEMO_PROJECT"
|
||||
Enter
|
||||
Type "git init -q && git symbolic-ref HEAD refs/heads/main && git add ."
|
||||
Enter
|
||||
Type `clear && printf "%s\n" "chopsticks: a long-term Vim efficiency kit" "" "For experienced Vim users who already know how to edit." "Keep Vim's language. Standardize the project loop:" "" " s + 2 chars visible jump on the current screen" " SPC SPC project-aware file switcher" " SPC rr run the current file" " SPC / ripgrep the project" " SPC gs git status, with no push/pull hotkeys" " SPC ? active keys explained inside Vim"`
|
||||
Enter
|
||||
Show
|
||||
Sleep 6s
|
||||
Type "clear"
|
||||
Enter
|
||||
Type `vim -Nu "$CHOPSTICKS_ROOT/.vimrc" server.py`
|
||||
Enter
|
||||
Wait+Screen /server.py/
|
||||
Sleep 1s
|
||||
|
||||
# ── 1. Screen-local motion: stop counting hjkl across dense code ───────────
|
||||
Type "s"
|
||||
Sleep 0.5s
|
||||
Type "python3 serve.py > /dev/null 2>&1 &"
|
||||
Type "di"
|
||||
Sleep 2s
|
||||
Ctrl+C
|
||||
Sleep 1s
|
||||
|
||||
# ── 2. Project files: one habit for git-aware file switching ───────────────
|
||||
Space 2
|
||||
Sleep 1.5s
|
||||
Type "report"
|
||||
Sleep 2s
|
||||
Enter
|
||||
Sleep 3s
|
||||
|
||||
# ── 3. Edit-run loop: run the current file without leaving Vim ─────────────
|
||||
Space
|
||||
Type "rr"
|
||||
Sleep 4s
|
||||
Enter
|
||||
Sleep 1s
|
||||
|
||||
# ── 1. Open file — syntax highlighting + statusline ────────────────────────
|
||||
Type "vim server.py"
|
||||
Enter
|
||||
Sleep 3.5s
|
||||
|
||||
# ── 2. Fuzzy find files (,ff → type → select) ────────────────────────────
|
||||
Type ",ff"
|
||||
Sleep 1.5s
|
||||
Type "route"
|
||||
# ── 4. Project grep: ripgrep as part of the same muscle-memory layer ───────
|
||||
Space
|
||||
Type "/"
|
||||
Sleep 1s
|
||||
Type "def get_users"
|
||||
Sleep 2.5s
|
||||
Enter
|
||||
Sleep 3s
|
||||
|
||||
# ── 3. Ripgrep project search (,rg → query → select) ──────────────────────
|
||||
Type ",rg"
|
||||
Sleep 1.5s
|
||||
Type "def "
|
||||
# ── 5. Git boundary: status is fast; push/pull stay explicit commands ──────
|
||||
Space
|
||||
Type "gs"
|
||||
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"
|
||||
Type ":bd!"
|
||||
Enter
|
||||
Sleep 1s
|
||||
Type ",?"
|
||||
|
||||
# ── 6. Self-documenting config: no private wiki required ───────────────────
|
||||
Space
|
||||
Type "?"
|
||||
Sleep 5.5s
|
||||
Type "q"
|
||||
Sleep 0.5s
|
||||
|
||||
# ── done ───────────────────────────────────────────────────────────────────
|
||||
Hide
|
||||
Type ":qa!"
|
||||
Enter
|
||||
Sleep 0.5s
|
||||
Type `test -n "$CHOPSTICKS_DEMO_PROJECT" && rm -rf "$CHOPSTICKS_DEMO_PROJECT"`
|
||||
Enter
|
||||
Sleep 0.5s
|
||||
|
|
|
|||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install Vim
|
||||
timeout-minutes: 5
|
||||
|
|
@ -61,7 +61,7 @@ jobs:
|
|||
shellcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Check test runner CLI
|
||||
run: |
|
||||
scripts/test.sh --help
|
||||
|
|
@ -72,7 +72,7 @@ jobs:
|
|||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install markdownlint
|
||||
run: npm install -g markdownlint-cli
|
||||
- name: Lint Markdown
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,8 +1,10 @@
|
|||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
__pycache__/
|
||||
Session.vim
|
||||
autoload/
|
||||
plugged/
|
||||
doc/tags
|
||||
.swap/
|
||||
.undo/
|
||||
|
|
|
|||
16
.vimrc
16
.vimrc
|
|
@ -1,9 +1,15 @@
|
|||
set nocompatible
|
||||
|
||||
let g:chopsticks_dir = fnamemodify(resolve(expand('<sfile>')), ':h')
|
||||
if index(split(&runtimepath, ','), g:chopsticks_dir) < 0
|
||||
let &runtimepath = g:chopsticks_dir . ',' . &runtimepath
|
||||
endif
|
||||
let s:xdg_config_home = !empty($XDG_CONFIG_HOME) && $XDG_CONFIG_HOME =~# '^/'
|
||||
\ ? $XDG_CONFIG_HOME
|
||||
\ : '~/.config'
|
||||
let s:local_config = expand(get(g:, 'chopsticks_local_config',
|
||||
\ s:xdg_config_home . '/chopsticks.vim'))
|
||||
let g:chopsticks_resolved_local_config = s:local_config
|
||||
if filereadable(s:local_config)
|
||||
execute 'source ' . fnameescape(s:local_config)
|
||||
endif
|
||||
|
|
@ -27,4 +33,14 @@ call s:load('lsp')
|
|||
call s:load('lint')
|
||||
call s:load('git')
|
||||
call s:load('languages')
|
||||
call s:load('buffers')
|
||||
call s:load('utilities')
|
||||
call s:load('files')
|
||||
call s:load('runner')
|
||||
call s:load('quickfix')
|
||||
call s:load('status')
|
||||
call s:load('cheatsheet')
|
||||
call s:load('tutor')
|
||||
call s:load('beta')
|
||||
call s:load('help')
|
||||
call s:load('tools')
|
||||
|
|
|
|||
104
BETA.md
Normal file
104
BETA.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# 2.3.0 Release Candidate Testing
|
||||
|
||||
This branch is the 2.3.0 release candidate. The goal is to prove that the Space
|
||||
layout can serve as the project loop for experienced Vim users, not just that
|
||||
the mappings work in isolation. Do not tag or publish it as `2.3.0` until the
|
||||
checklist below is closed.
|
||||
|
||||
Inside Vim, run `:ChopsticksBeta` for the compact checklist,
|
||||
`:ChopsticksBetaLog` for editable local notes, and `:ChopsticksBetaSession`
|
||||
to append a new session block. Run `:ChopsticksHelp` or `:help chopsticks`
|
||||
for the native Vim reference.
|
||||
|
||||
## Install the release candidate
|
||||
|
||||
Existing checkout:
|
||||
|
||||
```bash
|
||||
cd ~/.vim
|
||||
git fetch origin
|
||||
git checkout release/2.3.0
|
||||
git pull --ff-only
|
||||
vim -Nu ~/.vimrc -n -es +'PlugInstall --sync' +'qa!'
|
||||
```
|
||||
|
||||
Fresh checkout:
|
||||
|
||||
```bash
|
||||
git clone --branch release/2.3.0 https://github.com/m1ngsama/chopsticks.git ~/.vim
|
||||
ln -sf ~/.vim/.vimrc ~/.vimrc
|
||||
vim -Nu ~/.vimrc -n -es +'PlugInstall --sync' +'qa!'
|
||||
```
|
||||
|
||||
Keep local choices in `${XDG_CONFIG_HOME:-~/.config}/chopsticks.vim`:
|
||||
|
||||
```vim
|
||||
let g:chopsticks_profile = 'engineer'
|
||||
let g:chopsticks_keymap_style = 'space'
|
||||
```
|
||||
|
||||
Inside Vim, `:ChopsticksConfig` opens that file and `:ChopsticksReload`
|
||||
reloads chopsticks after saving it.
|
||||
|
||||
## Daily test loop
|
||||
|
||||
Use the release candidate for real editing, not only demos. A session should
|
||||
exercise the trained loop until it either feels automatic or exposes friction.
|
||||
For each session, record:
|
||||
|
||||
- The task: project navigation, code edit, grep, git, LSP, Markdown, SSH.
|
||||
- The first key you tried when you got stuck.
|
||||
- Whether `SPC ?`, `:ChopsticksTutor`, or `:ChopsticksStatus` answered it.
|
||||
- Any mapping that felt slow, awkward, surprising, or too easy to mistype.
|
||||
- Any documentation line that was wrong, missing, or redundant.
|
||||
|
||||
`:ChopsticksBetaLog` opens `${XDG_CONFIG_HOME:-~/.config}/chopsticks-2.3.0.md`
|
||||
by default. Set `g:chopsticks_beta_log` before loading chopsticks to use a
|
||||
different path. Use `:ChopsticksBetaSession` at the start of each real editing
|
||||
session so every test has a timestamped block.
|
||||
|
||||
## Workflows to exercise
|
||||
|
||||
```text
|
||||
SPC SPC find file SPC / grep project
|
||||
s + 2ch jump on screen gd / gr definition / references
|
||||
SPC rr run current file SPC gs git status
|
||||
SPC cf format SPC ca code action
|
||||
SPC fc local config SPC ? active cheat sheet
|
||||
Ctrl-hjkl windows SPC e sidebar
|
||||
:ChopsticksStatus health :ChopsticksConfig preferences
|
||||
```
|
||||
|
||||
Also test the boring path: save, quit, reopen Vim, edit over SSH, open a large
|
||||
file, edit Markdown, and use a machine with missing optional tools.
|
||||
|
||||
## Exit criteria
|
||||
|
||||
- `s` as the default visible jump still feels worth the native override after
|
||||
real editing.
|
||||
- No high-frequency action requires remembering an undocumented key.
|
||||
- Window/sidebar navigation feels faster than native `<C-w>` only.
|
||||
- README, QUICKSTART, `:help chopsticks`, `SPC ?`, and `:ChopsticksTutor`
|
||||
teach the same layout.
|
||||
- No private wiki or external note is needed to remember the daily loop.
|
||||
- `scripts/test.sh quick` and `scripts/test.sh vim` pass locally.
|
||||
- The README GIF has been regenerated from `.github/demo.tape` after any public
|
||||
key change.
|
||||
- The release candidate has been tested on macOS and over SSH on Linux.
|
||||
|
||||
## Roll back
|
||||
|
||||
Return to the latest stable release:
|
||||
|
||||
```bash
|
||||
cd ~/.vim
|
||||
git fetch origin --tags
|
||||
git checkout v2.2.0
|
||||
vim -Nu ~/.vimrc -n -es +'PlugInstall --sync' +'qa!'
|
||||
```
|
||||
|
||||
Or keep the code but switch back to the legacy layout:
|
||||
|
||||
```vim
|
||||
let g:chopsticks_keymap_style = 'classic'
|
||||
```
|
||||
59
CHANGELOG.md
59
CHANGELOG.md
|
|
@ -1,6 +1,63 @@
|
|||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
## 2.3.0 — 2026-05-24
|
||||
|
||||
### Breaking
|
||||
|
||||
- Default keymap style is now `space`: `SPC` is the command leader and `,`
|
||||
is reserved for filetype-local actions. Set
|
||||
`let g:chopsticks_keymap_style = 'classic'` in
|
||||
`${XDG_CONFIG_HOME:-~/.config}/chopsticks.vim` to keep the old comma layout
|
||||
- In the default Space layout, Normal-mode `s` is now the fastest
|
||||
EasyMotion over-window two-character jump. Use `cl` for native `s`
|
||||
substitute and `cc` for native `S` substitute
|
||||
- Git push and pull hotkeys are removed from both Space and classic layouts;
|
||||
use `:Git push` / `:Git pull` explicitly for irreversible remote operations
|
||||
|
||||
### Added
|
||||
|
||||
- Canonical QWERTY/CapsLock-friendly Space leader layout:
|
||||
`SPC SPC`, `SPC /`, `SPC ,`, `SPC w`, `SPC q`, `SPC rr`,
|
||||
`SPC gs`, `SPC gl`, `SPC ca`, `SPC cr`, `SPC cf`, and `SPC ?`
|
||||
- Native LSP motions in the default layout: `gd`, `gr`, `gI`, `gy`, `K`,
|
||||
`[d`, and `]d`
|
||||
- `:ChopsticksTutor` guided practice buffer for learning the final keymap
|
||||
- `:ChopsticksCheatSheet` command, with `SPC ?` as the discoverable default
|
||||
- `:ChopsticksHelp` and native `:help chopsticks` documentation for in-editor
|
||||
support without a separate wiki
|
||||
- `:ChopsticksConfig` and `:ChopsticksReload` for editing local preferences
|
||||
without touching the managed `.vimrc`
|
||||
- `:ChopsticksBeta`, `:ChopsticksBetaLog`, and `:ChopsticksBetaSession` for
|
||||
guided release-candidate testing and local session notes
|
||||
- Dedicated modules for buffers, utilities, files, runner, quickfix, status,
|
||||
cheat sheet, tutor, release-candidate testing, and help
|
||||
- Split test runner: `scripts/test.sh` now dispatches to
|
||||
`scripts/test-quick.sh` and `scripts/test-vim.sh`
|
||||
|
||||
### Changed
|
||||
|
||||
- README, QUICKSTART, installer onboarding text, PR template, and demo tape now
|
||||
teach the Space layout first while keeping the legacy classic layout documented
|
||||
- `SPC ?` / `,?` cheat sheet output is generated from the active keymap style
|
||||
and profile, so minimal installs no longer display disabled features
|
||||
- Markdown actions now use localleader maps in the Space layout:
|
||||
`,mp` preview and `,mt` table of contents
|
||||
- `SPC U` opens UndoTree, `SPC z` toggles maximize, `SPC bp` / `SPC bn`
|
||||
move between buffers, and quickfix/location-list actions live under `SPC x`
|
||||
- `tools.vim` is now a compatibility placeholder; runtime behavior lives in
|
||||
smaller focused modules
|
||||
- CI/local smoke coverage now asserts Space defaults, classic compatibility,
|
||||
missing push/pull hotkeys, tutor and cheat-sheet content, runner behavior,
|
||||
large-file protection, and startup budget
|
||||
|
||||
### Fixed
|
||||
|
||||
- GitHub Actions now uses `actions/checkout@v5`, avoiding the Node.js 20
|
||||
deprecation warning on PR checks
|
||||
- `diffopt` enhancements now degrade safely on macOS system Vim builds that
|
||||
report the required patch level but reject individual diff options
|
||||
- Installer system-tool reporting now still detects already-installed tools on
|
||||
Linux hosts where sudo is unavailable in non-interactive mode
|
||||
|
||||
## 2.2.0 — 2026-05-17
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
1. **No Node.js in the Vim runtime.** Plugins must work with pure VimScript — no coc.nvim or other Node-backed completion engines. External CLIs (prettier, eslint, markdownlint, stylelint, tsc) installed via npm are fine; ALE shells out to them as optional system tools, not as part of the Vim runtime.
|
||||
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. **Native-first keymaps.** Enhance Vim's native behavior instead of replacing it. Do not override built-in motions, operators, text objects, or help-oriented keys for discoverability alone; prefer leader-prefixed or otherwise non-conflicting ergonomic mappings.
|
||||
4. **Native-first keymaps.** Enhance Vim's native behavior instead of replacing it. Do not override built-in motions, operators, text objects, or help-oriented keys for discoverability alone. Rare exceptions, such as the default Space-layout `s` jump, must have a documented native replacement, cheat-sheet coverage, and a classic-layout fallback.
|
||||
5. **One module, one concern.** Don't put git config in lsp.vim.
|
||||
|
||||
## Adding a plugin
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
2. If it's not needed at startup, lazy-load it: `Plug 'foo/bar', { 'on': 'FooCommand' }`
|
||||
3. Put config in the appropriate module
|
||||
4. Check new mappings against native Vim behavior before adding them
|
||||
5. Update the cheat sheet in `modules/tools.vim` if you add keybindings
|
||||
5. Update the cheat sheet definitions in `modules/cheatsheet.vim` if you add keybindings
|
||||
6. Run `scripts/test.sh vim` locally after installing plugins
|
||||
7. Test on both macOS and Linux when changing terminal or package-manager behavior
|
||||
|
||||
|
|
|
|||
117
QUICKSTART.md
117
QUICKSTART.md
|
|
@ -1,9 +1,17 @@
|
|||
# Quick Start
|
||||
|
||||
Five minutes from zero to a working Vim setup.
|
||||
Five minutes to understand the chopsticks project loop.
|
||||
|
||||
This guide assumes you already know Vim's editing language. chopsticks keeps
|
||||
that language intact and gives you one stable layer for the work around it:
|
||||
jump on the visible screen, switch project files, grep, run, inspect code,
|
||||
check git, and ask Vim which keys are active.
|
||||
|
||||
## Install
|
||||
|
||||
These commands install current `main`. For the 2.3.0 release-candidate
|
||||
checklist and rollback steps, use [BETA.md](BETA.md).
|
||||
|
||||
```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
|
||||
|
|
@ -18,50 +26,81 @@ without prompting. You can later put `let g:chopsticks_profile = 'minimal'` in
|
|||
`${XDG_CONFIG_HOME:-~/.config}/chopsticks.vim` for a smaller core-only setup,
|
||||
or use `full` for the heavier Markdown/LSP feedback.
|
||||
|
||||
The default keymap style is `space`: `SPC` is the command leader and `,` is
|
||||
reserved for filetype-local actions. To use the legacy comma layout instead,
|
||||
add this to `${XDG_CONFIG_HOME:-~/.config}/chopsticks.vim`:
|
||||
|
||||
```vim
|
||||
let g:chopsticks_keymap_style = 'classic'
|
||||
```
|
||||
|
||||
To switch later without reinstalling anything:
|
||||
|
||||
```bash
|
||||
cd ~/.vim && ./install.sh --configure-only --profile=full
|
||||
```
|
||||
|
||||
## Modes
|
||||
## Daily loop
|
||||
|
||||
| Mode | Enter | Leave |
|
||||
| ------ | --------------- | ------------- |
|
||||
| Normal | startup default | — |
|
||||
| Insert | `i` / `a` / `o` | `Esc` |
|
||||
| Visual | `v` / `V` | `Esc` |
|
||||
Train this first. It is the core reason to use chopsticks instead of assembling
|
||||
the same pieces yourself:
|
||||
|
||||
```
|
||||
SPC SPC open a project file
|
||||
s + 2 chars jump to visible text
|
||||
gd / gr / K inspect definition, references, docs
|
||||
SPC rr run the current file
|
||||
SPC / grep the project
|
||||
SPC gs check git status
|
||||
SPC ? show the active keymap
|
||||
```
|
||||
|
||||
## Survival
|
||||
|
||||
```
|
||||
Esc back to Normal
|
||||
SPC w save
|
||||
SPC q quit current window
|
||||
:x / ZZ save + quit
|
||||
:q! force quit
|
||||
SPC ? cheat sheet (toggle sidebar)
|
||||
SPC fc edit local preferences
|
||||
:ChopsticksHelp full native help
|
||||
```
|
||||
|
||||
Classic layout equivalents:
|
||||
|
||||
```
|
||||
Esc back to Normal
|
||||
,w save
|
||||
,x save + quit
|
||||
:q! force quit
|
||||
,? cheat sheet (toggle sidebar)
|
||||
,ec edit local preferences
|
||||
:ChopsticksHelp full native help
|
||||
```
|
||||
|
||||
## Find things
|
||||
|
||||
```
|
||||
,ff fuzzy find file (git-aware)
|
||||
,rg ripgrep project
|
||||
,b search buffers
|
||||
,fh recent files
|
||||
,e file browser
|
||||
,, last file
|
||||
SPC SPC fuzzy find file (git-aware)
|
||||
SPC / ripgrep project
|
||||
SPC , search buffers
|
||||
SPC fr recent files
|
||||
SPC e sidebar at project cwd
|
||||
SPC E sidebar at current file dir
|
||||
SPC Tab last file
|
||||
```
|
||||
|
||||
## Write code
|
||||
|
||||
```
|
||||
,dd go to definition
|
||||
,dk hover docs
|
||||
,rn rename symbol
|
||||
,ca code action
|
||||
,f format
|
||||
,cr run current file
|
||||
gd go to definition
|
||||
K hover docs
|
||||
SPC cr rename symbol
|
||||
SPC ca code action
|
||||
SPC cf format
|
||||
SPC rr run current file
|
||||
Tab / S-Tab cycle completions
|
||||
```
|
||||
|
||||
|
|
@ -70,31 +109,39 @@ Tab / S-Tab cycle completions
|
|||
## Git
|
||||
|
||||
```
|
||||
,gs status (s=stage, cc=commit)
|
||||
,gd diff
|
||||
,gb blame
|
||||
,gp push
|
||||
SPC gs status (s=stage, cc=commit)
|
||||
SPC gd diff
|
||||
SPC gb blame
|
||||
SPC gl log graph
|
||||
]x / [x conflict markers
|
||||
```
|
||||
|
||||
## Edit
|
||||
|
||||
In the default Space layout, Normal-mode `s` is a fast visible-text jump.
|
||||
Use `cl` when you want Vim's original single-character substitute behavior,
|
||||
and `cc` when you want Vim's original line substitute behavior.
|
||||
|
||||
```
|
||||
,S + 2 chars EasyMotion jump
|
||||
s + 2 chars EasyMotion jump
|
||||
SPC S + 2 chars same jump, discoverable fallback
|
||||
cl / cc native s / S substitute replacements
|
||||
gc toggle comment
|
||||
cs"' change surrounding " to '
|
||||
Alt+j / Alt+k move line
|
||||
,u undo tree
|
||||
,y clipboard yank
|
||||
SPC U undo tree
|
||||
SPC y clipboard yank
|
||||
```
|
||||
|
||||
## Navigate
|
||||
|
||||
```
|
||||
<C-w>h/j/k/l splits
|
||||
,h / ,l prev / next buffer
|
||||
,z maximize window
|
||||
,tv / ,th terminal
|
||||
Ctrl-h/j/k/l splits
|
||||
<C-w>h/j/k/l native Vim fallback
|
||||
SPC e, Ctrl-h/l open sidebar, enter/leave it
|
||||
SPC bp / SPC bn prev / next buffer
|
||||
SPC z maximize window
|
||||
SPC tt / SPC th terminal
|
||||
```
|
||||
|
||||
## Markdown
|
||||
|
|
@ -110,10 +157,16 @@ syntax. Enable the heavier Markdown tools only when you want them.
|
|||
## Health check
|
||||
|
||||
```
|
||||
:ChopsticksHelp full native Vim help
|
||||
:ChopsticksConfig edit local preferences
|
||||
:ChopsticksReload reload after saving local preferences
|
||||
:ChopsticksTutor guided practice for the final keymap
|
||||
:ChopsticksStatus see what's installed and what's missing
|
||||
```
|
||||
|
||||
The `,?` cheat sheet follows your active profile, so `minimal` users only see
|
||||
The `SPC ?` cheat sheet follows your active profile, so `minimal` users only see
|
||||
keys for features that are actually loaded.
|
||||
|
||||
See [README](README.md) for the full reference. See the [wiki](https://github.com/m1ngsama/chopsticks/wiki) for deep dives.
|
||||
Inside Vim, `:help chopsticks` opens the same reference after helptags are
|
||||
available. See [README](README.md) for the full reference. For release-candidate
|
||||
testing and rollback, see [BETA.md](BETA.md).
|
||||
|
|
|
|||
200
README.md
200
README.md
|
|
@ -1,11 +1,13 @@
|
|||
<p align="center">
|
||||
<img src=".github/demo.gif" alt="chopsticks demo" width="720">
|
||||
<br>
|
||||
<sub>For Vim users who already know how to edit: one trained project loop for jump, files, run, grep, git, and active key help.</sub>
|
||||
</p>
|
||||
|
||||
<h1 align="center">chopsticks</h1>
|
||||
|
||||
<p align="center">
|
||||
<strong>Vim for engineers. ~25 plugins, works over SSH.</strong>
|
||||
<strong>A long-term Vim efficiency kit: find, jump, run, grep, git, LSP, and self-documenting keys over SSH.</strong>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
|
@ -18,19 +20,48 @@
|
|||
|
||||
---
|
||||
|
||||
Install current `main`:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash
|
||||
```
|
||||
|
||||
Current `main` is preparing the 2.3.0 release. Use [BETA.md](BETA.md) for the
|
||||
release-candidate checklist, rollback steps, and Space keymap test loop.
|
||||
|
||||
---
|
||||
|
||||
## Why
|
||||
|
||||
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.
|
||||
chopsticks is for experienced Vim users who want one stable, ergonomic working
|
||||
set they can train once and keep for years. It does not replace Vim's editing
|
||||
language; it standardizes the project loop around it.
|
||||
|
||||
Stock Vim is a great editor core, but it does not ship that complete project
|
||||
workflow. You still have to assemble fuzzy finding, project grep, git, LSP,
|
||||
diagnostics, formatters, runners, terminal behavior, and a keymap that will not
|
||||
collapse over SSH.
|
||||
|
||||
That assembly work is the pain chopsticks removes:
|
||||
|
||||
- **Project motion is scattered.** Files, buffers, grep, tags, marks, git, and
|
||||
diagnostics live behind unrelated commands unless you design a system.
|
||||
- **Plugin defaults fight muscle memory.** chopsticks gives QWERTY users one
|
||||
canonical Space layout and keeps native Vim/LSP habits where they matter:
|
||||
`gd`, `gr`, `K`, `Ctrl-h/j/k/l`, `<C-w>hjkl`, `cl`, `cc`.
|
||||
- **Remote editing is fragile.** It is built to degrade on TTY, slow SSH, and
|
||||
headless machines instead of assuming a GUI desktop.
|
||||
- **Custom configs are hard to onboard.** `:ChopsticksHelp`, `SPC ?`,
|
||||
`:ChopsticksTutor`, `:ChopsticksConfig`, and `:ChopsticksStatus` make the
|
||||
active keymap, full help, local preferences, and missing tools visible inside
|
||||
Vim.
|
||||
|
||||
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.
|
||||
|
||||
chopsticks gives you a production-ready Vim config in one command. Pure VimScript — no Node.js for the core. Degrades gracefully on TTY. Works the same on your MacBook and your headless Arch box.
|
||||
|
||||
**24–25 plugins** (tmux-navigator loads only inside tmux), LSP, linting, and a hand-built statusline. No bloat, no decorations, just tools.
|
||||
**23+ plugins** depending on profile and opt-ins, LSP, linting, and a hand-built statusline. No bloat, no decorations, just tools.
|
||||
|
||||
## What's in the box
|
||||
|
||||
|
|
@ -39,14 +70,17 @@ chopsticks gives you a production-ready Vim config in one command. Pure VimScrip
|
|||
| **LSP** | completion, go-to-def, hover, rename, code actions — pure VimScript ([vim-lsp](https://github.com/prabirshrestha/vim-lsp)) |
|
||||
| **Lint + format** | [ALE](https://github.com/dense-analysis/ale) runs black, prettier, goimports, rustfmt on save |
|
||||
| **Fuzzy find** | files, buffers, grep, tags, marks, commands — [FZF](https://github.com/junegunn/fzf.vim) |
|
||||
| **Git** | status, diff, blame, push, pull, conflict markers — [fugitive](https://github.com/tpope/vim-fugitive) + [gitgutter](https://github.com/airblade/vim-gitgutter) |
|
||||
| **Run file** | `,cr` — auto-detects Python, Go, Rust, JS, C, Shell, and more |
|
||||
| **Git** | status, diff, blame, commit, log, conflict markers — [fugitive](https://github.com/tpope/vim-fugitive) + [gitgutter](https://github.com/airblade/vim-gitgutter) |
|
||||
| **Run file** | `SPC rr` — auto-detects Python, Go, Rust, JS, C, Shell, and more |
|
||||
| **Markdown** | quiet writing defaults, browser preview (`,mp`), table of contents (`,mt`) |
|
||||
| **Diagnostics** | `:ChopsticksStatus` — see what's installed, what's missing, how to fix it |
|
||||
| **TTY-aware** | degrades gracefully on SSH, console, slow links — never breaks |
|
||||
|
||||
## Install
|
||||
|
||||
These commands install current `main`. For the 2.3.0 release-candidate
|
||||
checklist and rollback steps, use [BETA.md](BETA.md).
|
||||
|
||||
```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
|
||||
|
|
@ -78,17 +112,20 @@ profile or uses `engineer`.
|
|||
|
||||
```vim
|
||||
" Put this in ${XDG_CONFIG_HOME:-~/.config}/chopsticks.vim.
|
||||
let g:chopsticks_profile = 'minimal' " core navigation/editing/git/markdown
|
||||
let g:chopsticks_profile = 'engineer' " default: LSP, ALE, syntax extras
|
||||
let g:chopsticks_profile = 'full' " engineer + heavier Markdown feedback
|
||||
" let g:chopsticks_profile = 'minimal' " core navigation/editing/git/markdown
|
||||
" let g:chopsticks_profile = 'full' " engineer + heavier Markdown feedback
|
||||
let g:chopsticks_keymap_style = 'space' " default: Space leader grouped layout
|
||||
" let g:chopsticks_keymap_style = 'classic' " optional legacy comma layout
|
||||
let g:chopsticks_enable_jk_escape = 1 " optional: insert-mode jk exits insert
|
||||
let g:chopsticks_enable_ctrl_s_save = 1 " optional: Ctrl-S saves
|
||||
let g:chopsticks_enable_sudo_save_bang = 1 " optional: :w!! sudo save
|
||||
let g:chopsticks_enable_completion_keymaps = 1 " optional: Tab/Enter completion
|
||||
let g:chopsticks_enable_auto_pairs = 1 " optional: automatic pair insertion
|
||||
let g:chopsticks_enable_terminal_keymaps = 1 " optional: terminal Esc/Ctrl navigation
|
||||
let g:chopsticks_enable_tmux_navigator = 1 " optional: vim-tmux-navigator integration
|
||||
let g:chopsticks_enable_exrc = 1 " optional: source project-local .vimrc/.exrc from CWD
|
||||
let g:chopsticks_enable_reindent_file = 1 " optional: ,F reindents the entire file
|
||||
let g:chopsticks_enable_reindent_file = 1 " optional: full-file reindent map
|
||||
```
|
||||
|
||||
`minimal` avoids LSP, ALE, completion plugins, extra language syntax plugins,
|
||||
|
|
@ -96,44 +133,73 @@ Startify, UndoTree, and browser Markdown preview. `full` keeps those and opts
|
|||
into Markdown lint, format, spell, conceal, Marksman, and LSP virtual text.
|
||||
|
||||
Project updates leave `~/.config/chopsticks.vim` alone, so put local choices
|
||||
there instead of editing the managed `.vimrc`. The `,?` cheat sheet follows the
|
||||
active profile and only shows keys for enabled features.
|
||||
there instead of editing the managed `.vimrc`. The `SPC ?` cheat sheet follows
|
||||
the active profile and only shows keys for enabled features. Inside Vim, use
|
||||
`:ChopsticksConfig` to edit that local file and `:ChopsticksReload` after
|
||||
saving it.
|
||||
|
||||
## Keys
|
||||
|
||||
Leader: `,`
|
||||
Default layout: `space`, leader `SPC`, localleader `,`.
|
||||
|
||||
This is the canonical layout for QWERTY keyboards with CapsLock mapped to
|
||||
tap-Esc / hold-Ctrl. Escape and Ctrl stay at the system layer; Vim keeps the
|
||||
native `<C-w>` window model as a fallback and standard LSP motions (`gd`,
|
||||
`gr`, `K`).
|
||||
Git push/pull are intentionally not bound to default hotkeys. Normal-mode `s`
|
||||
is a screen-local EasyMotion jump; use `cl` for native `s` substitute and `cc`
|
||||
for native `S`.
|
||||
|
||||
For learning the kit, use `:ChopsticksTutor` to train the core loop, `SPC ?`
|
||||
for the active keymap, `:ChopsticksHelp` / `:help chopsticks` for full native
|
||||
Vim help, `:ChopsticksConfig` for local preferences, and `:ChopsticksStatus`
|
||||
for tool/LSP health.
|
||||
`QUICKSTART.md` is the 5-minute path; this README is the full reference.
|
||||
During release-candidate testing, `:ChopsticksBeta` opens the in-editor
|
||||
checklist, `:ChopsticksBetaLog` opens editable local notes, and
|
||||
`:ChopsticksBetaSession` appends a timestamped session block.
|
||||
|
||||
```
|
||||
,ff fuzzy find file ,dd go to definition
|
||||
,rg ripgrep project ,dk hover docs
|
||||
,e toggle file sidebar ,cr run current file
|
||||
,gs git status ,f format
|
||||
,w save ,q quit
|
||||
Esc exit insert mode ,? cheat sheet
|
||||
SPC SPC fuzzy find file gd go to definition
|
||||
SPC / ripgrep project K hover docs
|
||||
SPC e toggle file sidebar SPC rr run current file
|
||||
Ctrl-h/l enter/leave sidebar Ctrl-hjkl windows
|
||||
SPC gs git status SPC cf format
|
||||
SPC w save SPC q quit
|
||||
Esc exit insert mode SPC ? cheat sheet
|
||||
:ChopsticksConfig local prefs :ChopsticksReload reload
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary><strong>All keybindings</strong></summary>
|
||||
<summary><strong>Canonical Space keybindings</strong></summary>
|
||||
|
||||
### Fast Path
|
||||
|
||||
`SPC SPC` files | `SPC ,` buffers | `SPC /` grep | `SPC Tab` alternate buffer | `SPC e` browser | `SPC E` browser (file dir)
|
||||
|
||||
### Files
|
||||
|
||||
`,ff` find | `,b` buffers | `,rg` grep | `,rG` grep word | `,fh` recent | `,fl` lines | `,e` browser | `,E` browser (file dir) | `,,` last file
|
||||
`SPC ff` files | `SPC fb` buffers | `SPC fg` git files | `SPC fr` recent | `SPC fl` buffer lines | `SPC fL` all lines | `SPC fc` local config | `SPC fv` edit vimrc | `SPC fV` reload
|
||||
|
||||
### Search
|
||||
|
||||
`SPC sg` grep | `SPC sw` grep word | `SPC s/` search history | `SPC s:` command history | `SPC sm` marks | `SPC st` tags | `SPC sr` replace word
|
||||
|
||||
### Code
|
||||
|
||||
`,dd` def | `,dt` type | `,di` impl | `,dr` refs | `,dk` docs | `,dp` `,dn` diagnostics | `[e` `]e` ALE errors | `,rn` rename | `,ca` action | `,o` outline | `,cr` run
|
||||
`gd` def | `gr` refs | `gI` impl | `gy` type | `K` docs | `[d` `]d` LSP diagnostics | `[e` `]e` ALE errors | `SPC ca` action | `SPC cr` rename | `SPC cf` format | `SPC co` outline | `SPC ci` LSP status | `SPC rr` run
|
||||
|
||||
### Edit
|
||||
|
||||
`,S`+2ch jump | `gc` comment | `cs"'` surround | `Alt+j/k` move line | `,u` undo tree | `,y` clipboard | `,*` replace word | `,F` re-indent (v) | `,W` strip whitespace | `[<Space>` `]<Space>` blank lines
|
||||
`s`+2ch jump | `SPC S` jump fallback | `cl` native `s` substitute | `cc` native `S` substitute | `gc` comment | `cs"'` surround | `Alt+j/k` move line | `SPC U` undo tree | `SPC y` clipboard | `SPC =` re-indent visual | `SPC cW` strip whitespace | `[<Space>` `]<Space>` blank lines
|
||||
|
||||
### Git
|
||||
|
||||
`,gs` status | `,gd` diff | `,gb` blame | `,gc` commit | `,gp` push | `,gl` pull | `,gL` log graph | `,gC` FZF commits | `,gB` buffer commits | `]x` `[x` conflict
|
||||
`SPC gs` status | `SPC gd` diff | `SPC gb` blame | `SPC gc` commit | `SPC gl` log graph | `SPC gC` FZF commits | `SPC gB` buffer commits | `]x` `[x` conflict
|
||||
|
||||
### Windows
|
||||
|
||||
`<C-w>hjkl` navigate | `,z` maximize | `,h` `,l` buffers | `,bd` close buffer | `,=` `,−` resize | `,tv` `,th` terminal
|
||||
`Ctrl-h/j/k/l` windows | `<C-w>h/j/k/l` native fallback | `SPC z` maximize | `SPC bp` `SPC bn` buffers | `SPC bd` close buffer | `SPC bo` close other buffers | `SPC tt` `SPC th` terminal | `]q` `[q` quickfix | `SPC xq` `SPC xQ` open/close quickfix | `SPC xl` `SPC xL` open/close loclist
|
||||
|
||||
### Markdown
|
||||
|
||||
|
|
@ -141,11 +207,48 @@ Esc exit insert mode ,? cheat sheet
|
|||
|
||||
### Toggle
|
||||
|
||||
`F2` paste | `F3` line numbers | `F4` relative numbers | `F6` invisible chars | `SPC us` spell check | `SPC uf` format on save
|
||||
|
||||
### Survival
|
||||
|
||||
`SPC w` save | `SPC W` save all | `SPC q` quit | `:x` / `ZZ` save and quit | `SPC fc` local config | `SPC fV` reload | `SPC ?` cheat sheet | `:ChopsticksHelp` full help | `:ChopsticksTutor` practice | `:ChopsticksStatus` diagnostics
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Legacy classic keybindings</strong></summary>
|
||||
|
||||
### Classic Files
|
||||
|
||||
`,ff` find | `,b` buffers | `,rg` grep | `,rG` grep word | `,fh` recent | `,fl` lines | `,e` browser | `,E` browser (file dir) | `,,` last file
|
||||
|
||||
### Classic Code
|
||||
|
||||
`,dd` def | `,dt` type | `,di` impl | `,dr` refs | `,dk` docs | `,dp` `,dn` diagnostics | `[e` `]e` ALE errors | `,rn` rename | `,ca` action | `,o` outline | `,cr` run
|
||||
|
||||
### Classic Edit
|
||||
|
||||
`,S`+2ch jump | `gc` comment | `cs"'` surround | `Alt+j/k` move line | `,u` undo tree | `,y` clipboard | `,*` replace word | `,F` re-indent (v) | `,W` strip whitespace | `[<Space>` `]<Space>` blank lines
|
||||
|
||||
### Classic Git
|
||||
|
||||
`,gs` status | `,gd` diff | `,gb` blame | `,gc` commit | `,gL` log graph | `,gC` FZF commits | `,gB` buffer commits | `]x` `[x` conflict
|
||||
|
||||
### Classic Windows
|
||||
|
||||
`Ctrl-h/j/k/l` windows | `<C-w>h/j/k/l` native fallback | `,z` maximize | `,h` `,l` buffers | `,bd` close buffer | `,=` `,-` resize | `,tv` `,th` terminal
|
||||
|
||||
### Classic Markdown
|
||||
|
||||
`,mp` preview in browser | `,mt` table of contents
|
||||
|
||||
### Classic Toggle
|
||||
|
||||
`F2` paste | `F3` line numbers | `F4` relative numbers | `F6` invisible chars | `,ss` spell check | `,af` format on save
|
||||
|
||||
### Utilities
|
||||
|
||||
`,cp` copy full path | `,cf` copy filename | `,ev` edit vimrc | `,sv` reload vimrc | `,wa` save all | `:ChopsticksStatus` diagnostics
|
||||
`,cp` copy full path | `,cf` copy filename | `,ec` local config | `,ev` edit vimrc | `,sv` reload | `,wa` save all | `:ChopsticksStatus` diagnostics
|
||||
|
||||
</details>
|
||||
|
||||
|
|
@ -192,18 +295,30 @@ For Markdown LSP, install or select `marksman` first.
|
|||
```
|
||||
~/.vim/
|
||||
├── .vimrc thin loader
|
||||
├── modules/
|
||||
│ ├── env.vim TTY detection, truecolor, skip built-in plugins
|
||||
│ ├── plugins.vim vim-plug + 24–25 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
|
||||
├── doc/
|
||||
│ └── chopsticks.txt :help chopsticks
|
||||
└── modules/
|
||||
├── env.vim TTY detection, truecolor, skip built-in plugins
|
||||
├── plugins.vim vim-plug + profile/option-driven 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
|
||||
├── buffers.vim buffer commands
|
||||
├── utilities.vim config, reload, trim, clipboard helpers
|
||||
├── files.vim auto mkdir, large-file protection
|
||||
├── runner.vim run current file
|
||||
├── quickfix.vim quickfix and location-list helpers
|
||||
├── status.vim :ChopsticksStatus diagnostics
|
||||
├── cheatsheet.vim SPC ? and :ChopsticksCheatSheet
|
||||
├── tutor.vim :ChopsticksTutor guided practice
|
||||
├── beta.vim :ChopsticksBeta release checklist
|
||||
├── help.vim :ChopsticksHelp native Vim help
|
||||
└── tools.vim compatibility placeholder
|
||||
```
|
||||
|
||||
Each module is self-contained. Comment out one line in `.vimrc` to disable it. Add your own with `call s:load('mine')`.
|
||||
|
|
@ -228,12 +343,23 @@ Each module is self-contained. Comment out one line in `.vimrc` to disable it. A
|
|||
| Everything slow | Large file? Auto-disabled >10MB |
|
||||
| What's installed? | `:ChopsticksStatus` shows tools, LSP, linters |
|
||||
|
||||
More in the [wiki](https://github.com/m1ngsama/chopsticks/wiki).
|
||||
For deeper checks, start with `:ChopsticksStatus`, `SPC ?`,
|
||||
`:ChopsticksTutor`, `:ChopsticksHelp`, `:ChopsticksConfig`, and
|
||||
[QUICKSTART.md](QUICKSTART.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md). The two rules that matter: no Node.js in the Vim runtime, and don't regress startup time.
|
||||
|
||||
Regenerate the README demo after changing public keybindings:
|
||||
|
||||
```bash
|
||||
vhs .github/demo.tape
|
||||
```
|
||||
|
||||
The tape uses `.github/demo-project` and forces the current repository `.vimrc`,
|
||||
so the GIF should show the same keymap the code actually ships.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
|
|
|||
181
doc/chopsticks.txt
Normal file
181
doc/chopsticks.txt
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
*chopsticks.txt* *chopsticks* A long-term Vim efficiency kit
|
||||
|
||||
==============================================================================
|
||||
CONTENTS *chopsticks-contents*
|
||||
|
||||
1. What chopsticks solves...............|chopsticks-why|
|
||||
2. First five minutes...................|chopsticks-start|
|
||||
3. Canonical Space layout...............|chopsticks-space|
|
||||
4. Commands.............................|chopsticks-commands|
|
||||
5. Profiles and local config............|chopsticks-profiles|
|
||||
6. Release testing......................|chopsticks-release-test|
|
||||
7. Troubleshooting......................|chopsticks-troubleshooting|
|
||||
|
||||
==============================================================================
|
||||
WHAT CHOPSTICKS SOLVES *chopsticks-why*
|
||||
|
||||
Chopsticks is for experienced Vim users who want one stable, ergonomic working
|
||||
set they can train once and keep for years. It connects fuzzy find, project
|
||||
grep, git, LSP, linting, formatting, runners, quickfix navigation, and
|
||||
self-documenting keys for people who edit locally and over SSH.
|
||||
|
||||
It is meant to supplement stock Vim, not replace Vim muscle memory. Native
|
||||
motions and well-known conventions stay where they are useful: gd, gr, K,
|
||||
Ctrl-h/j/k/l, <C-w>hjkl, cl, cc, quickfix, and normal Vim commands.
|
||||
|
||||
==============================================================================
|
||||
FIRST FIVE MINUTES *chopsticks-start*
|
||||
|
||||
Inside Vim:
|
||||
>
|
||||
:ChopsticksTutor guided practice page
|
||||
SPC ? active keymap cheat sheet
|
||||
:ChopsticksStatus tool, plugin, and LSP diagnostics
|
||||
:ChopsticksHelp this help page
|
||||
:ChopsticksConfig edit local preferences
|
||||
<
|
||||
|
||||
Daily loop:
|
||||
>
|
||||
SPC SPC find file SPC / grep project
|
||||
s + 2ch jump on screen gd / gr definition / references
|
||||
K hover docs SPC rr run current file
|
||||
SPC gs git status SPC cf format
|
||||
SPC w save SPC q quit
|
||||
<
|
||||
|
||||
==============================================================================
|
||||
CANONICAL SPACE LAYOUT *chopsticks-space*
|
||||
|
||||
Default layout: Space leader, comma localleader.
|
||||
|
||||
This layout assumes a QWERTY keyboard and CapsLock mapped at the system layer
|
||||
to tap-Esc / hold-Ctrl. Ctrl-h/j/k/l is the fast path for Vim windows;
|
||||
<C-w>hjkl remains the native fallback. Standard LSP motions stay on code.
|
||||
|
||||
Normal-mode s is a visible EasyMotion jump. This is intentionally different
|
||||
from stock Vim because screen-local jumping is higher value in project editing.
|
||||
Use cl for native s substitute and cc for native S substitute.
|
||||
|
||||
High-frequency keys:
|
||||
>
|
||||
SPC SPC files SPC , buffers
|
||||
SPC / grep project SPC Tab alternate file
|
||||
SPC e/E file sidebar SPC rr run file
|
||||
Ctrl-hjkl windows Ctrl-h/l enter/leave sidebar
|
||||
SPC gs git status SPC gl git log graph
|
||||
SPC ca code action SPC cr rename
|
||||
SPC cf format SPC fc edit local config
|
||||
SPC fV reload config SPC ? cheat sheet
|
||||
<
|
||||
|
||||
Classic comma mappings remain available:
|
||||
>
|
||||
let g:chopsticks_keymap_style = 'classic'
|
||||
<
|
||||
|
||||
==============================================================================
|
||||
COMMANDS *chopsticks-commands*
|
||||
|
||||
User-facing commands:
|
||||
>
|
||||
:ChopsticksHelp open this help
|
||||
:ChopsticksConfig edit local preferences
|
||||
:ChopsticksReload reload chopsticks after config changes
|
||||
:ChopsticksTutor guided practice
|
||||
:ChopsticksCheatSheet active keymap reference
|
||||
:ChopsticksStatus health diagnostics
|
||||
:ChopsticksBeta release checklist
|
||||
:ChopsticksBetaLog editable release notes
|
||||
:ChopsticksBetaSession append a timestamped release note block
|
||||
<
|
||||
|
||||
LSP commands come from vim-lsp:
|
||||
>
|
||||
:LspInstallServer install a server for the current filetype
|
||||
:LspStatus inspect attached servers
|
||||
<
|
||||
|
||||
==============================================================================
|
||||
PROFILES AND LOCAL CONFIG *chopsticks-profiles*
|
||||
|
||||
Keep personal choices outside the managed .vimrc:
|
||||
>
|
||||
" ${XDG_CONFIG_HOME:-~/.config}/chopsticks.vim
|
||||
let g:chopsticks_profile = 'engineer'
|
||||
let g:chopsticks_keymap_style = 'space'
|
||||
<
|
||||
|
||||
Open that file from inside Vim:
|
||||
>
|
||||
:ChopsticksConfig
|
||||
<
|
||||
|
||||
Reload after saving it:
|
||||
>
|
||||
:ChopsticksReload
|
||||
<
|
||||
|
||||
Profiles:
|
||||
|
||||
minimal Core navigation, editing, git, Markdown. No LSP/ALE/completion.
|
||||
engineer Default. LSP, ALE, completion, syntax extras.
|
||||
full Engineer plus heavier Markdown feedback.
|
||||
|
||||
Optional habits:
|
||||
>
|
||||
let g:chopsticks_enable_jk_escape = 1
|
||||
let g:chopsticks_enable_ctrl_s_save = 1
|
||||
let g:chopsticks_enable_auto_pairs = 1
|
||||
let g:chopsticks_enable_terminal_keymaps = 1
|
||||
<
|
||||
|
||||
==============================================================================
|
||||
RELEASE TESTING *chopsticks-release-test*
|
||||
|
||||
For the 2.3.0 release candidate, record real editing friction instead of
|
||||
abstract opinions:
|
||||
>
|
||||
:ChopsticksBeta compact release checklist
|
||||
:ChopsticksBetaLog open editable local notes
|
||||
:ChopsticksBetaSession append a new session block
|
||||
<
|
||||
|
||||
Default release log:
|
||||
>
|
||||
${XDG_CONFIG_HOME:-~/.config}/chopsticks-2.3.0.md
|
||||
<
|
||||
|
||||
Exit criteria before a stable release:
|
||||
|
||||
- s as jump still feels worth the native override.
|
||||
- No high-frequency action needs an undocumented key.
|
||||
- README, QUICKSTART, :help chopsticks, SPC ?, and :ChopsticksTutor agree.
|
||||
- quick and Vim smoke tests pass locally and over SSH.
|
||||
- The README GIF matches the public keymap.
|
||||
|
||||
==============================================================================
|
||||
TROUBLESHOOTING *chopsticks-troubleshooting*
|
||||
|
||||
Start with:
|
||||
>
|
||||
:ChopsticksStatus
|
||||
SPC ?
|
||||
:ChopsticksTutor
|
||||
:ChopsticksHelp
|
||||
:ChopsticksConfig
|
||||
<
|
||||
|
||||
Common fixes:
|
||||
|
||||
Plugins missing :PlugInstall
|
||||
LSP missing Open that filetype, then :LspInstallServer
|
||||
Need active keys SPC ?
|
||||
Need full docs :help chopsticks
|
||||
Need local config :ChopsticksConfig
|
||||
Changed local config :ChopsticksReload
|
||||
Need release notes :ChopsticksBetaSession
|
||||
Slow large file Syntax, undo, swap, and ALE are auto-reduced
|
||||
|
||||
==============================================================================
|
||||
vim:tw=78:ts=8:ft=help:norl:
|
||||
33
install.sh
33
install.sh
|
|
@ -719,6 +719,21 @@ if [[ $_plug_count -eq 0 ]]; then
|
|||
fi
|
||||
ok "Plugins installed ($_plug_count)"
|
||||
|
||||
step "Installing Vim help"
|
||||
if [[ -d "$SCRIPT_DIR/doc" ]]; then
|
||||
# shellcheck disable=SC2016 # $CHOPSTICKS_HELP_DIR is expanded by Vim.
|
||||
if CHOPSTICKS_HELP_DIR="$SCRIPT_DIR/doc" \
|
||||
"$VIM_BIN" -Nu NONE -i NONE -n -es -N \
|
||||
-c 'execute "silent! helptags " . fnameescape($CHOPSTICKS_HELP_DIR)' \
|
||||
-c 'qa!' >/dev/null 2>&1; then
|
||||
ok ":help chopsticks"
|
||||
else
|
||||
warn "Could not generate Vim help tags. Inside Vim, run: :ChopsticksHelp"
|
||||
fi
|
||||
else
|
||||
skip "Vim help docs not found"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# 4. Module Selection
|
||||
# ============================================================================
|
||||
|
|
@ -741,22 +756,22 @@ _I_NPM=-1; _I_PYTHON=-1; _I_GO=-1; _I_TMUX=-1
|
|||
# Is any package manager available?
|
||||
HAS_PKG_MGR=0
|
||||
if [[ $HAS_BREW -eq 1 ]] || \
|
||||
{ [[ $HAS_APT -eq 1 || $HAS_PACMAN -eq 1 || $HAS_DNF -eq 1 ]] && [[ $HAS_SUDO -eq 1 ]]; }; then
|
||||
[[ $HAS_APT -eq 1 || $HAS_PACMAN -eq 1 || $HAS_DNF -eq 1 ]]; then
|
||||
HAS_PKG_MGR=1
|
||||
fi
|
||||
|
||||
# ── System tools ─────────────────────────────────────────────────────────────
|
||||
if [[ $HAS_PKG_MGR -eq 1 ]]; then
|
||||
_I_RIPGREP=$_idx
|
||||
_ITEMS+=("ripgrep|,rg / ,rG project-wide search · powers FZF preview|1")
|
||||
_ITEMS+=("ripgrep|SPC / project search · SPC sw word search · powers FZF preview|1")
|
||||
: $(( _idx++ ))
|
||||
|
||||
_I_FZF=$_idx
|
||||
_ITEMS+=("fzf|,ff fuzzy file search · ,b buffers · ,rt tag search|1")
|
||||
_ITEMS+=("fzf|SPC SPC files · SPC , buffers · SPC st tag search|1")
|
||||
: $(( _idx++ ))
|
||||
|
||||
_I_CTAGS=$_idx
|
||||
_ITEMS+=("universal-ctags|Optional symbol index for ,rt tag jumps|0")
|
||||
_ITEMS+=("universal-ctags|Optional symbol index for SPC st tag jumps|0")
|
||||
: $(( _idx++ ))
|
||||
|
||||
if [[ $_PROFILE_TOOLING -eq 1 ]]; then
|
||||
|
|
@ -848,6 +863,11 @@ _do_sys() {
|
|||
if command -v "$check" >/dev/null 2>&1; then
|
||||
ok "$name (already installed)"; return
|
||||
fi
|
||||
if [[ $OS != "macos" && $HAS_SUDO -ne 1 ]]; then
|
||||
skip "$name — sudo not available, install manually"
|
||||
SKIPPED+=("$name")
|
||||
return
|
||||
fi
|
||||
if pkg_install "$brew_p" "$apt_p" "$pac_p" "$dnf_p"; then
|
||||
ok "$name"; INSTALLED+=("$name")
|
||||
else
|
||||
|
|
@ -1201,9 +1221,10 @@ echo -e " ${CYAN}vim .${NC} Open dashboard in current directory"
|
|||
echo -e " ${CYAN}vim myfile${NC} Edit a specific file"
|
||||
echo ""
|
||||
echo -e "${BOLD} First steps inside Vim${NC}"
|
||||
echo -e " ${CYAN},?${NC} Open cheat sheet — your map of every keybinding"
|
||||
echo -e " ${CYAN}SPC ?${NC} Open cheat sheet — your map of every keybinding"
|
||||
echo -e " ${CYAN}Esc${NC} Exit insert mode → back to Normal"
|
||||
echo -e " ${CYAN},x${NC} Save and quit"
|
||||
echo -e " ${CYAN}SPC q${NC} Quit current window"
|
||||
echo -e " ${CYAN}:x${NC} / ${CYAN}ZZ${NC} Save and quit"
|
||||
echo -e " ${CYAN}:q!${NC} + Enter Emergency quit without saving"
|
||||
if [[ $CONFIG_PROFILE != "minimal" ]]; then
|
||||
echo -e " ${CYAN}:LspInstallServer${NC} Install LSP for current filetype"
|
||||
|
|
|
|||
124
modules/beta.vim
Normal file
124
modules/beta.vim
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
" beta.vim — in-editor release-candidate checklist
|
||||
|
||||
let g:chopsticks_beta_label = get(g:, 'chopsticks_beta_label', '2.3.0')
|
||||
|
||||
function! s:OpenBetaGuide() abort
|
||||
let l:name = '__ChopsticksBeta__'
|
||||
if bufwinnr(l:name) > 0
|
||||
execute bufwinnr(l:name) . 'wincmd w | bd'
|
||||
return
|
||||
endif
|
||||
|
||||
execute 'botright new ' . l:name
|
||||
resize 34
|
||||
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile
|
||||
setlocal nowrap nonumber norelativenumber signcolumn=no
|
||||
|
||||
let l:lines = [
|
||||
\ ' chopsticks 2.3.0 q close',
|
||||
\ ' ─────────────────────────────',
|
||||
\ '',
|
||||
\ ' goal',
|
||||
\ ' Prove this can be the long-term project loop.',
|
||||
\ ' Record real editing friction before release.',
|
||||
\ '',
|
||||
\ ' daily loop',
|
||||
\ ' SPC SPC find file',
|
||||
\ ' s + 2ch jump on screen',
|
||||
\ ' gd / gr definition / references',
|
||||
\ ' K hover docs',
|
||||
\ ' SPC / grep project',
|
||||
\ ' SPC rr run current file',
|
||||
\ ' SPC gs git status',
|
||||
\ ' SPC cf format',
|
||||
\ ' SPC ? active cheat sheet',
|
||||
\ ' :ChopsticksBetaSession new note block',
|
||||
\ '',
|
||||
\ ' record',
|
||||
\ ' task: project navigation, code, grep, git, LSP, Markdown, SSH',
|
||||
\ ' first key tried when stuck',
|
||||
\ ' whether SPC ?, :ChopsticksTutor, or :ChopsticksStatus answered it',
|
||||
\ ' any key that felt slow, awkward, surprising, or easy to mistype',
|
||||
\ '',
|
||||
\ ' exit criteria',
|
||||
\ ' s as jump still feels worth the native override',
|
||||
\ ' no high-frequency action needs an undocumented key',
|
||||
\ ' window/sidebar navigation beats native <C-w> only',
|
||||
\ ' README, QUICKSTART, SPC ?, and tutor teach the same layout',
|
||||
\ ' no private wiki is needed to remember the daily loop',
|
||||
\ ' quick/vim tests pass locally and over SSH',
|
||||
\ '',
|
||||
\ ' files',
|
||||
\ ' BETA.md release checklist and rollback',
|
||||
\ ' :ChopsticksBetaLog editable local release notes',
|
||||
\ ' :ChopsticksBetaSession append a new session block',
|
||||
\ ' QUICKSTART.md five-minute path',
|
||||
\ ' README.md complete reference',
|
||||
\ ]
|
||||
|
||||
call setline(1, l:lines)
|
||||
setlocal nomodifiable readonly
|
||||
nnoremap <buffer> <silent> q :bd<CR>
|
||||
nnoremap <buffer> <silent> ? :ChopsticksCheatSheet<CR>
|
||||
endfunction
|
||||
|
||||
function! s:BetaLogPath() abort
|
||||
let l:configured = get(g:, 'chopsticks_beta_log', '')
|
||||
if !empty(l:configured)
|
||||
return expand(l:configured)
|
||||
endif
|
||||
|
||||
let l:xdg = !empty($XDG_CONFIG_HOME) && $XDG_CONFIG_HOME =~# '^/'
|
||||
\ ? $XDG_CONFIG_HOME
|
||||
\ : '~/.config'
|
||||
return expand(l:xdg . '/chopsticks-2.3.0.md')
|
||||
endfunction
|
||||
|
||||
function! s:SessionBlock() abort
|
||||
return [
|
||||
\ '',
|
||||
\ '## ' . strftime('%Y-%m-%d %H:%M'),
|
||||
\ '',
|
||||
\ '- Task:',
|
||||
\ '- First key tried when stuck:',
|
||||
\ '- Did SPC ?, :ChopsticksTutor, or :ChopsticksStatus answer it:',
|
||||
\ '- Friction:',
|
||||
\ '- Decision:',
|
||||
\ ]
|
||||
endfunction
|
||||
|
||||
function! s:EnsureBetaLog(path) abort
|
||||
let l:path = a:path
|
||||
let l:dir = fnamemodify(l:path, ':h')
|
||||
if !isdirectory(l:dir)
|
||||
call mkdir(l:dir, 'p')
|
||||
endif
|
||||
|
||||
if !filereadable(l:path)
|
||||
call writefile([
|
||||
\ '# chopsticks 2.3.0 release log',
|
||||
\ '',
|
||||
\ 'Use :ChopsticksBeta for the release checklist. Keep one session block per real editing session.',
|
||||
\ ] + s:SessionBlock(), l:path)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:OpenBetaLog() abort
|
||||
let l:path = s:BetaLogPath()
|
||||
call s:EnsureBetaLog(l:path)
|
||||
execute 'edit ' . fnameescape(l:path)
|
||||
setlocal filetype=markdown
|
||||
endfunction
|
||||
|
||||
function! s:AppendBetaSession() abort
|
||||
let l:path = s:BetaLogPath()
|
||||
call s:EnsureBetaLog(l:path)
|
||||
call writefile(s:SessionBlock(), l:path, 'a')
|
||||
execute 'edit ' . fnameescape(l:path)
|
||||
setlocal filetype=markdown
|
||||
normal! G
|
||||
endfunction
|
||||
|
||||
command! ChopsticksBeta call s:OpenBetaGuide()
|
||||
command! ChopsticksBetaLog call s:OpenBetaLog()
|
||||
command! ChopsticksBetaSession call s:AppendBetaSession()
|
||||
18
modules/buffers.vim
Normal file
18
modules/buffers.vim
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
" buffers.vim — buffer commands
|
||||
|
||||
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
|
||||
291
modules/cheatsheet.vim
Normal file
291
modules/cheatsheet.vim
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
" cheatsheet.vim — active keymap reference
|
||||
|
||||
function! s:OpenCheatSheet(lines) abort
|
||||
let l:name = '__ChopsticksCheatSheet__'
|
||||
if bufwinnr(l:name) > 0
|
||||
execute bufwinnr(l:name) . 'wincmd w | bd'
|
||||
return
|
||||
endif
|
||||
|
||||
execute 'vertical botright new ' . l:name
|
||||
vertical resize 42
|
||||
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile
|
||||
setlocal nowrap nonumber norelativenumber signcolumn=no
|
||||
setlocal winfixwidth
|
||||
call setline(1, a:lines)
|
||||
setlocal nomodifiable readonly
|
||||
nnoremap <buffer> <silent> q :bd<CR>
|
||||
nnoremap <buffer> <silent> <leader>? :bd<CR>
|
||||
endfunction
|
||||
|
||||
function! s:CheatSheet() abort
|
||||
let l:has_lsp = get(g:, 'chopsticks_enable_lsp', 1)
|
||||
let l:has_lint = get(g:, 'chopsticks_enable_lint', 1)
|
||||
let l:has_undotree = exists('g:plugs["undotree"]')
|
||||
let l:has_previm = exists('g:plugs["previm"]')
|
||||
|
||||
if g:chopsticks_space_keymaps
|
||||
let l:lines = [
|
||||
\ ' chopsticks <Space>? close',
|
||||
\ ' ─────────────────────────────────',
|
||||
\ '',
|
||||
\ ' trained loop:',
|
||||
\ ' files → s jump → gd/K',
|
||||
\ ' run → grep → git',
|
||||
\ '',
|
||||
\ ' ── fast path ─────────────',
|
||||
\ ' SPC SPC files',
|
||||
\ ' SPC , buffers',
|
||||
\ ' SPC / grep project',
|
||||
\ ' SPC Tab last file',
|
||||
\ ' SPC e sidebar (cwd)',
|
||||
\ ' SPC E sidebar (file dir)',
|
||||
\ '',
|
||||
\ ' ── files/find ────────────',
|
||||
\ ' SPC ff files',
|
||||
\ ' SPC fb buffers',
|
||||
\ ' SPC fg git files',
|
||||
\ ' SPC fr recent files',
|
||||
\ ' SPC fl lines in buffer',
|
||||
\ ' SPC sc commands',
|
||||
\ ' SPC sm marks',
|
||||
\ ' SPC s/ search history',
|
||||
\ ' SPC s: command history',
|
||||
\ ' SPC sg grep project',
|
||||
\ ' SPC sw grep word',
|
||||
\ ' SPC st tags',
|
||||
\ '',
|
||||
\ ' ── code ──────────────────',
|
||||
\ ]
|
||||
|
||||
if l:has_lsp
|
||||
call extend(l:lines, [
|
||||
\ ' gd definition',
|
||||
\ ' gr references',
|
||||
\ ' gI implementation',
|
||||
\ ' gy type definition',
|
||||
\ ' K hover docs',
|
||||
\ ' [d ]d LSP diagnostics',
|
||||
\ ' SPC ca code action',
|
||||
\ ' SPC cr rename',
|
||||
\ ' SPC cf format',
|
||||
\ ' SPC ci LSP status',
|
||||
\ ' SPC co outline',
|
||||
\ ' SPC cS workspace symbols',
|
||||
\ ' :LspInstallServer setup LSP',
|
||||
\ ' :ChopsticksStatus check LSP setup',
|
||||
\ ])
|
||||
endif
|
||||
|
||||
call extend(l:lines, [
|
||||
\ ' SPC rr run file',
|
||||
\ ' SPC cW strip trailing',
|
||||
\ ' SPC c= re-indent file (opt-in)',
|
||||
\ ' SPC = re-indent (v)',
|
||||
\ ])
|
||||
if l:has_previm
|
||||
call add(l:lines, ' ,mp markdown preview')
|
||||
endif
|
||||
call add(l:lines, ' ,mt table of contents')
|
||||
|
||||
if l:has_lint
|
||||
call extend(l:lines, [
|
||||
\ ' [e ]e ALE errors',
|
||||
\ ' SPC xd ALE detail',
|
||||
\ ' SPC uf format on save',
|
||||
\ ])
|
||||
endif
|
||||
|
||||
call extend(l:lines, [
|
||||
\ '',
|
||||
\ ' ── edit ──────────────────',
|
||||
\ ' s+2ch easymotion jump',
|
||||
\ ' gc comment',
|
||||
\ ' cl / cc native s / S substitute',
|
||||
\ ' SPC S+2ch jump fallback',
|
||||
\ ' cs"'' surround',
|
||||
\ ])
|
||||
|
||||
if l:has_undotree
|
||||
call add(l:lines, ' SPC U undo tree')
|
||||
endif
|
||||
|
||||
call extend(l:lines, [
|
||||
\ ' SPC y/p clipboard y/p (v)',
|
||||
\ ' Alt+j/k move line (v)',
|
||||
\ ' SPC sr replace word (v)',
|
||||
\ '',
|
||||
\ ' ── git ───────────────────',
|
||||
\ ' SPC gs status',
|
||||
\ ' SPC gd diff',
|
||||
\ ' SPC gb blame',
|
||||
\ ' SPC gc commit',
|
||||
\ ' SPC gl log graph',
|
||||
\ ' SPC gC FZF commits',
|
||||
\ ' SPC gB FZF buffer commits',
|
||||
\ ' [x ]x conflict markers',
|
||||
\ '',
|
||||
\ ' ── windows ───────────────',
|
||||
\ ' Ctrl-hjkl windows',
|
||||
\ ' <C-w>hjkl native fallback',
|
||||
\ ' SPC bp/bn prev / next buf',
|
||||
\ ' SPC bd close buffer',
|
||||
\ ' SPC bo close other buffers',
|
||||
\ ' SPC z maximize toggle',
|
||||
\ ' SPC tt/th terminal / split',
|
||||
\ ' ]q [q next / prev qf',
|
||||
\ ' SPC xq/xQ open / close qf',
|
||||
\ ' SPC xl/xL open / close loclist',
|
||||
\ '',
|
||||
\ ' ── toggle ────────────────',
|
||||
\ ' F2 paste mode',
|
||||
\ ' F3 line numbers',
|
||||
\ ' F4 relative numbers',
|
||||
\ ' F6 invisible chars',
|
||||
\ ' SPC us spell check',
|
||||
\ '',
|
||||
\ ' ── survival ──────────────',
|
||||
\ ' SPC w save',
|
||||
\ ' SPC W save all',
|
||||
\ ' SPC q quit',
|
||||
\ ' :x / ZZ save + quit',
|
||||
\ ' Esc exit insert',
|
||||
\ ' SPC fc edit local config',
|
||||
\ ' SPC fv edit vimrc',
|
||||
\ ' SPC fV reload vimrc',
|
||||
\ ' :ChopsticksHelp full help',
|
||||
\ ' :ChopsticksConfig local config',
|
||||
\ ' :ChopsticksReload reload config',
|
||||
\ ' :ChopsticksTutor practice',
|
||||
\ ' :ChopsticksStatus health',
|
||||
\ ' :ChopsticksBeta release checklist',
|
||||
\ ' :ChopsticksBetaLog release notes',
|
||||
\ ' :ChopsticksBetaSession new release note',
|
||||
\ ])
|
||||
|
||||
call s:OpenCheatSheet(l:lines)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:lines = [
|
||||
\ ' chopsticks ,? close',
|
||||
\ ' ─────────────────────────────',
|
||||
\ '',
|
||||
\ ' trained loop:',
|
||||
\ ' files → jump → inspect',
|
||||
\ ' run → grep → git',
|
||||
\ '',
|
||||
\ ' ── files ──────────────────',
|
||||
\ ' ,ff files',
|
||||
\ ' ,b buffers',
|
||||
\ ' ,rg grep project',
|
||||
\ ' ,rG grep word',
|
||||
\ ' ,e sidebar (cwd)',
|
||||
\ ' ,E sidebar (file dir)',
|
||||
\ ' ,, last file',
|
||||
\ ' ,fh recent files',
|
||||
\ ' ,fl lines in buffer',
|
||||
\ ' ,fc commands',
|
||||
\ ' ,fm marks',
|
||||
\ '',
|
||||
\ ' ── code ──────────────────',
|
||||
\ ]
|
||||
|
||||
if l:has_lsp
|
||||
call extend(l:lines, [
|
||||
\ ' ,dd definition',
|
||||
\ ' ,dt type definition',
|
||||
\ ' ,di implementation',
|
||||
\ ' ,dr references',
|
||||
\ ' ,dk hover docs',
|
||||
\ ' ,rn rename',
|
||||
\ ' ,ca code action',
|
||||
\ ' ,f format',
|
||||
\ ' ,o outline',
|
||||
\ ' ,dp ,dn LSP diagnostics',
|
||||
\ ' :LspInstallServer setup LSP',
|
||||
\ ' :ChopsticksStatus check LSP setup',
|
||||
\ ])
|
||||
endif
|
||||
|
||||
call add(l:lines, ' ,cr run file')
|
||||
if l:has_previm
|
||||
call add(l:lines, ' ,mp markdown preview')
|
||||
endif
|
||||
call add(l:lines, ' ,mt table of contents')
|
||||
|
||||
if l:has_lint
|
||||
call extend(l:lines, [
|
||||
\ ' [e ]e ALE errors',
|
||||
\ ' ,af format on save',
|
||||
\ ])
|
||||
endif
|
||||
|
||||
call extend(l:lines, [
|
||||
\ '',
|
||||
\ ' ── edit ──────────────────',
|
||||
\ ' gc comment',
|
||||
\ ' ,S+2ch easymotion jump',
|
||||
\ ' cs"'' surround',
|
||||
\ ])
|
||||
|
||||
if l:has_undotree
|
||||
call add(l:lines, ' ,u undo tree')
|
||||
endif
|
||||
|
||||
call extend(l:lines, [
|
||||
\ ' ,y ,p clipboard y/p (v)',
|
||||
\ ' Alt+j/k move line (v)',
|
||||
\ ' ,* replace word (v)',
|
||||
\ ' ,F re-indent (v)',
|
||||
\ ' ,W strip trailing',
|
||||
\ '',
|
||||
\ ' ── git ───────────────────',
|
||||
\ ' ,gs status',
|
||||
\ ' ,gd diff',
|
||||
\ ' ,gb blame',
|
||||
\ ' ,gc commit',
|
||||
\ ' ,gL log graph',
|
||||
\ ' ,gC FZF commits',
|
||||
\ ' [x ]x conflict markers',
|
||||
\ '',
|
||||
\ ' ── windows ───────────────',
|
||||
\ ' Ctrl-hjkl windows',
|
||||
\ ' <C-w>hjkl native fallback',
|
||||
\ ' ,h ,l prev / next buf',
|
||||
\ ' ,bd close buffer',
|
||||
\ ' ,z maximize toggle',
|
||||
\ ' ,= ,- resize height',
|
||||
\ ' ,tv ,th terminal v / h',
|
||||
\ ' ]q [q next / prev qf',
|
||||
\ ' ,qo ,qc open / close qf',
|
||||
\ '',
|
||||
\ ' ── toggle ────────────────',
|
||||
\ ' F2 paste mode',
|
||||
\ ' F3 line numbers',
|
||||
\ ' F4 relative numbers',
|
||||
\ ' F6 invisible chars',
|
||||
\ ' ,ss spell check',
|
||||
\ '',
|
||||
\ ' ── survival ──────────────',
|
||||
\ ' ,w save',
|
||||
\ ' ,q quit',
|
||||
\ ' ,x save + quit',
|
||||
\ ' Esc exit insert',
|
||||
\ ' ,ec edit local config',
|
||||
\ ' ,ev edit vimrc',
|
||||
\ ' ,sv reload vimrc',
|
||||
\ ' :ChopsticksHelp full help',
|
||||
\ ' :ChopsticksConfig local config',
|
||||
\ ' :ChopsticksReload reload config',
|
||||
\ ' :ChopsticksTutor practice',
|
||||
\ ' :ChopsticksStatus health',
|
||||
\ ' :ChopsticksBeta release checklist',
|
||||
\ ' :ChopsticksBetaLog release notes',
|
||||
\ ' :ChopsticksBetaSession new release note',
|
||||
\ ])
|
||||
|
||||
call s:OpenCheatSheet(l:lines)
|
||||
endfunction
|
||||
command! ChopsticksCheatSheet call s:CheatSheet()
|
||||
nnoremap <silent> <leader>? :ChopsticksCheatSheet<CR>
|
||||
|
|
@ -94,22 +94,43 @@ set smartindent
|
|||
|
||||
" ── Leader ──────────────────────────────────────────────────────────────────
|
||||
|
||||
let mapleader = ","
|
||||
if g:chopsticks_space_keymaps
|
||||
let mapleader = "\<Space>"
|
||||
let maplocalleader = ","
|
||||
else
|
||||
let mapleader = ","
|
||||
endif
|
||||
|
||||
" ── Basic Keymaps ───────────────────────────────────────────────────────────
|
||||
|
||||
nnoremap <leader>w :w<cr>
|
||||
nnoremap <leader>q :q<cr>
|
||||
nnoremap <leader>x :x<cr>
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <leader>w :w<cr>
|
||||
nnoremap <leader>W :wa<cr>
|
||||
nnoremap <leader>q :q<cr>
|
||||
|
||||
nnoremap <silent> <leader><cr> :noh<cr>
|
||||
nnoremap <silent> <leader>uh :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>bd :Bclose<cr>
|
||||
nnoremap <leader>ba :bufdo bd<cr>
|
||||
nnoremap <leader>bo :%bd<bar>e#<bar>bd#<cr>
|
||||
nnoremap <leader>bn :bnext<cr>
|
||||
nnoremap <leader>bp :bprevious<cr>
|
||||
|
||||
nnoremap <leader>cd :lcd %:p:h<cr>:pwd<cr>
|
||||
nnoremap <leader>fd :lcd %:p:h<cr>:pwd<cr>
|
||||
else
|
||||
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>
|
||||
endif
|
||||
|
||||
nnoremap <leader>v `[v`]
|
||||
|
||||
|
|
@ -118,7 +139,11 @@ 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>
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <silent> <leader>us :setlocal spell!<CR>:echo 'Spell: ' . (&spell ? 'ON' : 'OFF')<CR>
|
||||
else
|
||||
nnoremap <silent> <leader>ss :setlocal spell!<CR>:echo 'Spell: ' . (&spell ? 'ON' : 'OFF')<CR>
|
||||
endif
|
||||
|
||||
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>
|
||||
|
|
@ -157,8 +182,15 @@ if has('clipboard')
|
|||
vnoremap <leader>P "+P
|
||||
endif
|
||||
|
||||
nnoremap <leader>qo :copen<CR>
|
||||
nnoremap <leader>qc :cclose<CR>
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <leader>xq :copen<CR>
|
||||
nnoremap <leader>xQ :cclose<CR>
|
||||
nnoremap <leader>xl :lopen<CR>
|
||||
nnoremap <leader>xL :lclose<CR>
|
||||
else
|
||||
nnoremap <leader>qo :copen<CR>
|
||||
nnoremap <leader>qc :cclose<CR>
|
||||
endif
|
||||
|
||||
augroup ChopstickResize
|
||||
autocmd!
|
||||
|
|
@ -193,7 +225,10 @@ endif
|
|||
set sessionoptions=blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal
|
||||
|
||||
if has("patch-8.1.0360")
|
||||
set diffopt=filler,internal,context:3,algorithm:histogram,indent-heuristic
|
||||
silent! set diffopt+=internal
|
||||
silent! set diffopt+=context:3
|
||||
silent! set diffopt+=algorithm:histogram
|
||||
silent! set diffopt+=indent-heuristic
|
||||
endif
|
||||
|
||||
" ── Format Options ──────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -6,16 +6,27 @@ 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)
|
||||
if g:chopsticks_space_keymaps
|
||||
" In the canonical layout, cl/cc cover native s/S substitute behavior;
|
||||
" s becomes the fastest screen-local jump entry.
|
||||
nmap s <Plug>(easymotion-overwin-f2)
|
||||
nmap <Leader>S <Plug>(easymotion-overwin-f2)
|
||||
else
|
||||
nmap <Leader>S <Plug>(easymotion-overwin-f2)
|
||||
nmap <Leader>j <Plug>(easymotion-j)
|
||||
nmap <Leader>k <Plug>(easymotion-k)
|
||||
endif
|
||||
endif
|
||||
|
||||
" ── UndoTree ────────────────────────────────────────────────────────────────
|
||||
|
||||
if exists('g:plugs["undotree"]')
|
||||
nnoremap <F5> :UndotreeToggle<CR>
|
||||
nnoremap <leader>u :UndotreeToggle<CR>
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <leader>U :UndotreeToggle<CR>
|
||||
else
|
||||
nnoremap <leader>u :UndotreeToggle<CR>
|
||||
endif
|
||||
endif
|
||||
|
||||
" ── Yank Highlight ──────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ if index(['minimal', 'engineer', 'full'], g:chopsticks_profile) < 0
|
|||
let g:chopsticks_profile = 'engineer'
|
||||
endif
|
||||
|
||||
let g:chopsticks_keymap_style = get(g:, 'chopsticks_keymap_style', 'space')
|
||||
if index(['classic', 'space'], g:chopsticks_keymap_style) < 0
|
||||
let g:chopsticks_keymap_style = 'space'
|
||||
endif
|
||||
let g:chopsticks_space_keymaps = g:chopsticks_keymap_style ==# 'space'
|
||||
|
||||
let s:profile_full = g:chopsticks_profile ==# 'full'
|
||||
let s:profile_minimal = g:chopsticks_profile ==# 'minimal'
|
||||
|
||||
|
|
@ -28,6 +34,8 @@ let g:chopsticks_enable_auto_pairs = get(g:,
|
|||
\ 'chopsticks_enable_auto_pairs', 0)
|
||||
let g:chopsticks_enable_terminal_keymaps = get(g:,
|
||||
\ 'chopsticks_enable_terminal_keymaps', 0)
|
||||
let g:chopsticks_enable_tmux_navigator = get(g:,
|
||||
\ 'chopsticks_enable_tmux_navigator', 0)
|
||||
|
||||
let g:chopsticks_markdown_lint = get(g:, 'chopsticks_markdown_lint',
|
||||
\ s:profile_full)
|
||||
|
|
|
|||
54
modules/files.vim
Normal file
54
modules/files.vim
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
" files.vim — file safety and large-file handling
|
||||
|
||||
function! s:MkNonExDir(file, buf)
|
||||
if empty(getbufvar(a:buf, '&buftype')) && a:file !~# '\v^\w+\:\/'
|
||||
let dir = fnamemodify(a:file, ':h')
|
||||
if !isdirectory(dir)
|
||||
call mkdir(dir, 'p')
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
augroup BWCCreateDir
|
||||
autocmd!
|
||||
autocmd BufWritePre *
|
||||
\ if !empty(expand('<afile>')) |
|
||||
\ call s:MkNonExDir(expand('<afile>'), +expand('<abuf>')) |
|
||||
\ endif
|
||||
augroup END
|
||||
|
||||
let g:LargeFile = get(g:, 'LargeFile', 1024 * 1024 * 10)
|
||||
let s:tty_large = g:is_tty ? 512000 : g:LargeFile
|
||||
|
||||
function! s:ApplyLargeFileSettings() abort
|
||||
if get(b:, 'chopsticks_large_file', 0)
|
||||
setlocal bufhidden=unload undolevels=-1 noswapfile
|
||||
let b:ale_enabled = 0
|
||||
if &l:syntax !=# ''
|
||||
setlocal syntax=
|
||||
endif
|
||||
elseif get(b:, 'chopsticks_tty_large_file', 0)
|
||||
if &l:syntax !=# ''
|
||||
setlocal syntax=
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:MarkLargeFile(file) abort
|
||||
if empty(a:file)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:fsize = getfsize(a:file)
|
||||
if l:fsize > g:LargeFile || l:fsize == -2
|
||||
let b:chopsticks_large_file = 1
|
||||
elseif g:is_tty && l:fsize > s:tty_large
|
||||
let b:chopsticks_tty_large_file = 1
|
||||
endif
|
||||
call s:ApplyLargeFileSettings()
|
||||
endfunction
|
||||
|
||||
augroup ChopstickLargeFile
|
||||
autocmd!
|
||||
autocmd BufReadPre * call s:MarkLargeFile(expand('<afile>'))
|
||||
autocmd BufReadPost,FileType,Syntax * call s:ApplyLargeFileSettings()
|
||||
augroup END
|
||||
|
|
@ -14,11 +14,13 @@ let g:gitgutter_sign_modified_removed = '~'
|
|||
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>
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <leader>gl :Git log --oneline --graph -20<CR>
|
||||
else
|
||||
nnoremap <leader>gL :Git log --oneline --graph -20<CR>
|
||||
endif
|
||||
endif
|
||||
|
||||
" ── Conflict Navigation ────────────────────────────────────────────────────
|
||||
|
|
|
|||
18
modules/help.vim
Normal file
18
modules/help.vim
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
" help.vim — native Vim help entrypoint
|
||||
|
||||
function! s:OpenHelp() abort
|
||||
let l:doc = g:chopsticks_dir . '/doc'
|
||||
if isdirectory(l:doc)
|
||||
silent! execute 'helptags ' . fnameescape(l:doc)
|
||||
endif
|
||||
|
||||
try
|
||||
help chopsticks
|
||||
catch /^Vim\%((\a\+)\)\=:E149/
|
||||
echohl WarningMsg
|
||||
echom 'chopsticks help tags are missing; run :helptags ' . l:doc
|
||||
echohl None
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
command! ChopsticksHelp call s:OpenHelp()
|
||||
|
|
@ -14,7 +14,16 @@ let g:vim_markdown_follow_anchor = 1
|
|||
let g:vim_markdown_new_list_item_indent = 2
|
||||
let g:vim_markdown_strikethrough = 1
|
||||
|
||||
if exists('g:plugs["vim-markdown"]')
|
||||
function! s:MarkdownKeymaps() abort
|
||||
if exists('g:plugs["vim-markdown"]')
|
||||
nnoremap <buffer> <localleader>mt :Toc<CR>
|
||||
endif
|
||||
if exists('g:plugs["previm"]')
|
||||
nnoremap <buffer> <localleader>mp :PrevimOpen<CR>
|
||||
endif
|
||||
endfunction
|
||||
|
||||
if exists('g:plugs["vim-markdown"]') && !g:chopsticks_space_keymaps
|
||||
nnoremap <leader>mt :Toc<CR>
|
||||
endif
|
||||
|
||||
|
|
@ -24,7 +33,7 @@ elseif executable('xdg-open')
|
|||
let g:previm_open_cmd = 'xdg-open'
|
||||
endif
|
||||
let g:previm_enable_realtime = get(g:, 'previm_enable_realtime', 0)
|
||||
if exists('g:plugs["previm"]')
|
||||
if exists('g:plugs["previm"]') && !g:chopsticks_space_keymaps
|
||||
nnoremap <leader>mp :PrevimOpen<CR>
|
||||
endif
|
||||
|
||||
|
|
@ -78,6 +87,7 @@ augroup ChopstickFiletype
|
|||
autocmd FileType yaml
|
||||
\ setlocal expandtab shiftwidth=2 tabstop=2
|
||||
autocmd FileType markdown call s:MarkdownDefaults()
|
||||
autocmd FileType markdown if g:chopsticks_space_keymaps | call s:MarkdownKeymaps() | endif
|
||||
autocmd FileType sh
|
||||
\ setlocal expandtab shiftwidth=2 tabstop=2 textwidth=80
|
||||
autocmd FileType make
|
||||
|
|
|
|||
|
|
@ -64,7 +64,13 @@ 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>
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <silent> <leader>xd :ALEDetail<cr>
|
||||
nnoremap <silent> <leader>uf :let g:ale_fix_on_save = !g:ale_fix_on_save
|
||||
\ <bar> echo 'Format on save: ' . (g:ale_fix_on_save ? 'ON' : 'OFF')<cr>
|
||||
else
|
||||
nnoremap <silent> <leader>aD :ALEDetail<cr>
|
||||
nnoremap <silent> <leader>af :let g:ale_fix_on_save = !g:ale_fix_on_save
|
||||
\ <bar> echo 'Format on save: ' . (g:ale_fix_on_save ? 'ON' : 'OFF')<cr>
|
||||
endif
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -65,23 +65,42 @@ function! s:on_lsp_buffer_enabled() abort
|
|||
setlocal signcolumn=yes
|
||||
endif
|
||||
|
||||
nmap <buffer> <leader>dd <plug>(lsp-definition)
|
||||
nmap <buffer> <leader>dt <plug>(lsp-type-definition)
|
||||
nmap <buffer> <leader>di <plug>(lsp-implementation)
|
||||
nmap <buffer> <leader>dr <plug>(lsp-references)
|
||||
nmap <buffer> <leader>dp <plug>(lsp-previous-diagnostic)
|
||||
nmap <buffer> <leader>dn <plug>(lsp-next-diagnostic)
|
||||
if g:chopsticks_space_keymaps
|
||||
nmap <buffer> gd <plug>(lsp-definition)
|
||||
nmap <buffer> gr <plug>(lsp-references)
|
||||
nmap <buffer> gI <plug>(lsp-implementation)
|
||||
nmap <buffer> gy <plug>(lsp-type-definition)
|
||||
nmap <buffer> K <plug>(lsp-hover)
|
||||
nmap <buffer> [d <plug>(lsp-previous-diagnostic)
|
||||
nmap <buffer> ]d <plug>(lsp-next-diagnostic)
|
||||
|
||||
nmap <buffer> <leader>dk <plug>(lsp-hover)
|
||||
nmap <buffer> <leader>ca <plug>(lsp-code-action)
|
||||
nmap <buffer> <leader>cr <plug>(lsp-rename)
|
||||
nmap <buffer> <leader>cf <plug>(lsp-document-format)
|
||||
xmap <buffer> <leader>cf <plug>(lsp-document-range-format)
|
||||
|
||||
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)
|
||||
nnoremap <buffer> <leader>ci :LspStatus<CR>
|
||||
nmap <buffer> <leader>co <plug>(lsp-document-symbol-search)
|
||||
nmap <buffer> <leader>cS <plug>(lsp-workspace-symbol-search)
|
||||
else
|
||||
nmap <buffer> <leader>dd <plug>(lsp-definition)
|
||||
nmap <buffer> <leader>dt <plug>(lsp-type-definition)
|
||||
nmap <buffer> <leader>di <plug>(lsp-implementation)
|
||||
nmap <buffer> <leader>dr <plug>(lsp-references)
|
||||
nmap <buffer> <leader>dp <plug>(lsp-previous-diagnostic)
|
||||
nmap <buffer> <leader>dn <plug>(lsp-next-diagnostic)
|
||||
|
||||
nmap <buffer> <leader>o <plug>(lsp-document-symbol-search)
|
||||
nmap <buffer> <leader>ws <plug>(lsp-workspace-symbol-search)
|
||||
nmap <buffer> <leader>cD <plug>(lsp-document-diagnostics)
|
||||
nmap <buffer> <leader>dk <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)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
augroup lsp_install
|
||||
|
|
|
|||
|
|
@ -28,12 +28,29 @@ function! s:ToggleSidebar(...) abort
|
|||
wincmd p
|
||||
endfunction
|
||||
|
||||
function! s:NavigateWindow(direction) abort
|
||||
execute 'wincmd ' . a:direction
|
||||
endfunction
|
||||
|
||||
nnoremap <silent> <C-h> :<C-U>call <SID>NavigateWindow('h')<CR>
|
||||
nnoremap <silent> <C-j> :<C-U>call <SID>NavigateWindow('j')<CR>
|
||||
nnoremap <silent> <C-k> :<C-U>call <SID>NavigateWindow('k')<CR>
|
||||
nnoremap <silent> <C-l> :<C-U>call <SID>NavigateWindow('l')<CR>
|
||||
|
||||
nnoremap <silent> <leader>e :call <SID>ToggleSidebar()<CR>
|
||||
nnoremap <silent> <leader>E :call <SID>ToggleSidebar(expand('%:p:h'))<CR>
|
||||
|
||||
function! s:NetrwKeymaps() abort
|
||||
setlocal bufhidden=wipe
|
||||
nnoremap <buffer> <silent> <C-h> :<C-U>call <SID>NavigateWindow('h')<CR>
|
||||
nnoremap <buffer> <silent> <C-j> :<C-U>call <SID>NavigateWindow('j')<CR>
|
||||
nnoremap <buffer> <silent> <C-k> :<C-U>call <SID>NavigateWindow('k')<CR>
|
||||
nnoremap <buffer> <silent> <C-l> :<C-U>call <SID>NavigateWindow('l')<CR>
|
||||
endfunction
|
||||
|
||||
augroup ChopstickNetrw
|
||||
autocmd!
|
||||
autocmd FileType netrw setlocal bufhidden=wipe
|
||||
autocmd FileType netrw call s:NetrwKeymaps()
|
||||
augroup END
|
||||
|
||||
" ── FZF ─────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -47,21 +64,42 @@ function! s:SmartFiles() abort
|
|||
endfunction
|
||||
|
||||
if exists('g:plugs["fzf.vim"]')
|
||||
nnoremap <leader>ff :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>
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <leader><Space> :call <SID>SmartFiles()<CR>
|
||||
nnoremap <leader>, :Buffers<CR>
|
||||
nnoremap <leader>/ :Rg<CR>
|
||||
nnoremap <leader>ff :call <SID>SmartFiles()<CR>
|
||||
nnoremap <leader>fb :Buffers<CR>
|
||||
nnoremap <leader>fg :GFiles<CR>
|
||||
nnoremap <leader>fr :History<CR>
|
||||
nnoremap <leader>fl :BLines<CR>
|
||||
nnoremap <leader>fL :Lines<CR>
|
||||
nnoremap <leader>s/ :History/<CR>
|
||||
nnoremap <leader>s: :History:<CR>
|
||||
nnoremap <leader>sc :Commands<CR>
|
||||
nnoremap <leader>sm :Marks<CR>
|
||||
nnoremap <leader>sg :Rg<CR>
|
||||
nnoremap <leader>sw :RgWord<CR>
|
||||
nnoremap <leader>st :Tags<CR>
|
||||
nnoremap <leader>gC :Commits<CR>
|
||||
nnoremap <leader>gB :BCommits<CR>
|
||||
else
|
||||
nnoremap <leader>ff :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
|
||||
endif
|
||||
|
||||
let g:fzf_layout = { 'down': '40%' }
|
||||
|
|
@ -99,18 +137,27 @@ function! s:ToggleMaximize() abort
|
|||
echo 'Window: MAXIMIZED'
|
||||
endif
|
||||
endfunction
|
||||
nnoremap <silent> <leader>z :call <SID>ToggleMaximize()<CR>
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <silent> <leader>z :call <SID>ToggleMaximize()<CR>
|
||||
else
|
||||
nnoremap <silent> <leader>z :call <SID>ToggleMaximize()<CR>
|
||||
endif
|
||||
|
||||
" ── Terminal ────────────────────────────────────────────────────────────────
|
||||
|
||||
if has('terminal')
|
||||
nnoremap <leader>tv :terminal<CR>
|
||||
nnoremap <leader>th :terminal ++rows=10<CR>
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <leader>tt :terminal<CR>
|
||||
nnoremap <leader>th :terminal ++rows=10<CR>
|
||||
else
|
||||
nnoremap <leader>tv :terminal<CR>
|
||||
nnoremap <leader>th :terminal ++rows=10<CR>
|
||||
endif
|
||||
if g:chopsticks_enable_terminal_keymaps
|
||||
tnoremap <Esc><Esc> <C-\><C-n>
|
||||
tnoremap <C-h> <C-\><C-n><C-w>h
|
||||
tnoremap <C-j> <C-\><C-n><C-w>j
|
||||
tnoremap <C-k> <C-\><C-n><C-w>k
|
||||
tnoremap <C-l> <C-\><C-n><C-w>l
|
||||
tnoremap <C-h> <C-\><C-n>:<C-U>call <SID>NavigateWindow('h')<CR>
|
||||
tnoremap <C-j> <C-\><C-n>:<C-U>call <SID>NavigateWindow('j')<CR>
|
||||
tnoremap <C-k> <C-\><C-n>:<C-U>call <SID>NavigateWindow('k')<CR>
|
||||
tnoremap <C-l> <C-\><C-n>:<C-U>call <SID>NavigateWindow('l')<CR>
|
||||
endif
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ if g:chopsticks_enable_ui_extras
|
|||
Plug 'mhinz/vim-startify'
|
||||
endif
|
||||
Plug 'lifepillar/vim-solarized8'
|
||||
if !empty($TMUX)
|
||||
if g:chopsticks_enable_tmux_navigator && !empty($TMUX)
|
||||
Plug 'christoomey/vim-tmux-navigator'
|
||||
endif
|
||||
|
||||
|
|
|
|||
10
modules/quickfix.vim
Normal file
10
modules/quickfix.vim
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
" quickfix.vim — quickfix and location-list helpers
|
||||
|
||||
augroup ChopstickQF
|
||||
autocmd!
|
||||
autocmd QuickFixCmdPost [^l]* cwindow
|
||||
autocmd QuickFixCmdPost l* lwindow
|
||||
augroup END
|
||||
|
||||
nnoremap <silent> ]q :cnext<CR>
|
||||
nnoremap <silent> [q :cprev<CR>
|
||||
28
modules/runner.vim
Normal file
28
modules/runner.vim
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
" runner.vim — run the current file by filetype
|
||||
|
||||
function! s:RunFile() abort
|
||||
write
|
||||
let l:ft = &filetype
|
||||
let l:file = shellescape(expand('%:p'))
|
||||
if l:ft ==# 'python' | execute '!python3 ' . l:file
|
||||
elseif l:ft ==# 'javascript' | execute '!node ' . l:file
|
||||
elseif l:ft ==# 'typescript' | execute '!npx ts-node ' . l:file
|
||||
elseif l:ft ==# 'go' | execute '!go run ' . l:file
|
||||
elseif l:ft ==# 'rust' | execute '!cargo run'
|
||||
elseif l:ft ==# 'sh' | execute '!bash ' . l:file
|
||||
elseif l:ft ==# 'c'
|
||||
let l:out_path = tempname()
|
||||
let l:out = shellescape(l:out_path)
|
||||
execute '!gcc -o ' . l:out . ' ' . l:file . ' && ' . l:out
|
||||
call delete(l:out_path)
|
||||
elseif l:ft ==# 'lua' | execute '!lua ' . l:file
|
||||
elseif l:ft ==# 'ruby' | execute '!ruby ' . l:file
|
||||
elseif l:ft ==# 'perl' | execute '!perl ' . l:file
|
||||
else | echo 'No runner for filetype: ' . l:ft
|
||||
endif
|
||||
endfunction
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <leader>rr :call <SID>RunFile()<CR>
|
||||
else
|
||||
nnoremap <leader>cr :call <SID>RunFile()<CR>
|
||||
endif
|
||||
199
modules/status.vim
Normal file
199
modules/status.vim
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
" status.vim — health diagnostics
|
||||
|
||||
function! s:Check(name, cmd) abort
|
||||
return executable(a:cmd) ? ' OK ' . a:name : ' -- ' . a:name . ' (missing: ' . a:cmd . ')'
|
||||
endfunction
|
||||
|
||||
function! s:Off(name, reason) abort
|
||||
return ' off ' . a:name . ' (' . a:reason . ')'
|
||||
endfunction
|
||||
|
||||
function! s:PlugDir(name) abort
|
||||
if !exists('g:plugs') || !has_key(g:plugs, a:name)
|
||||
return ''
|
||||
endif
|
||||
return fnamemodify(get(g:plugs[a:name], 'dir', ''), ':p')
|
||||
endfunction
|
||||
|
||||
function! s:PlugInstalled(name) abort
|
||||
let l:dir = s:PlugDir(a:name)
|
||||
return !empty(l:dir) && isdirectory(l:dir)
|
||||
endfunction
|
||||
|
||||
function! s:LspStackIssue() abort
|
||||
if !get(g:, 'chopsticks_enable_lsp', 1)
|
||||
return 'LSP disabled by profile'
|
||||
endif
|
||||
if empty(s:PlugDir('vim-lsp'))
|
||||
return 'vim-lsp not declared by this profile'
|
||||
endif
|
||||
if !s:PlugInstalled('vim-lsp')
|
||||
return 'vim-lsp not installed; run :PlugInstall'
|
||||
endif
|
||||
if empty(s:PlugDir('vim-lsp-settings'))
|
||||
return 'vim-lsp-settings not declared by this profile'
|
||||
endif
|
||||
if !s:PlugInstalled('vim-lsp-settings')
|
||||
return 'vim-lsp-settings not installed; run :PlugInstall'
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! s:LspStackCheck() abort
|
||||
let l:issue = s:LspStackIssue()
|
||||
if l:issue ==# 'LSP disabled by profile'
|
||||
return s:Off('vim-lsp stack', l:issue)
|
||||
endif
|
||||
if !empty(l:issue)
|
||||
return ' -- vim-lsp stack (' . l:issue . ')'
|
||||
endif
|
||||
if exists(':LspStatus') == 2 || exists(':LspInstallServer') == 2
|
||||
return ' OK vim-lsp stack (installed)'
|
||||
endif
|
||||
return ' OK vim-lsp stack (installed; not loaded yet)'
|
||||
endfunction
|
||||
|
||||
function! s:LspCheck(ft, server) abort
|
||||
let l:issue = s:LspStackIssue()
|
||||
if l:issue ==# 'LSP disabled by profile'
|
||||
return s:Off(a:ft, l:issue)
|
||||
endif
|
||||
if !empty(l:issue)
|
||||
return ' -- ' . a:ft . ' (' . l:issue . ')'
|
||||
endif
|
||||
let l:dir = expand('~/.local/share/vim-lsp-settings/servers/' . a:server)
|
||||
if isdirectory(l:dir)
|
||||
return ' OK ' . a:ft . ' (' . a:server . ')'
|
||||
endif
|
||||
return ' -- ' . a:ft . ' (:LspInstallServer in a ' . a:ft . ' file)'
|
||||
endfunction
|
||||
|
||||
function! s:BetaLogPath() abort
|
||||
let l:configured = get(g:, 'chopsticks_beta_log', '')
|
||||
if !empty(l:configured)
|
||||
return expand(l:configured)
|
||||
endif
|
||||
|
||||
let l:xdg = !empty($XDG_CONFIG_HOME) && $XDG_CONFIG_HOME =~# '^/'
|
||||
\ ? $XDG_CONFIG_HOME
|
||||
\ : '~/.config'
|
||||
return expand(l:xdg . '/chopsticks-2.3.0.md')
|
||||
endfunction
|
||||
|
||||
function! s:LocalConfigPath() abort
|
||||
let l:xdg = !empty($XDG_CONFIG_HOME) && $XDG_CONFIG_HOME =~# '^/'
|
||||
\ ? $XDG_CONFIG_HOME
|
||||
\ : '~/.config'
|
||||
return expand(get(g:, 'chopsticks_resolved_local_config',
|
||||
\ get(g:, 'chopsticks_local_config', l:xdg . '/chopsticks.vim')))
|
||||
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, ' help :ChopsticksHelp :ChopsticksTutor SPC ?')
|
||||
call add(l:lines, ' config ' . s:LocalConfigPath())
|
||||
call add(l:lines, ' commands :ChopsticksConfig :ChopsticksReload')
|
||||
call add(l:lines, '')
|
||||
|
||||
if !empty(get(g:, 'chopsticks_beta_label', ''))
|
||||
call add(l:lines, '── release candidate ──')
|
||||
call add(l:lines, ' candidate ' . g:chopsticks_beta_label)
|
||||
call add(l:lines, ' keymap ' . (get(g:, 'chopsticks_space_keymaps', 0) ? 'space' : 'classic'))
|
||||
call add(l:lines, ' log ' . s:BetaLogPath())
|
||||
call add(l:lines, ' commands :ChopsticksBeta :ChopsticksBetaLog')
|
||||
call add(l:lines, ' :ChopsticksBetaSession')
|
||||
call add(l:lines, '')
|
||||
endif
|
||||
|
||||
call add(l:lines, '── system tools ──')
|
||||
call add(l:lines, s:Check('fzf', 'fzf'))
|
||||
call add(l:lines, s:Check('ripgrep', 'rg'))
|
||||
call add(l:lines, s:Check('git', 'git'))
|
||||
call add(l:lines, s:Check('curl', 'curl'))
|
||||
call add(l:lines, s:Check('node', 'node'))
|
||||
call add(l:lines, s:Check('python3', 'python3'))
|
||||
call add(l:lines, s:Check('go', 'go'))
|
||||
call add(l:lines, '')
|
||||
|
||||
call add(l:lines, '── lsp servers ── (:LspInstallServer to install)')
|
||||
call add(l:lines, s:LspStackCheck())
|
||||
if get(g:, 'chopsticks_enable_lsp', 1)
|
||||
call add(l:lines, ' LSP actions are buffer-local and start after a server attaches.')
|
||||
call add(l:lines, ' Missing one? Open that filetype and run :LspInstallServer once.')
|
||||
endif
|
||||
call add(l:lines, s:LspCheck('python', 'pylsp'))
|
||||
call add(l:lines, s:LspCheck('go', 'gopls'))
|
||||
call add(l:lines, s:LspCheck('rust', 'rust-analyzer'))
|
||||
call add(l:lines, s:LspCheck('typescript', 'typescript-language-server'))
|
||||
call add(l:lines, s:LspCheck('c/c++', 'clangd'))
|
||||
call add(l:lines, s:LspCheck('bash', 'bash-language-server'))
|
||||
call add(l:lines, s:LspCheck('html', 'vscode-html-language-server'))
|
||||
call add(l:lines, s:LspCheck('json', 'vscode-json-language-server'))
|
||||
call add(l:lines, s:LspCheck('yaml', 'yaml-language-server'))
|
||||
call add(l:lines, s:LspCheck('markdown', 'marksman'))
|
||||
call add(l:lines, s:LspCheck('sql', 'sqls'))
|
||||
call add(l:lines, '')
|
||||
|
||||
call add(l:lines, '── linters ──')
|
||||
if get(g:, 'chopsticks_enable_lint', 1)
|
||||
call add(l:lines, s:Check('flake8 (python)', 'flake8'))
|
||||
call add(l:lines, s:Check('pylint (python)', 'pylint'))
|
||||
call add(l:lines, s:Check('eslint (js/ts)', 'eslint'))
|
||||
call add(l:lines, s:Check('staticcheck (go)', 'staticcheck'))
|
||||
call add(l:lines, s:Check('shellcheck (sh)', 'shellcheck'))
|
||||
call add(l:lines, s:Check('yamllint (yaml)', 'yamllint'))
|
||||
call add(l:lines, s:Check('hadolint (docker)', 'hadolint'))
|
||||
if get(g:, 'chopsticks_markdown_lint', 0)
|
||||
call add(l:lines, s:Check('markdownlint (md)', 'markdownlint'))
|
||||
else
|
||||
call add(l:lines, s:Off('markdownlint (md)', 'disabled by default'))
|
||||
endif
|
||||
else
|
||||
call add(l:lines, s:Off('ALE linters', 'lint disabled by profile'))
|
||||
endif
|
||||
call add(l:lines, '')
|
||||
|
||||
call add(l:lines, '── formatters ── (format-on-save is ' . (get(g:, 'ale_fix_on_save', 0) ? 'ON' : 'OFF') . ')')
|
||||
if get(g:, 'chopsticks_enable_lint', 1)
|
||||
call add(l:lines, s:Check('black (python)', 'black'))
|
||||
call add(l:lines, s:Check('isort (python)', 'isort'))
|
||||
call add(l:lines, s:Check('prettier (js/ts/json)', 'prettier'))
|
||||
if get(g:, 'chopsticks_markdown_format_on_save', 0)
|
||||
call add(l:lines, s:Check('prettier (md)', 'prettier'))
|
||||
else
|
||||
call add(l:lines, s:Off('prettier (md)', 'disabled by default'))
|
||||
endif
|
||||
call add(l:lines, s:Check('goimports (go)', 'goimports'))
|
||||
call add(l:lines, s:Check('rustfmt (rust)', 'rustfmt'))
|
||||
call add(l:lines, s:Check('clang-format (c)', 'clang-format'))
|
||||
else
|
||||
call add(l:lines, s:Off('ALE formatters', 'lint disabled by profile'))
|
||||
endif
|
||||
call add(l:lines, '')
|
||||
|
||||
let l:ok = len(filter(copy(l:lines), 'v:val =~# " OK "'))
|
||||
let l:miss = len(filter(copy(l:lines), 'v:val =~# " -- "'))
|
||||
call add(l:lines, repeat('─', 50))
|
||||
call add(l:lines, ' ' . l:ok . ' ready, ' . l:miss . ' missing')
|
||||
call add(l:lines, '')
|
||||
call add(l:lines, ' Install missing tools with ./install.sh')
|
||||
if get(g:, 'chopsticks_enable_lsp', 1)
|
||||
call add(l:lines, ' Install LSP servers with :LspInstallServer')
|
||||
endif
|
||||
|
||||
let l:name = '__ChopsticksStatus__'
|
||||
if bufwinnr(l:name) > 0
|
||||
execute bufwinnr(l:name) . 'wincmd w | bd'
|
||||
endif
|
||||
execute 'botright new ' . l:name
|
||||
resize 45
|
||||
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile
|
||||
setlocal nowrap nonumber norelativenumber signcolumn=no
|
||||
call setline(1, l:lines)
|
||||
setlocal nomodifiable readonly
|
||||
nnoremap <buffer> <silent> q :bd<CR>
|
||||
endfunction
|
||||
command! ChopsticksStatus call s:ChopsticksStatus()
|
||||
|
|
@ -1,445 +1,4 @@
|
|||
" 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 ──────────────────────────────────────────────────────────────
|
||||
|
||||
if get(g:, 'chopsticks_enable_reindent_file', 0)
|
||||
nnoremap <leader>F gg=G``
|
||||
endif
|
||||
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'
|
||||
let l:out_path = tempname()
|
||||
let l:out = shellescape(l:out_path)
|
||||
execute '!gcc -o ' . l:out . ' ' . l:file . ' && ' . l:out
|
||||
call delete(l:out_path)
|
||||
elseif l:ft ==# 'lua' | execute '!lua ' . l:file
|
||||
elseif l:ft ==# 'ruby' | execute '!ruby ' . l:file
|
||||
elseif l:ft ==# 'perl' | execute '!perl ' . l:file
|
||||
else | echo 'No runner for filetype: ' . l:ft
|
||||
endif
|
||||
endfunction
|
||||
nnoremap <leader>cr :call <SID>RunFile()<CR>
|
||||
|
||||
" ── Sudo Save ───────────────────────────────────────────────────────────────
|
||||
|
||||
if get(g:, 'chopsticks_enable_sudo_save_bang', 0)
|
||||
cnoremap w!! w !sudo tee > /dev/null %
|
||||
endif
|
||||
|
||||
" ── QuickFix ────────────────────────────────────────────────────────────────
|
||||
|
||||
augroup ChopstickQF
|
||||
autocmd!
|
||||
autocmd QuickFixCmdPost [^l]* cwindow
|
||||
autocmd QuickFixCmdPost l* lwindow
|
||||
augroup END
|
||||
|
||||
nnoremap <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:PlugDir(name) abort
|
||||
if !exists('g:plugs') || !has_key(g:plugs, a:name)
|
||||
return ''
|
||||
endif
|
||||
return fnamemodify(get(g:plugs[a:name], 'dir', ''), ':p')
|
||||
endfunction
|
||||
|
||||
function! s:PlugInstalled(name) abort
|
||||
let l:dir = s:PlugDir(a:name)
|
||||
return !empty(l:dir) && isdirectory(l:dir)
|
||||
endfunction
|
||||
|
||||
function! s:LspStackIssue() abort
|
||||
if !get(g:, 'chopsticks_enable_lsp', 1)
|
||||
return 'LSP disabled by profile'
|
||||
endif
|
||||
if empty(s:PlugDir('vim-lsp'))
|
||||
return 'vim-lsp not declared by this profile'
|
||||
endif
|
||||
if !s:PlugInstalled('vim-lsp')
|
||||
return 'vim-lsp not installed; run :PlugInstall'
|
||||
endif
|
||||
if empty(s:PlugDir('vim-lsp-settings'))
|
||||
return 'vim-lsp-settings not declared by this profile'
|
||||
endif
|
||||
if !s:PlugInstalled('vim-lsp-settings')
|
||||
return 'vim-lsp-settings not installed; run :PlugInstall'
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! s:LspStackCheck() abort
|
||||
let l:issue = s:LspStackIssue()
|
||||
if l:issue ==# 'LSP disabled by profile'
|
||||
return s:Off('vim-lsp stack', l:issue)
|
||||
endif
|
||||
if !empty(l:issue)
|
||||
return ' -- vim-lsp stack (' . l:issue . ')'
|
||||
endif
|
||||
if exists(':LspStatus') == 2 || exists(':LspInstallServer') == 2
|
||||
return ' OK vim-lsp stack (installed)'
|
||||
endif
|
||||
return ' OK vim-lsp stack (installed; not loaded yet)'
|
||||
endfunction
|
||||
|
||||
function! s:LspCheck(ft, server) abort
|
||||
let l:issue = s:LspStackIssue()
|
||||
if l:issue ==# 'LSP disabled by profile'
|
||||
return s:Off(a:ft, l:issue)
|
||||
endif
|
||||
if !empty(l:issue)
|
||||
return ' -- ' . a:ft . ' (' . l:issue . ')'
|
||||
endif
|
||||
let l:dir = expand('~/.local/share/vim-lsp-settings/servers/' . a:server)
|
||||
if isdirectory(l:dir)
|
||||
return ' OK ' . a:ft . ' (' . a:server . ')'
|
||||
endif
|
||||
return ' -- ' . a:ft . ' (:LspInstallServer in a ' . a:ft . ' file)'
|
||||
endfunction
|
||||
|
||||
function! s:ChopsticksStatus() abort
|
||||
let l:lines = []
|
||||
call add(l:lines, 'chopsticks status')
|
||||
call add(l:lines, repeat('─', 50))
|
||||
call add(l:lines, '')
|
||||
|
||||
call add(l:lines, '── system tools ──')
|
||||
call add(l:lines, s:Check('fzf', 'fzf'))
|
||||
call add(l:lines, s:Check('ripgrep', 'rg'))
|
||||
call add(l:lines, s:Check('git', 'git'))
|
||||
call add(l:lines, s:Check('curl', 'curl'))
|
||||
call add(l:lines, s:Check('node', 'node'))
|
||||
call add(l:lines, s:Check('python3', 'python3'))
|
||||
call add(l:lines, s:Check('go', 'go'))
|
||||
call add(l:lines, '')
|
||||
|
||||
call add(l:lines, '── lsp servers ── (:LspInstallServer to install)')
|
||||
call add(l:lines, s:LspStackCheck())
|
||||
if get(g:, 'chopsticks_enable_lsp', 1)
|
||||
call add(l:lines, ' LSP actions are buffer-local and start after a server attaches.')
|
||||
call add(l:lines, ' Missing one? Open that filetype and run :LspInstallServer once.')
|
||||
endif
|
||||
call add(l:lines, s:LspCheck('python', 'pylsp'))
|
||||
call add(l:lines, s:LspCheck('go', 'gopls'))
|
||||
call add(l:lines, s:LspCheck('rust', 'rust-analyzer'))
|
||||
call add(l:lines, s:LspCheck('typescript', 'typescript-language-server'))
|
||||
call add(l:lines, s:LspCheck('c/c++', 'clangd'))
|
||||
call add(l:lines, s:LspCheck('bash', 'bash-language-server'))
|
||||
call add(l:lines, s:LspCheck('html', 'vscode-html-language-server'))
|
||||
call add(l:lines, s:LspCheck('json', 'vscode-json-language-server'))
|
||||
call add(l:lines, s:LspCheck('yaml', 'yaml-language-server'))
|
||||
call add(l:lines, s:LspCheck('markdown', 'marksman'))
|
||||
call add(l:lines, s:LspCheck('sql', 'sqls'))
|
||||
call add(l:lines, '')
|
||||
|
||||
call add(l:lines, '── linters ──')
|
||||
if get(g:, 'chopsticks_enable_lint', 1)
|
||||
call add(l:lines, s:Check('flake8 (python)', 'flake8'))
|
||||
call add(l:lines, s:Check('pylint (python)', 'pylint'))
|
||||
call add(l:lines, s:Check('eslint (js/ts)', 'eslint'))
|
||||
call add(l:lines, s:Check('staticcheck (go)', 'staticcheck'))
|
||||
call add(l:lines, s:Check('shellcheck (sh)', 'shellcheck'))
|
||||
call add(l:lines, s:Check('yamllint (yaml)', 'yamllint'))
|
||||
call add(l:lines, s:Check('hadolint (docker)', 'hadolint'))
|
||||
if get(g:, 'chopsticks_markdown_lint', 0)
|
||||
call add(l:lines, s:Check('markdownlint (md)', 'markdownlint'))
|
||||
else
|
||||
call add(l:lines, s:Off('markdownlint (md)', 'disabled by default'))
|
||||
endif
|
||||
else
|
||||
call add(l:lines, s:Off('ALE linters', 'lint disabled by profile'))
|
||||
endif
|
||||
call add(l:lines, '')
|
||||
|
||||
call add(l:lines, '── formatters ── (format-on-save is ' . (get(g:, 'ale_fix_on_save', 0) ? 'ON' : 'OFF') . ')')
|
||||
if get(g:, 'chopsticks_enable_lint', 1)
|
||||
call add(l:lines, s:Check('black (python)', 'black'))
|
||||
call add(l:lines, s:Check('isort (python)', 'isort'))
|
||||
call add(l:lines, s:Check('prettier (js/ts/json)', 'prettier'))
|
||||
if get(g:, 'chopsticks_markdown_format_on_save', 0)
|
||||
call add(l:lines, s:Check('prettier (md)', 'prettier'))
|
||||
else
|
||||
call add(l:lines, s:Off('prettier (md)', 'disabled by default'))
|
||||
endif
|
||||
call add(l:lines, s:Check('goimports (go)', 'goimports'))
|
||||
call add(l:lines, s:Check('rustfmt (rust)', 'rustfmt'))
|
||||
call add(l:lines, s:Check('clang-format (c)', 'clang-format'))
|
||||
else
|
||||
call add(l:lines, s:Off('ALE formatters', 'lint disabled by profile'))
|
||||
endif
|
||||
call add(l:lines, '')
|
||||
|
||||
let l:ok = len(filter(copy(l:lines), 'v:val =~# " OK "'))
|
||||
let l:miss = len(filter(copy(l:lines), 'v:val =~# " -- "'))
|
||||
call add(l:lines, repeat('─', 50))
|
||||
call add(l:lines, ' ' . l:ok . ' ready, ' . l:miss . ' missing')
|
||||
call add(l:lines, '')
|
||||
call add(l:lines, ' Install missing tools with ./install.sh')
|
||||
if get(g:, 'chopsticks_enable_lsp', 1)
|
||||
call add(l:lines, ' Install LSP servers with :LspInstallServer')
|
||||
endif
|
||||
|
||||
let l:name = '__ChopsticksStatus__'
|
||||
if bufwinnr(l:name) > 0
|
||||
execute bufwinnr(l:name) . 'wincmd w | bd'
|
||||
endif
|
||||
execute 'botright new ' . l:name
|
||||
resize 45
|
||||
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile
|
||||
setlocal nowrap nonumber norelativenumber signcolumn=no
|
||||
call setline(1, l:lines)
|
||||
setlocal nomodifiable readonly
|
||||
nnoremap <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 ──────────────────',
|
||||
\ ' ,ff files',
|
||||
\ ' ,b buffers',
|
||||
\ ' ,rg grep project',
|
||||
\ ' ,rG grep word',
|
||||
\ ' ,e sidebar (cwd)',
|
||||
\ ' ,E sidebar (file dir)',
|
||||
\ ' ,, last file',
|
||||
\ ' ,fh recent files',
|
||||
\ ' ,fl lines in buffer',
|
||||
\ ' ,fc commands',
|
||||
\ ' ,fm marks',
|
||||
\ '',
|
||||
\ ' ── code ──────────────────',
|
||||
\ ]
|
||||
|
||||
if l:has_lsp
|
||||
call extend(l:lines, [
|
||||
\ ' ,dd definition',
|
||||
\ ' ,dt type definition',
|
||||
\ ' ,di implementation',
|
||||
\ ' ,dr references',
|
||||
\ ' ,dk hover docs',
|
||||
\ ' ,rn rename',
|
||||
\ ' ,ca code action',
|
||||
\ ' ,f format',
|
||||
\ ' ,o outline',
|
||||
\ ' ,dp ,dn LSP diagnostics',
|
||||
\ ' :LspInstallServer setup LSP',
|
||||
\ ' :ChopsticksStatus check LSP setup',
|
||||
\ ])
|
||||
endif
|
||||
|
||||
call add(l:lines, ' ,cr run file')
|
||||
if l:has_previm
|
||||
call add(l:lines, ' ,mp markdown preview')
|
||||
endif
|
||||
call add(l:lines, ' ,mt table of contents')
|
||||
|
||||
if l:has_lint
|
||||
call extend(l:lines, [
|
||||
\ ' [e ]e ALE errors',
|
||||
\ ' ,af format on save',
|
||||
\ ])
|
||||
endif
|
||||
|
||||
call extend(l:lines, [
|
||||
\ '',
|
||||
\ ' ── edit ──────────────────',
|
||||
\ ' gc comment',
|
||||
\ ' ,S+2ch easymotion jump',
|
||||
\ ' cs"'' surround',
|
||||
\ ])
|
||||
|
||||
if l:has_undotree
|
||||
call add(l:lines, ' ,u undo tree')
|
||||
endif
|
||||
|
||||
call extend(l:lines, [
|
||||
\ ' ,y ,p clipboard y/p (v)',
|
||||
\ ' Alt+j/k move line (v)',
|
||||
\ ' ,* replace word (v)',
|
||||
\ ' ,F re-indent (v)',
|
||||
\ ' ,W strip trailing (v)',
|
||||
\ '',
|
||||
\ ' ── git ───────────────────',
|
||||
\ ' ,gs status',
|
||||
\ ' ,gd diff',
|
||||
\ ' ,gb blame',
|
||||
\ ' ,gc commit',
|
||||
\ ' ,gp push',
|
||||
\ ' ,gl pull',
|
||||
\ ' ,gL log graph',
|
||||
\ ' ,gC FZF commits',
|
||||
\ ' [x ]x conflict markers',
|
||||
\ '',
|
||||
\ ' ── windows ───────────────',
|
||||
\ ' <C-w>hjkl navigate splits',
|
||||
\ ' ,h ,l prev / next buf',
|
||||
\ ' ,bd close buffer',
|
||||
\ ' ,z maximize toggle',
|
||||
\ ' ,= ,- resize height',
|
||||
\ ' ,tv ,th terminal v / h',
|
||||
\ ' ]q [q next / prev qf',
|
||||
\ ' ,qo ,qc open / close qf',
|
||||
\ '',
|
||||
\ ' ── toggle ────────────────',
|
||||
\ ' F2 paste mode',
|
||||
\ ' F3 line numbers',
|
||||
\ ' F4 relative numbers',
|
||||
\ ' F6 invisible chars',
|
||||
\ ' ,ss spell check',
|
||||
\ '',
|
||||
\ ' ── survival ──────────────',
|
||||
\ ' ,w save',
|
||||
\ ' ,q quit',
|
||||
\ ' ,x save + quit',
|
||||
\ ' Esc exit insert',
|
||||
\ ' ,ev edit vimrc',
|
||||
\ ' ,sv reload vimrc',
|
||||
\ ' :ChopsticksStatus health',
|
||||
\ ])
|
||||
|
||||
execute 'vertical botright new ' . l:name
|
||||
vertical resize 42
|
||||
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile
|
||||
setlocal nowrap nonumber norelativenumber signcolumn=no
|
||||
setlocal winfixwidth
|
||||
call setline(1, l:lines)
|
||||
setlocal nomodifiable readonly
|
||||
nnoremap <buffer> <silent> q :bd<CR>
|
||||
nnoremap <buffer> <silent> <leader>? :bd<CR>
|
||||
endfunction
|
||||
nnoremap <silent> <leader>? :call <SID>CheatSheet()<CR>
|
||||
" tools.vim — compatibility placeholder
|
||||
"
|
||||
" Tooling was split into granular modules:
|
||||
" buffers, utilities, files, runner, quickfix, status, cheatsheet, and tutor.
|
||||
|
|
|
|||
134
modules/tutor.vim
Normal file
134
modules/tutor.vim
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
" tutor.vim — guided practice for chopsticks keymaps
|
||||
|
||||
function! s:OpenTutor(lines) abort
|
||||
let l:name = '__ChopsticksTutor__'
|
||||
if bufwinnr(l:name) > 0
|
||||
execute bufwinnr(l:name) . 'wincmd w | bd'
|
||||
return 0
|
||||
endif
|
||||
|
||||
execute 'botright new ' . l:name
|
||||
resize 38
|
||||
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile
|
||||
setlocal nowrap nonumber norelativenumber signcolumn=no
|
||||
call setline(1, a:lines)
|
||||
setlocal nomodifiable readonly
|
||||
nnoremap <buffer> <silent> q :bd<CR>
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! s:ChopsticksTutor() abort
|
||||
if g:chopsticks_space_keymaps
|
||||
let l:lines = [
|
||||
\ ' chopsticks tutor q close',
|
||||
\ ' ───────────────────────────────',
|
||||
\ '',
|
||||
\ ' Goal: train one long-term project loop around Vim.',
|
||||
\ ' Keep Vim editing habits; standardize the surrounding work.',
|
||||
\ '',
|
||||
\ ' 1. trained loop',
|
||||
\ ' SPC SPC open a project file',
|
||||
\ ' s + 2 chars jump to visible text',
|
||||
\ ' gd / gr / K inspect definition / refs / docs',
|
||||
\ ' SPC rr run current file',
|
||||
\ ' SPC / grep project',
|
||||
\ ' SPC gs check git status',
|
||||
\ ' SPC ? active cheat sheet',
|
||||
\ '',
|
||||
\ ' 2. survival',
|
||||
\ ' Esc Normal mode',
|
||||
\ ' SPC w save',
|
||||
\ ' SPC q quit',
|
||||
\ ' :x / ZZ save and quit',
|
||||
\ ' SPC fc edit local config',
|
||||
\ ' SPC fV reload config',
|
||||
\ ' :ChopsticksHelp full help',
|
||||
\ ' :ChopsticksConfig local config',
|
||||
\ ' :ChopsticksReload reload config',
|
||||
\ ' :ChopsticksStatus health check',
|
||||
\ ' :ChopsticksBeta release checklist',
|
||||
\ ' :ChopsticksBetaLog release notes',
|
||||
\ ' :ChopsticksBetaSession new note',
|
||||
\ '',
|
||||
\ ' 3. find and switch',
|
||||
\ ' SPC SPC find files',
|
||||
\ ' SPC / grep project',
|
||||
\ ' SPC , buffers',
|
||||
\ ' SPC Tab alternate buffer',
|
||||
\ ' SPC e/E sidebar cwd / file dir',
|
||||
\ '',
|
||||
\ ' 4. jump and edit',
|
||||
\ ' s + 2 chars visible jump',
|
||||
\ ' SPC S same jump fallback',
|
||||
\ ' cl / cc native s / S substitute',
|
||||
\ ' gc comment',
|
||||
\ ' SPC U undo tree',
|
||||
\ '',
|
||||
\ ' 5. code loop',
|
||||
\ ' gd / gr / K definition / refs / docs',
|
||||
\ ' gI / gy implementation / type',
|
||||
\ ' [d ]d LSP diagnostics',
|
||||
\ ' SPC ca/cr/cf action / rename / format',
|
||||
\ ' SPC rr run current file',
|
||||
\ '',
|
||||
\ ' 6. git and windows',
|
||||
\ ' SPC gs/gd/gb status / diff / blame',
|
||||
\ ' SPC gl log graph',
|
||||
\ ' Ctrl-h/j/k/l split navigation',
|
||||
\ ' <C-w>hjkl native fallback',
|
||||
\ ' SPC e, Ctrl-h/l enter/leave sidebar',
|
||||
\ ' SPC z maximize split',
|
||||
\ '',
|
||||
\ ' daily drill',
|
||||
\ ' Repeat: SPC SPC, s, gd/K, edit, SPC rr, SPC /, SPC gs.',
|
||||
\ ]
|
||||
else
|
||||
let l:lines = [
|
||||
\ ' chopsticks tutor q close',
|
||||
\ ' ───────────────────────────────',
|
||||
\ '',
|
||||
\ ' Goal: train one long-term project loop around Vim.',
|
||||
\ '',
|
||||
\ ' classic layout',
|
||||
\ ' ,? active cheat sheet',
|
||||
\ ' ,w / ,x save / save and quit',
|
||||
\ ' ,ff find files',
|
||||
\ ' ,rg grep project',
|
||||
\ ' ,b buffers',
|
||||
\ ' ,, alternate buffer',
|
||||
\ '',
|
||||
\ ' code loop',
|
||||
\ ' ,dd / ,dr definition / refs',
|
||||
\ ' ,dk hover docs',
|
||||
\ ' ,ca / ,rn action / rename',
|
||||
\ ' ,f format',
|
||||
\ ' ,cr run current file',
|
||||
\ '',
|
||||
\ ' edit and git',
|
||||
\ ' ,S + 2 chars EasyMotion jump',
|
||||
\ ' gc comment',
|
||||
\ ' ,u undo tree',
|
||||
\ ' ,gs/,gd/,gb status / diff / blame',
|
||||
\ ' Ctrl-h/j/k/l split navigation',
|
||||
\ ' <C-w>hjkl native fallback',
|
||||
\ '',
|
||||
\ ' support',
|
||||
\ ' ,ec edit local config',
|
||||
\ ' ,sv reload config',
|
||||
\ ' :ChopsticksHelp full help',
|
||||
\ ' :ChopsticksConfig local config',
|
||||
\ ' :ChopsticksReload reload config',
|
||||
\ ' :ChopsticksStatus health check',
|
||||
\ ' :ChopsticksBeta release checklist',
|
||||
\ ' :ChopsticksBetaLog release notes',
|
||||
\ ' :ChopsticksBetaSession new note',
|
||||
\ ' README.md full reference',
|
||||
\ ' QUICKSTART.md 5-minute path',
|
||||
\ ]
|
||||
endif
|
||||
|
||||
if s:OpenTutor(l:lines)
|
||||
nnoremap <buffer> <silent> ? :ChopsticksCheatSheet<CR>
|
||||
endif
|
||||
endfunction
|
||||
command! ChopsticksTutor call s:ChopsticksTutor()
|
||||
108
modules/utilities.vim
Normal file
108
modules/utilities.vim
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
" utilities.vim — small editing and config helpers
|
||||
|
||||
function! s:LocalConfigPath() abort
|
||||
let l:xdg = !empty($XDG_CONFIG_HOME) && $XDG_CONFIG_HOME =~# '^/'
|
||||
\ ? $XDG_CONFIG_HOME
|
||||
\ : '~/.config'
|
||||
return expand(get(g:, 'chopsticks_resolved_local_config',
|
||||
\ get(g:, 'chopsticks_local_config', l:xdg . '/chopsticks.vim')))
|
||||
endfunction
|
||||
|
||||
function! s:EditLocalConfig() abort
|
||||
let l:path = s:LocalConfigPath()
|
||||
let l:new_file = !filereadable(l:path)
|
||||
let l:dir = fnamemodify(l:path, ':h')
|
||||
if !isdirectory(l:dir)
|
||||
call mkdir(l:dir, 'p')
|
||||
endif
|
||||
|
||||
execute 'edit ' . fnameescape(l:path)
|
||||
setlocal filetype=vim
|
||||
if l:new_file && line('$') == 1 && getline(1) ==# ''
|
||||
call setline(1, [
|
||||
\ '" chopsticks local preferences',
|
||||
\ "let g:chopsticks_profile = 'engineer'",
|
||||
\ "let g:chopsticks_keymap_style = 'space'",
|
||||
\ '',
|
||||
\ '" Optional habits:',
|
||||
\ '" let g:chopsticks_enable_jk_escape = 1',
|
||||
\ '" let g:chopsticks_enable_ctrl_s_save = 1',
|
||||
\ '" let g:chopsticks_enable_auto_pairs = 1',
|
||||
\ ])
|
||||
setlocal nomodified
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:ReloadChopsticks() abort
|
||||
unlet! g:chopsticks_loaded
|
||||
execute 'source ' . fnameescape($MYVIMRC)
|
||||
echo 'chopsticks reloaded'
|
||||
endfunction
|
||||
|
||||
command! ChopsticksConfig call s:EditLocalConfig()
|
||||
command! ChopsticksReload call s:ReloadChopsticks()
|
||||
|
||||
if get(g:, 'chopsticks_enable_reindent_file', 0)
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <leader>c= gg=G``
|
||||
else
|
||||
nnoremap <leader>F gg=G``
|
||||
endif
|
||||
endif
|
||||
if g:chopsticks_space_keymaps
|
||||
vnoremap <leader>= =
|
||||
else
|
||||
vnoremap <leader>F =
|
||||
nnoremap <leader>wa :wa<CR>
|
||||
endif
|
||||
|
||||
if !g:chopsticks_space_keymaps
|
||||
nnoremap <silent> <Leader>= :exe "resize " . (winheight(0) * 3/2)<CR>
|
||||
nnoremap <silent> <Leader>- :exe "resize " . (winheight(0) * 2/3)<CR>
|
||||
endif
|
||||
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <leader><Tab> <c-^>
|
||||
else
|
||||
nnoremap <leader><leader> <c-^>
|
||||
endif
|
||||
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <leader>cW :%s/\s\+$//<CR>:let @/=''<CR>
|
||||
vnoremap <leader>cW :s/\s\+$//<CR>:let @/=''<CR>gv
|
||||
else
|
||||
nnoremap <leader>W :%s/\s\+$//<CR>:let @/=''<CR>
|
||||
vnoremap <leader>W :s/\s\+$//<CR>:let @/=''<CR>gv
|
||||
endif
|
||||
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <leader>fc :ChopsticksConfig<CR>
|
||||
nnoremap <leader>fv :edit $MYVIMRC<CR>
|
||||
nnoremap <leader>fV :ChopsticksReload<CR>
|
||||
else
|
||||
nnoremap <leader>ec :ChopsticksConfig<CR>
|
||||
nnoremap <leader>ev :edit $MYVIMRC<CR>
|
||||
nnoremap <leader>sv :ChopsticksReload<CR>
|
||||
endif
|
||||
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <leader>sr :%s/\<<C-r><C-w>\>//g<Left><Left>
|
||||
vnoremap <leader>sr :s///g<Left><Left><Left>
|
||||
else
|
||||
nnoremap <leader>* :%s/\<<C-r><C-w>\>//g<Left><Left>
|
||||
vnoremap <leader>* :s///g<Left><Left><Left>
|
||||
endif
|
||||
|
||||
if has('clipboard')
|
||||
if g:chopsticks_space_keymaps
|
||||
nnoremap <leader>fp :let @+ = expand("%:p")<CR>:echo "Copied: " . expand("%:p")<CR>
|
||||
nnoremap <leader>fn :let @+ = expand("%:t")<CR>:echo "Copied: " . expand("%:t")<CR>
|
||||
else
|
||||
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
|
||||
endif
|
||||
|
||||
if get(g:, 'chopsticks_enable_sudo_save_bang', 0)
|
||||
cnoremap w!! w !sudo tee > /dev/null %
|
||||
endif
|
||||
28
scripts/test-common.sh
Normal file
28
scripts/test-common.sh
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env bash
|
||||
# Shared setup for chopsticks test scripts.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/chopsticks-test-XXXXXX")"
|
||||
EMPTY_XDG="$TMP_ROOT/xdg-empty"
|
||||
STARTUP_LIMIT_MS="${STARTUP_LIMIT_MS:-150}"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMP_ROOT"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
cd "$ROOT"
|
||||
mkdir -p "$EMPTY_XDG"
|
||||
|
||||
step() {
|
||||
printf '\n==> %s\n' "$1"
|
||||
}
|
||||
|
||||
need() {
|
||||
command -v "$1" >/dev/null 2>&1 || {
|
||||
echo "Missing required command: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
144
scripts/test-quick.sh
Executable file
144
scripts/test-quick.sh
Executable file
|
|
@ -0,0 +1,144 @@
|
|||
#!/usr/bin/env bash
|
||||
# Shell, docs, installer, and bootstrap checks.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=scripts/test-common.sh
|
||||
source "$SCRIPT_DIR/test-common.sh"
|
||||
|
||||
check_shell() {
|
||||
step "Shell syntax and lint"
|
||||
need bash
|
||||
bash -n install.sh
|
||||
bash -n get.sh
|
||||
bash -n scripts/test.sh
|
||||
bash -n scripts/test-common.sh
|
||||
bash -n scripts/test-quick.sh
|
||||
bash -n scripts/test-vim.sh
|
||||
test -x install.sh
|
||||
test -x get.sh
|
||||
test -x scripts/test.sh
|
||||
|
||||
need shellcheck
|
||||
shellcheck install.sh get.sh scripts/test.sh \
|
||||
scripts/test-common.sh scripts/test-quick.sh scripts/test-vim.sh
|
||||
}
|
||||
|
||||
check_docs() {
|
||||
step "Markdown lint"
|
||||
need markdownlint
|
||||
markdownlint README.md QUICKSTART.md CONTRIBUTING.md CHANGELOG.md BETA.md
|
||||
|
||||
step "Documentation consistency"
|
||||
for command in ChopsticksHelp ChopsticksConfig ChopsticksReload \
|
||||
ChopsticksBeta ChopsticksBetaLog ChopsticksBetaSession
|
||||
do
|
||||
for file in README.md BETA.md doc/chopsticks.txt modules/cheatsheet.vim \
|
||||
modules/tutor.vim modules/status.vim
|
||||
do
|
||||
grep -Fq "$command" "$file" || {
|
||||
echo "Missing $command in $file" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
done
|
||||
grep -Fq '*chopsticks.txt*' doc/chopsticks.txt
|
||||
grep -Fq '*chopsticks-space*' doc/chopsticks.txt
|
||||
grep -Fq 'command! ChopsticksHelp' modules/help.vim
|
||||
grep -Fq 'command! ChopsticksConfig' modules/utilities.vim
|
||||
grep -Fq 'command! ChopsticksReload' modules/utilities.vim
|
||||
for command in ChopsticksBeta ChopsticksBetaLog ChopsticksBetaSession; do
|
||||
grep -Fq "command! $command" modules/beta.vim || {
|
||||
echo "Missing $command definition in modules/beta.vim" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
if command -v vhs >/dev/null 2>&1; then
|
||||
vhs validate .github/demo.tape
|
||||
else
|
||||
echo "Skipping VHS tape validation: vhs not installed"
|
||||
fi
|
||||
}
|
||||
|
||||
check_installer_modes() {
|
||||
step "Installer profile-only modes"
|
||||
XDG_CONFIG_HOME="$TMP_ROOT/dry" ./install.sh --dry-run --profile=full \
|
||||
| tee "$TMP_ROOT/install-dry-run.txt"
|
||||
grep -q 'Profile: full' "$TMP_ROOT/install-dry-run.txt"
|
||||
test ! -e "$TMP_ROOT/dry/chopsticks.vim"
|
||||
|
||||
XDG_CONFIG_HOME="$TMP_ROOT/config" ./install.sh --configure-only --profile=minimal
|
||||
grep -q "let g:chopsticks_profile = 'minimal'" "$TMP_ROOT/config/chopsticks.vim"
|
||||
|
||||
XDG_CONFIG_HOME="$TMP_ROOT/config" ./install.sh --configure-only --profile=full
|
||||
grep -q "let g:chopsticks_profile = 'full'" "$TMP_ROOT/config/chopsticks.vim"
|
||||
|
||||
XDG_CONFIG_HOME="$TMP_ROOT/default" ./install.sh --configure-only --yes
|
||||
grep -q "let g:chopsticks_profile = 'engineer'" "$TMP_ROOT/default/chopsticks.vim"
|
||||
}
|
||||
|
||||
check_bootstrap() {
|
||||
step "Bootstrap dry-run safety"
|
||||
CHOPSTICKS_DEST="$TMP_ROOT/bootstrap" ./get.sh --dry-run --profile=minimal \
|
||||
| tee "$TMP_ROOT/get-dry-run.txt"
|
||||
grep -q 'Would clone' "$TMP_ROOT/get-dry-run.txt"
|
||||
test ! -e "$TMP_ROOT/bootstrap"
|
||||
|
||||
mkdir -p "$TMP_ROOT/no-git-bin"
|
||||
printf '%s\n' \
|
||||
'#!/usr/bin/env bash' \
|
||||
"echo \"brew was called\" >> \"\$BREW_LOG\"" \
|
||||
'exit 42' > "$TMP_ROOT/no-git-bin/brew"
|
||||
chmod +x "$TMP_ROOT/no-git-bin/brew"
|
||||
BREW_LOG="$TMP_ROOT/no-git-brew.log" \
|
||||
PATH="$TMP_ROOT/no-git-bin" \
|
||||
CHOPSTICKS_DEST="$TMP_ROOT/no-git-bootstrap" \
|
||||
/bin/bash ./get.sh --dry-run --profile=full \
|
||||
| tee "$TMP_ROOT/get-no-git-dry-run.txt"
|
||||
grep -q 'Would require: git' "$TMP_ROOT/get-no-git-dry-run.txt"
|
||||
grep -q 'Would clone' "$TMP_ROOT/get-no-git-dry-run.txt"
|
||||
test ! -e "$TMP_ROOT/no-git-brew.log"
|
||||
test ! -e "$TMP_ROOT/no-git-bootstrap"
|
||||
|
||||
mkdir -p "$TMP_ROOT/not-chopsticks"
|
||||
git -c init.defaultBranch=main init "$TMP_ROOT/not-chopsticks" >/dev/null
|
||||
git -C "$TMP_ROOT/not-chopsticks" remote add origin https://github.com/example/not-chopsticks.git
|
||||
if CHOPSTICKS_DEST="$TMP_ROOT/not-chopsticks" ./get.sh --dry-run; then
|
||||
echo "Expected get.sh to reject non-chopsticks repo" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$TMP_ROOT/chopsticks-existing"
|
||||
git -c init.defaultBranch=main init "$TMP_ROOT/chopsticks-existing" >/dev/null
|
||||
git -C "$TMP_ROOT/chopsticks-existing" remote add origin https://github.com/m1ngsama/chopsticks.git
|
||||
touch "$TMP_ROOT/chopsticks-existing/install.sh" "$TMP_ROOT/chopsticks-existing/.vimrc"
|
||||
CHOPSTICKS_DEST="$TMP_ROOT/chopsticks-existing" ./get.sh --dry-run --yes \
|
||||
| tee "$TMP_ROOT/get-existing.txt"
|
||||
grep -q 'Would update existing chopsticks repo' "$TMP_ROOT/get-existing.txt"
|
||||
}
|
||||
|
||||
run_quick_group() {
|
||||
case "$1" in
|
||||
quick)
|
||||
check_shell
|
||||
check_docs
|
||||
check_installer_modes
|
||||
check_bootstrap
|
||||
;;
|
||||
shell) check_shell ;;
|
||||
docs) check_docs ;;
|
||||
installer) check_installer_modes ;;
|
||||
bootstrap) check_bootstrap ;;
|
||||
*)
|
||||
echo "Unknown quick test group: $1" >&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
set -- quick
|
||||
fi
|
||||
|
||||
for group in "$@"; do
|
||||
run_quick_group "$group"
|
||||
done
|
||||
539
scripts/test-vim.sh
Executable file
539
scripts/test-vim.sh
Executable file
|
|
@ -0,0 +1,539 @@
|
|||
#!/usr/bin/env bash
|
||||
# Vim smoke tests. Requires plugins in ~/.vim/plugged.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=scripts/test-common.sh
|
||||
source "$SCRIPT_DIR/test-common.sh"
|
||||
|
||||
check_plugin_dirs() {
|
||||
step "Plugin directories"
|
||||
for plugin in \
|
||||
fzf fzf.vim vim-fugitive vim-gitgutter ale vim-lsp vim-lsp-settings \
|
||||
asyncomplete.vim asyncomplete-lsp.vim vim-markdown
|
||||
do
|
||||
test -d "$HOME/.vim/plugged/$plugin" || {
|
||||
echo "Missing plugin directory: $plugin" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
}
|
||||
|
||||
check_vim() {
|
||||
step "Vim smoke tests"
|
||||
need vim
|
||||
check_plugin_dirs
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -c 'qa!' 2>&1
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N -c 'qa!' 2>&1
|
||||
if [ -x /usr/bin/vim ] && [ "$(command -v vim)" != "/usr/bin/vim" ]; then
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" /usr/bin/vim -u .vimrc -i NONE -es -N -c 'qa!' 2>&1
|
||||
fi
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c "redir! > $TMP_ROOT/plugs.txt" \
|
||||
-c 'silent echo len(g:plugs)' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>/dev/null
|
||||
PLUGS="$(tr -d '[:space:]' < "$TMP_ROOT/plugs.txt")"
|
||||
echo "Plugins registered: $PLUGS"
|
||||
if [ "$PLUGS" -lt 20 ]; then
|
||||
echo "Expected 20+ plugins, got $PLUGS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$TMP_ROOT/chopsticks path/modules"
|
||||
mkdir -p "$TMP_ROOT/chopsticks path/doc"
|
||||
cp .vimrc "$TMP_ROOT/chopsticks path/.vimrc"
|
||||
cp modules/*.vim "$TMP_ROOT/chopsticks path/modules/"
|
||||
cp doc/*.txt "$TMP_ROOT/chopsticks path/doc/"
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u "$TMP_ROOT/chopsticks path/.vimrc" \
|
||||
-i NONE -es -N -c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" \
|
||||
vim -u "$TMP_ROOT/chopsticks path/.vimrc" -i NONE -es -N \
|
||||
-c 'ChopsticksHelp' \
|
||||
-c 'if expand("%:t") !=# "chopsticks.txt" | cquit | endif' \
|
||||
-c 'if search("chopsticks-space", "n") == 0 | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_profile = "minimal"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if has_key(g:plugs, "ale") || has_key(g:plugs, "vim-lsp") || has_key(g:plugs, "vim-lsp-settings") || has_key(g:plugs, "asyncomplete.vim") || has_key(g:plugs, "auto-pairs") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
mkdir -p "$TMP_ROOT/local"
|
||||
printf "%s\n" "let g:chopsticks_profile = 'minimal'" > "$TMP_ROOT/local/config.vim"
|
||||
vim -u NONE -i NONE -es -N \
|
||||
-c "let g:chopsticks_local_config = '$TMP_ROOT/local/config.vim'" \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if g:chopsticks_resolved_local_config !~# "config.vim$" || g:chopsticks_profile !=# "minimal" || has_key(g:plugs, "ale") || has_key(g:plugs, "vim-lsp") || has_key(g:plugs, "auto-pairs") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
mkdir -p "$TMP_ROOT/xdg"
|
||||
printf "%s\n" "let g:chopsticks_profile = 'minimal'" > "$TMP_ROOT/xdg/chopsticks.vim"
|
||||
XDG_CONFIG_HOME="$TMP_ROOT/xdg" vim -u NONE -i NONE -es -N \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if g:chopsticks_resolved_local_config !~# "chopsticks.vim$" || g:chopsticks_profile !=# "minimal" || has_key(g:plugs, "ale") || has_key(g:plugs, "vim-lsp") || has_key(g:plugs, "auto-pairs") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
local_config_cmd="$TMP_ROOT/config command/chopsticks.vim"
|
||||
vim -u NONE -i NONE -es -N \
|
||||
-c "let g:chopsticks_local_config = '$local_config_cmd'" \
|
||||
-c 'source .vimrc' \
|
||||
-c 'ChopsticksConfig' \
|
||||
-c 'if expand("%:p") !=# g:chopsticks_resolved_local_config || &l:filetype !=# "vim" | cquit | endif' \
|
||||
-c 'if getline(1) !~# "chopsticks local preferences" || &modified | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
test -d "$(dirname "$local_config_cmd")"
|
||||
test ! -e "$local_config_cmd"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'ChopsticksStatus' \
|
||||
-c "redir! > $TMP_ROOT/status-default.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
if grep -Fq 'vim-lsp not loaded' "$TMP_ROOT/status-default.txt"; then
|
||||
cat "$TMP_ROOT/status-default.txt"
|
||||
exit 1
|
||||
fi
|
||||
grep -Fq 'OK vim-lsp stack (installed)' "$TMP_ROOT/status-default.txt"
|
||||
grep -Fq 'help :ChopsticksHelp :ChopsticksTutor SPC ?' "$TMP_ROOT/status-default.txt"
|
||||
grep -Fq 'commands :ChopsticksConfig :ChopsticksReload' "$TMP_ROOT/status-default.txt"
|
||||
grep -Fq 'candidate 2.3.0' "$TMP_ROOT/status-default.txt"
|
||||
grep -Fq 'keymap space' "$TMP_ROOT/status-default.txt"
|
||||
grep -Fq 'commands :ChopsticksBeta :ChopsticksBetaLog' "$TMP_ROOT/status-default.txt"
|
||||
grep -Fq ':ChopsticksBetaSession' "$TMP_ROOT/status-default.txt"
|
||||
grep -Fq 'chopsticks-2.3.0.md' "$TMP_ROOT/status-default.txt"
|
||||
grep -Fq 'python (:LspInstallServer in a python file)' "$TMP_ROOT/status-default.txt"
|
||||
grep -Fq 'LSP actions are buffer-local and start after a server attaches.' "$TMP_ROOT/status-default.txt"
|
||||
grep -Fq 'Open that filetype and run :LspInstallServer once.' "$TMP_ROOT/status-default.txt"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'let last_change_map = nr2char(96) . "[v" . nr2char(96) . "]"' \
|
||||
-c 'if maparg("0", "n") !=# "" || maparg("0", "v") !=# "" || maparg("Y", "n") !=# "" || maparg("Q", "n") !=# "" || maparg("<Space>", "n") !=# "" || maparg("//", "v") !=# "" || maparg("gV", "n") !=# "" || maparg("jk", "i") !=# "" || maparg("<C-s>", "n") !=# "" || maparg("<C-s>", "i") !=# "" || maparg("<C-p>", "n") !=# "" || maparg("<C-p>", "c") !=# "" || maparg("<C-n>", "c") !=# "" || maparg("w!!", "c") !=# "" | cquit | endif' \
|
||||
-c 'if maparg("<C-h>", "n") !~# "NavigateWindow" || maparg("<C-j>", "n") !~# "NavigateWindow" || maparg("<C-k>", "n") !~# "NavigateWindow" || maparg("<C-l>", "n") !~# "NavigateWindow" | cquit | endif' \
|
||||
-c 'if has_key(g:plugs, "auto-pairs") || maparg("<Tab>", "i") =~# "pumvisible" || maparg("<S-Tab>", "i") =~# "pumvisible" || maparg("<CR>", "i") =~# "asyncomplete#close_popup" || maparg("<CR>", "i") =~# "AutoPairs" | cquit | endif' \
|
||||
-c 'if maparg("<Esc><Esc>", "t") !=# "" || maparg("<C-h>", "t") !=# "" || maparg("<C-j>", "t") !=# "" || maparg("<C-k>", "t") !=# "" || maparg("<C-l>", "t") !=# "" | cquit | endif' \
|
||||
-c 'if maparg("s", "n") !~# "easymotion-overwin-f2" | cquit | endif' \
|
||||
-c 'if maparg("<Space>/", "v") !~# "escape" || maparg("<Space>v", "n") !=# last_change_map || maparg("<Space><Space>", "n") !~# "SmartFiles" | cquit | endif' \
|
||||
-c 'if maparg(",/", "v") !=# "" || maparg(",v", "n") !=# "" || maparg(",ff", "n") !=# "" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_keymap_style = "classic"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'let last_change_map = nr2char(96) . "[v" . nr2char(96) . "]"' \
|
||||
-c 'if mapleader !=# "," || maparg("s", "n") !=# "" || maparg(",/", "v") !~# "escape" || maparg(",v", "n") !=# last_change_map || maparg(",ff", "n") !~# "SmartFiles" | cquit | endif' \
|
||||
-c 'if maparg("<C-h>", "n") !~# "NavigateWindow" || maparg("<C-j>", "n") !~# "NavigateWindow" || maparg("<C-k>", "n") !~# "NavigateWindow" || maparg("<C-l>", "n") !~# "NavigateWindow" | cquit | endif' \
|
||||
-c 'if maparg(",ec", "n") !~# "ChopsticksConfig" || maparg(",sv", "n") !~# "ChopsticksReload" | cquit | endif' \
|
||||
-c 'if maparg(",gp", "n") !=# "" || maparg(",gl", "n") !=# "" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_enable_jk_escape = 1' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if maparg("jk", "i") !~# "<Esc>" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_enable_ctrl_s_save = 1' \
|
||||
-c 'let g:chopsticks_enable_sudo_save_bang = 1' \
|
||||
-c 'let g:chopsticks_enable_completion_keymaps = 1' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if maparg("<C-s>", "n") !~# ":w" || maparg("<C-s>", "i") !~# ":w" || maparg("w!!", "c") !~# "sudo tee" | cquit | endif' \
|
||||
-c 'if maparg("<Tab>", "i") !~# "pumvisible" || maparg("<S-Tab>", "i") !~# "pumvisible" || maparg("<CR>", "i") !~# "asyncomplete#close_popup" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_enable_auto_pairs = 1' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if !has_key(g:plugs, "auto-pairs") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_enable_terminal_keymaps = 1' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if has("terminal") && (maparg("<Esc><Esc>", "t") !~# "<C-\\\\><C-N>" || maparg("<C-h>", "t") !~# "NavigateWindow" || maparg("<C-j>", "t") !~# "NavigateWindow" || maparg("<C-k>", "t") !~# "NavigateWindow" || maparg("<C-l>", "t") !~# "NavigateWindow") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
TMUX=/tmp/chopsticks-test XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'if has_key(g:plugs, "vim-tmux-navigator") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
TMUX=/tmp/chopsticks-test XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_enable_tmux_navigator = 1' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if !has_key(g:plugs, "vim-tmux-navigator") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'setfiletype netrw' \
|
||||
-c 'if &filetype !=# "netrw" | cquit | endif' \
|
||||
-c 'if !maparg("<C-l>", "n", 0, 1).buffer | cquit | endif' \
|
||||
-c 'if maparg("<C-h>", "n") !~# "NavigateWindow" || maparg("<C-l>", "n") !~# "NavigateWindow" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'if &exrc || &secure | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_enable_exrc = 1' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if !&exrc || !&secure | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'if maparg("<Space>c=", "n") !=# "" | cquit | endif' \
|
||||
-c 'if maparg("<Space>=", "v") !~# "=" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_enable_reindent_file = 1' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if maparg("<Space>c=", "n") !~# "gg=G" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
TERM=xterm-256color XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'if g:is_tty || &ttimeoutlen != 10 | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
TERM=linux XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'if !g:is_tty || &ttimeoutlen != 50 | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'if !exists("loaded_gzip") || !exists("loaded_logiPat") || !exists("loaded_rrhelper") || !exists("loaded_spellfile_plugin") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'silent! delcommand LspStatus' \
|
||||
-c 'silent! delcommand LspInstallServer' \
|
||||
-c 'ChopsticksStatus' \
|
||||
-c "redir! > $TMP_ROOT/status-lsp-not-loaded.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq 'OK vim-lsp stack (installed; not loaded yet)' "$TMP_ROOT/status-lsp-not-loaded.txt"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_profile = "minimal"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'ChopsticksStatus' \
|
||||
-c "redir! > $TMP_ROOT/status-minimal.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq 'off vim-lsp stack (LSP disabled by profile)' "$TMP_ROOT/status-minimal.txt"
|
||||
grep -Fq 'off python (LSP disabled by profile)' "$TMP_ROOT/status-minimal.txt"
|
||||
if grep -Fq 'LSP actions are buffer-local' "$TMP_ROOT/status-minimal.txt"; then
|
||||
cat "$TMP_ROOT/status-minimal.txt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$TMP_ROOT/missing-home/.vim/autoload"
|
||||
cp "$HOME/.vim/autoload/plug.vim" "$TMP_ROOT/missing-home/.vim/autoload/plug.vim"
|
||||
HOME="$TMP_ROOT/missing-home" XDG_CONFIG_HOME="$EMPTY_XDG" \
|
||||
vim -u .vimrc -i NONE -es -N \
|
||||
-c 'ChopsticksStatus' \
|
||||
-c "redir! > $TMP_ROOT/status-missing-plugin.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq 'vim-lsp not installed; run :PlugInstall' "$TMP_ROOT/status-missing-plugin.txt"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'doautocmd User lsp_buffer_enabled' \
|
||||
-c 'if maparg("gd", "n") !~# "lsp-definition" || maparg("gr", "n") !~# "lsp-references" || maparg("gI", "n") !~# "lsp-implementation" || maparg("gy", "n") !~# "lsp-type-definition" || maparg("K", "n") !~# "lsp-hover" | cquit | endif' \
|
||||
-c 'if maparg("[d", "n") !~# "lsp-previous-diagnostic" || maparg("]d", "n") !~# "lsp-next-diagnostic" | cquit | endif' \
|
||||
-c 'if maparg("<Space>ca", "n") !~# "lsp-code-action" || maparg("<Space>cr", "n") !~# "lsp-rename" || maparg("<Space>cf", "n") !~# "lsp-document-format" | cquit | endif' \
|
||||
-c 'if maparg("<Space>ci", "n") !~# "LspStatus" || maparg("<Space>co", "n") !~# "lsp-document-symbol-search" | cquit | endif' \
|
||||
-c 'if maparg("<Space>cd", "n") !=# "" || maparg("<Space>ck", "n") !=# "" || maparg("<Space>cp", "n") !=# "" || maparg("<Space>cn", "n") !=# "" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_keymap_style = "classic"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'doautocmd User lsp_buffer_enabled' \
|
||||
-c 'if maparg("gd", "n") !=# "" || maparg("K", "n") !=# "" || maparg("gI", "n") !=# "" || maparg("gr", "n") !=# "" | cquit | endif' \
|
||||
-c 'if maparg(",dd", "n") !~# "lsp-definition" || maparg(",dt", "n") !~# "lsp-type-definition" || maparg(",di", "n") !~# "lsp-implementation" || maparg(",dr", "n") !~# "lsp-references" || maparg(",dk", "n") !~# "lsp-hover" | cquit | endif' \
|
||||
-c 'if maparg(",dp", "n") !~# "lsp-previous-diagnostic" | cquit | endif' \
|
||||
-c 'if maparg(",dn", "n") !~# "lsp-next-diagnostic" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_keymap_style = "space"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if mapleader !=# "\<Space>" || maplocalleader !=# "," | cquit | endif' \
|
||||
-c 'if maparg(",ff", "n") !=# "" || maparg(",w", "n") !=# "" || maparg(",mt", "n") !=# "" || maparg(",gp", "n") !=# "" || maparg("<Space>gp", "n") !=# "" | cquit | endif' \
|
||||
-c 'if maparg("<Space>f", "n") !=# "" || maparg("<Space>u", "n") !=# "" || maparg("<Space>c", "n") !=# "" || maparg("<Space>x", "n") !=# "" || maparg("<Space>wm", "n") !=# "" || maparg("<Space>w+", "n") !=# "" || maparg("<Space>w-", "n") !=# "" | cquit | endif' \
|
||||
-c 'if maparg("<Space><Space>", "n") !~# "SmartFiles" || maparg("<Space>ff", "n") !~# "SmartFiles" || maparg("<Space>,", "n") !~# "Buffers" || maparg("<Space>bd", "n") !~# "Bclose" | cquit | endif' \
|
||||
-c 'if maparg("<Space>w", "n") !~# ":w" || maparg("<Space>W", "n") !~# ":wa" || maparg("<Space>q", "n") !~# ":q" || maparg("<Space>qq", "n") !=# "" || maparg("<Space>qx", "n") !=# "" || maparg("<Space>fc", "n") !~# "ChopsticksConfig" || maparg("<Space>fV", "n") !~# "ChopsticksReload" || maparg("<Space>U", "n") !~# "UndotreeToggle" || maparg("<Space>fs", "n") !=# "" || maparg("<Space>bu", "n") !=# "" | cquit | endif' \
|
||||
-c 'if maparg("<Space>gl", "n") !~# "Git log" || maparg("<Space>gC", "n") !~# "Commits" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_keymap_style = "space"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'doautocmd User lsp_buffer_enabled' \
|
||||
-c 'if maparg("<Space>cf", "n") !~# "lsp-document-format" | cquit | endif' \
|
||||
-c 'if maparg("gd", "n") !~# "lsp-definition" || maparg("gr", "n") !~# "lsp-references" || maparg("K", "n") !~# "lsp-hover" | cquit | endif' \
|
||||
-c 'if maparg("<Space>cd", "n") !=# "" || maparg("<Space>ck", "n") !=# "" | cquit | endif' \
|
||||
-c 'if maparg("<Space>f", "n") !=# "" || maparg("<Space>c", "n") !=# "" | cquit | endif' \
|
||||
-c 'if maparg(",f", "n") !=# "" || maparg(",dd", "n") !=# "" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'call feedkeys("\<Space>?", "xt")' \
|
||||
-c 'if max(map(getline(1, "$"), "strdisplaywidth(v:val)")) > 42 | cquit | endif' \
|
||||
-c "redir! > $TMP_ROOT/cheat-default.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq ':ChopsticksStatus check LSP setup' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq 'trained loop:' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq 'files → s jump → gd/K' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq 'run → grep → git' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq 'SPC SPC files' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq 'gd definition' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq 'K hover docs' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq '[d ]d LSP diagnostics' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq 'Ctrl-hjkl windows' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq '<C-w>hjkl native fallback' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq 'SPC w save' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq 'SPC fc edit local config' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq 's+2ch easymotion jump' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq 'cl / cc native s / S substitute' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq ':ChopsticksHelp full help' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq ':ChopsticksConfig local config' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq ':ChopsticksReload reload config' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq ':ChopsticksTutor practice' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq ':ChopsticksBeta release checklist' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq ':ChopsticksBetaLog release notes' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq ':ChopsticksBetaSession new release note' "$TMP_ROOT/cheat-default.txt"
|
||||
if grep -Eq 'Ctrl\\+p find file|Ctrl\\+hjkl navigate splits|Ctrl\\+s save|jk exit insert|SPC fs save|SPC cd definition|SPC ck hover|SPC wm|SPC w\\+/-|\\[g \\]g LSP diagnostics' "$TMP_ROOT/cheat-default.txt"; then
|
||||
cat "$TMP_ROOT/cheat-default.txt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'ChopsticksCheatSheet' \
|
||||
-c 'if max(map(getline(1, "$"), "strdisplaywidth(v:val)")) > 42 | cquit | endif' \
|
||||
-c "redir! > $TMP_ROOT/cheat-command.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq 'SPC SPC files' "$TMP_ROOT/cheat-command.txt"
|
||||
grep -Fq ':ChopsticksTutor practice' "$TMP_ROOT/cheat-command.txt"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_keymap_style = "classic"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'normal ,?' \
|
||||
-c 'if max(map(getline(1, "$"), "strdisplaywidth(v:val)")) > 42 | cquit | endif' \
|
||||
-c "redir! > $TMP_ROOT/cheat-classic.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq ',ff files' "$TMP_ROOT/cheat-classic.txt"
|
||||
grep -Fq 'trained loop:' "$TMP_ROOT/cheat-classic.txt"
|
||||
grep -Fq 'files → jump → inspect' "$TMP_ROOT/cheat-classic.txt"
|
||||
grep -Fq 'run → grep → git' "$TMP_ROOT/cheat-classic.txt"
|
||||
grep -Fq ',dd definition' "$TMP_ROOT/cheat-classic.txt"
|
||||
grep -Fq ',dk hover docs' "$TMP_ROOT/cheat-classic.txt"
|
||||
grep -Fq ',dp ,dn LSP diagnostics' "$TMP_ROOT/cheat-classic.txt"
|
||||
grep -Fq ',ec edit local config' "$TMP_ROOT/cheat-classic.txt"
|
||||
grep -Fq ':ChopsticksConfig local config' "$TMP_ROOT/cheat-classic.txt"
|
||||
if grep -Eq ',gp push|,gl pull' "$TMP_ROOT/cheat-classic.txt"; then
|
||||
cat "$TMP_ROOT/cheat-classic.txt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_profile = "minimal"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'call feedkeys("\<Space>?", "xt")' \
|
||||
-c 'if max(map(getline(1, "$"), "strdisplaywidth(v:val)")) > 42 | cquit | endif' \
|
||||
-c "redir! > $TMP_ROOT/cheat.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
if grep -Eq 'definition|LspInstallServer|ALE errors|undo tree|markdown preview' "$TMP_ROOT/cheat.txt"; then
|
||||
cat "$TMP_ROOT/cheat.txt"
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'SPC rr run file' "$TMP_ROOT/cheat.txt"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_keymap_style = "space"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'call feedkeys("\<Space>?", "xt")' \
|
||||
-c 'if max(map(getline(1, "$"), "strdisplaywidth(v:val)")) > 42 | cquit | endif' \
|
||||
-c "redir! > $TMP_ROOT/cheat-space.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq 'SPC w save' "$TMP_ROOT/cheat-space.txt"
|
||||
grep -Fq 'gd definition' "$TMP_ROOT/cheat-space.txt"
|
||||
grep -Fq 'SPC gl log graph' "$TMP_ROOT/cheat-space.txt"
|
||||
grep -Fq 'SPC fc edit local config' "$TMP_ROOT/cheat-space.txt"
|
||||
grep -Fq 's+2ch easymotion jump' "$TMP_ROOT/cheat-space.txt"
|
||||
if grep -Eq ',w save|,gp push|SPC gp push|SPC gl pull|SPC fs save|SPC cd definition|SPC f format' "$TMP_ROOT/cheat-space.txt"; then
|
||||
cat "$TMP_ROOT/cheat-space.txt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'ChopsticksTutor' \
|
||||
-c 'if max(map(getline(1, "$"), "strdisplaywidth(v:val)")) > 78 | cquit | endif' \
|
||||
-c "redir! > $TMP_ROOT/tutor-default.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq 'chopsticks tutor' "$TMP_ROOT/tutor-default.txt"
|
||||
grep -Fq 'Goal: train one long-term project loop around Vim.' "$TMP_ROOT/tutor-default.txt"
|
||||
grep -Fq '1. trained loop' "$TMP_ROOT/tutor-default.txt"
|
||||
grep -Fq 'SPC ? active cheat sheet' "$TMP_ROOT/tutor-default.txt"
|
||||
grep -Fq 'SPC fc edit local config' "$TMP_ROOT/tutor-default.txt"
|
||||
grep -Fq ':ChopsticksHelp full help' "$TMP_ROOT/tutor-default.txt"
|
||||
grep -Fq ':ChopsticksConfig local config' "$TMP_ROOT/tutor-default.txt"
|
||||
grep -Fq 'Ctrl-h/j/k/l split navigation' "$TMP_ROOT/tutor-default.txt"
|
||||
grep -Fq 'SPC e, Ctrl-h/l enter/leave sidebar' "$TMP_ROOT/tutor-default.txt"
|
||||
grep -Fq 's + 2 chars visible jump' "$TMP_ROOT/tutor-default.txt"
|
||||
grep -Fq 'cl / cc native s / S substitute' "$TMP_ROOT/tutor-default.txt"
|
||||
grep -Fq 'gd / gr / K definition / refs / docs' "$TMP_ROOT/tutor-default.txt"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_keymap_style = "classic"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'ChopsticksTutor' \
|
||||
-c 'if max(map(getline(1, "$"), "strdisplaywidth(v:val)")) > 78 | cquit | endif' \
|
||||
-c "redir! > $TMP_ROOT/tutor-classic.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq 'classic layout' "$TMP_ROOT/tutor-classic.txt"
|
||||
grep -Fq 'Goal: train one long-term project loop around Vim.' "$TMP_ROOT/tutor-classic.txt"
|
||||
grep -Fq ',? active cheat sheet' "$TMP_ROOT/tutor-classic.txt"
|
||||
grep -Fq ',ec edit local config' "$TMP_ROOT/tutor-classic.txt"
|
||||
grep -Fq 'Ctrl-h/j/k/l split navigation' "$TMP_ROOT/tutor-classic.txt"
|
||||
grep -Fq ',S + 2 chars EasyMotion jump' "$TMP_ROOT/tutor-classic.txt"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'ChopsticksBeta' \
|
||||
-c 'if max(map(getline(1, "$"), "strdisplaywidth(v:val)")) > 78 | cquit | endif' \
|
||||
-c "redir! > $TMP_ROOT/beta-guide.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq 'chopsticks 2.3.0' "$TMP_ROOT/beta-guide.txt"
|
||||
grep -Fq 'Prove this can be the long-term project loop.' "$TMP_ROOT/beta-guide.txt"
|
||||
grep -Fq 'Record real editing friction before release.' "$TMP_ROOT/beta-guide.txt"
|
||||
grep -Fq 'no private wiki is needed to remember the daily loop' "$TMP_ROOT/beta-guide.txt"
|
||||
grep -Fq 'window/sidebar navigation beats native <C-w> only' "$TMP_ROOT/beta-guide.txt"
|
||||
grep -Fq 'SPC ? active cheat sheet' "$TMP_ROOT/beta-guide.txt"
|
||||
grep -Fq 'BETA.md release checklist and rollback' "$TMP_ROOT/beta-guide.txt"
|
||||
grep -Fq ':ChopsticksBetaLog editable local release notes' "$TMP_ROOT/beta-guide.txt"
|
||||
grep -Fq ':ChopsticksBetaSession append a new session block' "$TMP_ROOT/beta-guide.txt"
|
||||
|
||||
beta_log="$TMP_ROOT/release log/chopsticks-2.3.0.md"
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c "let g:chopsticks_beta_log = '$beta_log'" \
|
||||
-c 'source .vimrc' \
|
||||
-c 'ChopsticksBetaLog' \
|
||||
-c 'if expand("%:p") !~# "chopsticks-2.3.0.md" || &l:filetype !=# "markdown" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq '# chopsticks 2.3.0 release log' "$beta_log"
|
||||
grep -Fq 'First key tried when stuck:' "$beta_log"
|
||||
printf '%s\n' '- keep-existing-note' >> "$beta_log"
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c "let g:chopsticks_beta_log = '$beta_log'" \
|
||||
-c 'source .vimrc' \
|
||||
-c 'ChopsticksBetaLog' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq -- '- keep-existing-note' "$beta_log"
|
||||
before_sessions="$(grep -c '^## ' "$beta_log")"
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c "let g:chopsticks_beta_log = '$beta_log'" \
|
||||
-c 'source .vimrc' \
|
||||
-c 'ChopsticksBetaSession' \
|
||||
-c 'qa!' 2>&1
|
||||
after_sessions="$(grep -c '^## ' "$beta_log")"
|
||||
test "$after_sessions" -eq $((before_sessions + 1))
|
||||
grep -Fq -- '- keep-existing-note' "$beta_log"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N README.md \
|
||||
-c 'let g:chopsticks_keymap_style = "space"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'set filetype=markdown' \
|
||||
-c 'if maparg(",mt", "n") !~# "Toc" || maparg(",mp", "n") !~# "PrevimOpen" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N README.md \
|
||||
-c 'set filetype=markdown' \
|
||||
-c 'if &l:spell || &l:conceallevel != 0 || &l:signcolumn !=# "no" || exists("g:lsp_settings_filetype_markdown") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'if maparg("s", "n") !~# "easymotion-overwin-f2" | cquit | endif' \
|
||||
-c 'if maparg("<Space>w", "n") =~# "!" | cquit | endif' \
|
||||
-c 'if !&swapfile || !&writebackup || &directory !~# "\.vim/.swap" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:ale_fix_on_save = 0' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if g:ale_fix_on_save != 0 | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
truncate -s 11000000 "$TMP_ROOT/large.py"
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N "$TMP_ROOT/large.py" \
|
||||
-c 'set filetype=python' \
|
||||
-c 'if &l:syntax !=# "" || &l:undolevels != -1 || &l:swapfile || get(b:, "ale_enabled", 1) != 0 | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
mkdir -p "$TMP_ROOT/fake-bin" "$TMP_ROOT/c runner"
|
||||
cat > "$TMP_ROOT/fake-bin/gcc" <<'GCCEOF'
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
printf '%s\n' "$@" > "$GCC_ARGS"
|
||||
out=""
|
||||
while [ "$#" -gt 0 ]; do
|
||||
if [ "$1" = "-o" ]; then
|
||||
shift
|
||||
out="$1"
|
||||
fi
|
||||
shift || true
|
||||
done
|
||||
test -n "$out"
|
||||
printf '%s\n' '#!/usr/bin/env bash' 'exit 0' > "$out"
|
||||
chmod +x "$out"
|
||||
GCCEOF
|
||||
chmod +x "$TMP_ROOT/fake-bin/gcc"
|
||||
c_file="$TMP_ROOT/c runner/main.c"
|
||||
c_file_real="$(cd "$TMP_ROOT/c runner" && pwd -P)/main.c"
|
||||
printf '%s\n' 'int main(void) { return 0; }' > "$c_file"
|
||||
GCC_ARGS="$TMP_ROOT/gcc-args.txt" \
|
||||
PATH="$TMP_ROOT/fake-bin:$PATH" \
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" \
|
||||
vim -u .vimrc -i NONE -es -N "$c_file" \
|
||||
-c 'set filetype=c' \
|
||||
-c 'call feedkeys("\<Space>rr", "xt")' \
|
||||
-c 'qa!' 2>&1
|
||||
c_out="$(sed -n '2p' "$TMP_ROOT/gcc-args.txt")"
|
||||
test -n "$c_out"
|
||||
test "$c_out" != "/tmp/a.out"
|
||||
test ! -e "$c_out"
|
||||
grep -Fxq "$c_file_real" "$TMP_ROOT/gcc-args.txt"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE --startuptime "$TMP_ROOT/startup.log" \
|
||||
-es -N -c 'qa!' 2>/dev/null
|
||||
tail -1 "$TMP_ROOT/startup.log"
|
||||
STARTUP_MS="$(awk 'END { print $1 }' "$TMP_ROOT/startup.log")"
|
||||
awk -v ms="$STARTUP_MS" -v limit="$STARTUP_LIMIT_MS" \
|
||||
'BEGIN { if (ms > limit) exit 1 }'
|
||||
}
|
||||
|
||||
check_vim
|
||||
408
scripts/test.sh
408
scripts/test.sh
|
|
@ -1,31 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
# Project test runner. CI calls the same groups that maintainers can run locally.
|
||||
# Project test runner. CI calls the same groups maintainers can run locally.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/chopsticks-test-XXXXXX")"
|
||||
EMPTY_XDG="$TMP_ROOT/xdg-empty"
|
||||
STARTUP_LIMIT_MS="${STARTUP_LIMIT_MS:-150}"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMP_ROOT"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
cd "$ROOT"
|
||||
mkdir -p "$EMPTY_XDG"
|
||||
|
||||
step() {
|
||||
printf '\n==> %s\n' "$1"
|
||||
}
|
||||
|
||||
need() {
|
||||
command -v "$1" >/dev/null 2>&1 || {
|
||||
echo "Missing required command: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
|
|
@ -50,385 +28,17 @@ list_groups() {
|
|||
printf '%s\n' quick shell docs installer bootstrap vim all
|
||||
}
|
||||
|
||||
check_shell() {
|
||||
step "Shell syntax and lint"
|
||||
need bash
|
||||
bash -n install.sh
|
||||
bash -n get.sh
|
||||
bash -n scripts/test.sh
|
||||
test -x install.sh
|
||||
test -x get.sh
|
||||
test -x scripts/test.sh
|
||||
|
||||
need shellcheck
|
||||
shellcheck install.sh get.sh scripts/test.sh
|
||||
}
|
||||
|
||||
check_docs() {
|
||||
step "Markdown lint"
|
||||
need markdownlint
|
||||
markdownlint README.md QUICKSTART.md CONTRIBUTING.md CHANGELOG.md
|
||||
}
|
||||
|
||||
check_installer_modes() {
|
||||
step "Installer profile-only modes"
|
||||
XDG_CONFIG_HOME="$TMP_ROOT/dry" ./install.sh --dry-run --profile=full \
|
||||
| tee "$TMP_ROOT/install-dry-run.txt"
|
||||
grep -q 'Profile: full' "$TMP_ROOT/install-dry-run.txt"
|
||||
test ! -e "$TMP_ROOT/dry/chopsticks.vim"
|
||||
|
||||
XDG_CONFIG_HOME="$TMP_ROOT/config" ./install.sh --configure-only --profile=minimal
|
||||
grep -q "let g:chopsticks_profile = 'minimal'" "$TMP_ROOT/config/chopsticks.vim"
|
||||
|
||||
XDG_CONFIG_HOME="$TMP_ROOT/config" ./install.sh --configure-only --profile=full
|
||||
grep -q "let g:chopsticks_profile = 'full'" "$TMP_ROOT/config/chopsticks.vim"
|
||||
|
||||
XDG_CONFIG_HOME="$TMP_ROOT/default" ./install.sh --configure-only --yes
|
||||
grep -q "let g:chopsticks_profile = 'engineer'" "$TMP_ROOT/default/chopsticks.vim"
|
||||
}
|
||||
|
||||
check_bootstrap() {
|
||||
step "Bootstrap dry-run safety"
|
||||
CHOPSTICKS_DEST="$TMP_ROOT/bootstrap" ./get.sh --dry-run --profile=minimal \
|
||||
| tee "$TMP_ROOT/get-dry-run.txt"
|
||||
grep -q 'Would clone' "$TMP_ROOT/get-dry-run.txt"
|
||||
test ! -e "$TMP_ROOT/bootstrap"
|
||||
|
||||
mkdir -p "$TMP_ROOT/no-git-bin"
|
||||
printf '%s\n' \
|
||||
'#!/usr/bin/env bash' \
|
||||
"echo \"brew was called\" >> \"\$BREW_LOG\"" \
|
||||
'exit 42' > "$TMP_ROOT/no-git-bin/brew"
|
||||
chmod +x "$TMP_ROOT/no-git-bin/brew"
|
||||
BREW_LOG="$TMP_ROOT/no-git-brew.log" \
|
||||
PATH="$TMP_ROOT/no-git-bin" \
|
||||
CHOPSTICKS_DEST="$TMP_ROOT/no-git-bootstrap" \
|
||||
/bin/bash ./get.sh --dry-run --profile=full \
|
||||
| tee "$TMP_ROOT/get-no-git-dry-run.txt"
|
||||
grep -q 'Would require: git' "$TMP_ROOT/get-no-git-dry-run.txt"
|
||||
grep -q 'Would clone' "$TMP_ROOT/get-no-git-dry-run.txt"
|
||||
test ! -e "$TMP_ROOT/no-git-brew.log"
|
||||
test ! -e "$TMP_ROOT/no-git-bootstrap"
|
||||
|
||||
mkdir -p "$TMP_ROOT/not-chopsticks"
|
||||
git -c init.defaultBranch=main init "$TMP_ROOT/not-chopsticks" >/dev/null
|
||||
git -C "$TMP_ROOT/not-chopsticks" remote add origin https://github.com/example/not-chopsticks.git
|
||||
if CHOPSTICKS_DEST="$TMP_ROOT/not-chopsticks" ./get.sh --dry-run; then
|
||||
echo "Expected get.sh to reject non-chopsticks repo" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$TMP_ROOT/chopsticks-existing"
|
||||
git -c init.defaultBranch=main init "$TMP_ROOT/chopsticks-existing" >/dev/null
|
||||
git -C "$TMP_ROOT/chopsticks-existing" remote add origin https://github.com/m1ngsama/chopsticks.git
|
||||
touch "$TMP_ROOT/chopsticks-existing/install.sh" "$TMP_ROOT/chopsticks-existing/.vimrc"
|
||||
CHOPSTICKS_DEST="$TMP_ROOT/chopsticks-existing" ./get.sh --dry-run --yes \
|
||||
| tee "$TMP_ROOT/get-existing.txt"
|
||||
grep -q 'Would update existing chopsticks repo' "$TMP_ROOT/get-existing.txt"
|
||||
}
|
||||
|
||||
check_plugin_dirs() {
|
||||
step "Plugin directories"
|
||||
for plugin in \
|
||||
fzf fzf.vim vim-fugitive vim-gitgutter ale vim-lsp vim-lsp-settings \
|
||||
asyncomplete.vim asyncomplete-lsp.vim vim-markdown
|
||||
do
|
||||
test -d "$HOME/.vim/plugged/$plugin" || {
|
||||
echo "Missing plugin directory: $plugin" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
}
|
||||
|
||||
check_vim() {
|
||||
step "Vim smoke tests"
|
||||
need vim
|
||||
check_plugin_dirs
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N -c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c "redir! > $TMP_ROOT/plugs.txt" \
|
||||
-c 'silent echo len(g:plugs)' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>/dev/null
|
||||
PLUGS="$(tr -d '[:space:]' < "$TMP_ROOT/plugs.txt")"
|
||||
echo "Plugins registered: $PLUGS"
|
||||
if [ "$PLUGS" -lt 20 ]; then
|
||||
echo "Expected 20+ plugins, got $PLUGS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$TMP_ROOT/chopsticks path/modules"
|
||||
cp .vimrc "$TMP_ROOT/chopsticks path/.vimrc"
|
||||
cp modules/*.vim "$TMP_ROOT/chopsticks path/modules/"
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u "$TMP_ROOT/chopsticks path/.vimrc" \
|
||||
-i NONE -es -N -c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_profile = "minimal"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if has_key(g:plugs, "ale") || has_key(g:plugs, "vim-lsp") || has_key(g:plugs, "vim-lsp-settings") || has_key(g:plugs, "asyncomplete.vim") || has_key(g:plugs, "auto-pairs") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
mkdir -p "$TMP_ROOT/local"
|
||||
printf "%s\n" "let g:chopsticks_profile = 'minimal'" > "$TMP_ROOT/local/config.vim"
|
||||
vim -u NONE -i NONE -es -N \
|
||||
-c "let g:chopsticks_local_config = '$TMP_ROOT/local/config.vim'" \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if g:chopsticks_profile !=# "minimal" || has_key(g:plugs, "ale") || has_key(g:plugs, "vim-lsp") || has_key(g:plugs, "auto-pairs") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
mkdir -p "$TMP_ROOT/xdg"
|
||||
printf "%s\n" "let g:chopsticks_profile = 'minimal'" > "$TMP_ROOT/xdg/chopsticks.vim"
|
||||
XDG_CONFIG_HOME="$TMP_ROOT/xdg" vim -u NONE -i NONE -es -N \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if g:chopsticks_profile !=# "minimal" || has_key(g:plugs, "ale") || has_key(g:plugs, "vim-lsp") || has_key(g:plugs, "auto-pairs") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'ChopsticksStatus' \
|
||||
-c "redir! > $TMP_ROOT/status-default.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
if grep -Fq 'vim-lsp not loaded' "$TMP_ROOT/status-default.txt"; then
|
||||
cat "$TMP_ROOT/status-default.txt"
|
||||
exit 1
|
||||
fi
|
||||
grep -Fq 'OK vim-lsp stack (installed)' "$TMP_ROOT/status-default.txt"
|
||||
grep -Fq 'python (:LspInstallServer in a python file)' "$TMP_ROOT/status-default.txt"
|
||||
grep -Fq 'LSP actions are buffer-local and start after a server attaches.' "$TMP_ROOT/status-default.txt"
|
||||
grep -Fq 'Open that filetype and run :LspInstallServer once.' "$TMP_ROOT/status-default.txt"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'let last_change_map = nr2char(96) . "[v" . nr2char(96) . "]"' \
|
||||
-c 'if maparg("0", "n") !=# "" || maparg("0", "v") !=# "" || maparg("Y", "n") !=# "" || maparg("Q", "n") !=# "" || maparg("<Space>", "n") !=# "" || maparg("//", "v") !=# "" || maparg("gV", "n") !=# "" || maparg("jk", "i") !=# "" || maparg("<C-s>", "n") !=# "" || maparg("<C-s>", "i") !=# "" || maparg("<C-h>", "n") !=# "" || maparg("<C-j>", "n") !=# "" || maparg("<C-k>", "n") !=# "" || maparg("<C-l>", "n") !=# "" || maparg("<C-p>", "n") !=# "" || maparg("<C-p>", "c") !=# "" || maparg("<C-n>", "c") !=# "" || maparg("w!!", "c") !=# "" | cquit | endif' \
|
||||
-c 'if has_key(g:plugs, "auto-pairs") || maparg("<Tab>", "i") =~# "pumvisible" || maparg("<S-Tab>", "i") =~# "pumvisible" || maparg("<CR>", "i") =~# "asyncomplete#close_popup" || maparg("<CR>", "i") =~# "AutoPairs" | cquit | endif' \
|
||||
-c 'if maparg("<Esc><Esc>", "t") !=# "" || maparg("<C-h>", "t") !=# "" || maparg("<C-j>", "t") !=# "" || maparg("<C-k>", "t") !=# "" || maparg("<C-l>", "t") !=# "" | cquit | endif' \
|
||||
-c 'if maparg(",/", "v") !~# "escape" || maparg(",v", "n") !=# last_change_map || maparg(",ff", "n") !~# "SmartFiles" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_enable_jk_escape = 1' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if maparg("jk", "i") !~# "<Esc>" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_enable_ctrl_s_save = 1' \
|
||||
-c 'let g:chopsticks_enable_sudo_save_bang = 1' \
|
||||
-c 'let g:chopsticks_enable_completion_keymaps = 1' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if maparg("<C-s>", "n") !~# ":w" || maparg("<C-s>", "i") !~# ":w" || maparg("w!!", "c") !~# "sudo tee" | cquit | endif' \
|
||||
-c 'if maparg("<Tab>", "i") !~# "pumvisible" || maparg("<S-Tab>", "i") !~# "pumvisible" || maparg("<CR>", "i") !~# "asyncomplete#close_popup" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_enable_auto_pairs = 1' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if !has_key(g:plugs, "auto-pairs") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_enable_terminal_keymaps = 1' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if has("terminal") && (maparg("<Esc><Esc>", "t") !~# "<C-\\\\><C-N>" || maparg("<C-h>", "t") !~# "<C-W>h" || maparg("<C-j>", "t") !~# "<C-W>j" || maparg("<C-k>", "t") !~# "<C-W>k" || maparg("<C-l>", "t") !~# "<C-W>l") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'if &exrc || &secure | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_enable_exrc = 1' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if !&exrc || !&secure | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'if maparg(",F", "n") !=# "" | cquit | endif' \
|
||||
-c 'if maparg(",F", "v") !~# "=" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_enable_reindent_file = 1' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if maparg(",F", "n") !~# "gg=G" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
TERM=xterm-256color XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'if g:is_tty || &ttimeoutlen != 10 | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
TERM=linux XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'if !g:is_tty || &ttimeoutlen != 50 | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'if !exists("loaded_gzip") || !exists("loaded_logiPat") || !exists("loaded_rrhelper") || !exists("loaded_spellfile_plugin") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'silent! delcommand LspStatus' \
|
||||
-c 'silent! delcommand LspInstallServer' \
|
||||
-c 'ChopsticksStatus' \
|
||||
-c "redir! > $TMP_ROOT/status-lsp-not-loaded.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq 'OK vim-lsp stack (installed; not loaded yet)' "$TMP_ROOT/status-lsp-not-loaded.txt"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_profile = "minimal"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'ChopsticksStatus' \
|
||||
-c "redir! > $TMP_ROOT/status-minimal.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq 'off vim-lsp stack (LSP disabled by profile)' "$TMP_ROOT/status-minimal.txt"
|
||||
grep -Fq 'off python (LSP disabled by profile)' "$TMP_ROOT/status-minimal.txt"
|
||||
if grep -Fq 'LSP actions are buffer-local' "$TMP_ROOT/status-minimal.txt"; then
|
||||
cat "$TMP_ROOT/status-minimal.txt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$TMP_ROOT/missing-home/.vim/autoload"
|
||||
cp "$HOME/.vim/autoload/plug.vim" "$TMP_ROOT/missing-home/.vim/autoload/plug.vim"
|
||||
HOME="$TMP_ROOT/missing-home" XDG_CONFIG_HOME="$EMPTY_XDG" \
|
||||
vim -u .vimrc -i NONE -es -N \
|
||||
-c 'ChopsticksStatus' \
|
||||
-c "redir! > $TMP_ROOT/status-missing-plugin.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq 'vim-lsp not installed; run :PlugInstall' "$TMP_ROOT/status-missing-plugin.txt"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'doautocmd User lsp_buffer_enabled' \
|
||||
-c 'if maparg("gd", "n") !=# "" || maparg("K", "n") !=# "" || maparg("gi", "n") !=# "" || maparg("gr", "n") !=# "" | cquit | endif' \
|
||||
-c 'if maparg(",dd", "n") !~# "lsp-definition" | cquit | endif' \
|
||||
-c 'if maparg(",dt", "n") !~# "lsp-type-definition" | cquit | endif' \
|
||||
-c 'if maparg(",di", "n") !~# "lsp-implementation" | cquit | endif' \
|
||||
-c 'if maparg(",dr", "n") !~# "lsp-references" | cquit | endif' \
|
||||
-c 'if maparg(",dk", "n") !~# "lsp-hover" | cquit | endif' \
|
||||
-c 'if maparg(",dp", "n") !~# "lsp-previous-diagnostic" | cquit | endif' \
|
||||
-c 'if maparg(",dn", "n") !~# "lsp-next-diagnostic" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'normal ,?' \
|
||||
-c "redir! > $TMP_ROOT/cheat-default.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
grep -Fq ':ChopsticksStatus check LSP setup' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq ',ff files' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq ',dd definition' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq ',dk hover docs' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq ',dp ,dn LSP diagnostics' "$TMP_ROOT/cheat-default.txt"
|
||||
grep -Fq '<C-w>hjkl navigate splits' "$TMP_ROOT/cheat-default.txt"
|
||||
if grep -Eq 'Ctrl\\+p find file|Ctrl\\+hjkl navigate splits|Ctrl\\+s save|jk exit insert|gd definition|K hover docs|\\[g \\]g LSP diagnostics' "$TMP_ROOT/cheat-default.txt"; then
|
||||
cat "$TMP_ROOT/cheat-default.txt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:chopsticks_profile = "minimal"' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'normal ,?' \
|
||||
-c "redir! > $TMP_ROOT/cheat.txt" \
|
||||
-c 'silent %print' \
|
||||
-c 'redir END' \
|
||||
-c 'qa!' 2>&1
|
||||
if grep -Eq 'definition|LspInstallServer|ALE errors|undo tree|markdown preview' "$TMP_ROOT/cheat.txt"; then
|
||||
cat "$TMP_ROOT/cheat.txt"
|
||||
exit 1
|
||||
fi
|
||||
grep -q ',cr run file' "$TMP_ROOT/cheat.txt"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N README.md \
|
||||
-c 'set filetype=markdown' \
|
||||
-c 'if &l:spell || &l:conceallevel != 0 || &l:signcolumn !=# "no" || exists("g:lsp_settings_filetype_markdown") | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N \
|
||||
-c 'if maparg("s", "n") !=# "" | cquit | endif' \
|
||||
-c 'if maparg(",w", "n") =~# "!" | cquit | endif' \
|
||||
-c 'if !&swapfile || !&writebackup || &directory !~# "\.vim/.swap" | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u NONE -i NONE -es -N \
|
||||
-c 'let g:ale_fix_on_save = 0' \
|
||||
-c 'source .vimrc' \
|
||||
-c 'if g:ale_fix_on_save != 0 | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
truncate -s 11000000 "$TMP_ROOT/large.py"
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE -es -N "$TMP_ROOT/large.py" \
|
||||
-c 'set filetype=python' \
|
||||
-c 'if &l:syntax !=# "" || &l:undolevels != -1 || &l:swapfile || get(b:, "ale_enabled", 1) != 0 | cquit | endif' \
|
||||
-c 'qa!' 2>&1
|
||||
|
||||
mkdir -p "$TMP_ROOT/fake-bin" "$TMP_ROOT/c runner"
|
||||
cat > "$TMP_ROOT/fake-bin/gcc" <<'GCCEOF'
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
printf '%s\n' "$@" > "$GCC_ARGS"
|
||||
out=""
|
||||
while [ "$#" -gt 0 ]; do
|
||||
if [ "$1" = "-o" ]; then
|
||||
shift
|
||||
out="$1"
|
||||
fi
|
||||
shift || true
|
||||
done
|
||||
test -n "$out"
|
||||
printf '%s\n' '#!/usr/bin/env bash' 'exit 0' > "$out"
|
||||
chmod +x "$out"
|
||||
GCCEOF
|
||||
chmod +x "$TMP_ROOT/fake-bin/gcc"
|
||||
c_file="$TMP_ROOT/c runner/main.c"
|
||||
c_file_real="$(cd "$TMP_ROOT/c runner" && pwd -P)/main.c"
|
||||
printf '%s\n' 'int main(void) { return 0; }' > "$c_file"
|
||||
GCC_ARGS="$TMP_ROOT/gcc-args.txt" \
|
||||
PATH="$TMP_ROOT/fake-bin:$PATH" \
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" \
|
||||
vim -u .vimrc -i NONE -es -N "$c_file" \
|
||||
-c 'set filetype=c' \
|
||||
-c 'normal ,cr' \
|
||||
-c 'qa!' 2>&1
|
||||
c_out="$(sed -n '2p' "$TMP_ROOT/gcc-args.txt")"
|
||||
test -n "$c_out"
|
||||
test "$c_out" != "/tmp/a.out"
|
||||
test ! -e "$c_out"
|
||||
grep -Fxq "$c_file_real" "$TMP_ROOT/gcc-args.txt"
|
||||
|
||||
XDG_CONFIG_HOME="$EMPTY_XDG" vim -u .vimrc -i NONE --startuptime "$TMP_ROOT/startup.log" \
|
||||
-es -N -c 'qa!' 2>/dev/null
|
||||
tail -1 "$TMP_ROOT/startup.log"
|
||||
STARTUP_MS="$(awk 'END { print $1 }' "$TMP_ROOT/startup.log")"
|
||||
awk -v ms="$STARTUP_MS" -v limit="$STARTUP_LIMIT_MS" \
|
||||
'BEGIN { if (ms > limit) exit 1 }'
|
||||
}
|
||||
|
||||
run_group() {
|
||||
case "$1" in
|
||||
quick)
|
||||
check_shell
|
||||
check_docs
|
||||
check_installer_modes
|
||||
check_bootstrap
|
||||
quick | shell | docs | installer | bootstrap)
|
||||
bash "$SCRIPT_DIR/test-quick.sh" "$1"
|
||||
;;
|
||||
vim)
|
||||
bash "$SCRIPT_DIR/test-vim.sh"
|
||||
;;
|
||||
shell) check_shell ;;
|
||||
docs) check_docs ;;
|
||||
installer) check_installer_modes ;;
|
||||
bootstrap) check_bootstrap ;;
|
||||
vim) check_vim ;;
|
||||
all)
|
||||
run_group quick
|
||||
check_vim
|
||||
bash "$SCRIPT_DIR/test-quick.sh" quick
|
||||
bash "$SCRIPT_DIR/test-vim.sh"
|
||||
;;
|
||||
list | --list) list_groups ;;
|
||||
-h | --help) usage ;;
|
||||
|
|
|
|||
Loading…
Reference in a new issue