Skip to content
gig3mPublic

About

A browser Vim tutor: short lessons, a real Vim engine, and the Neovim plugins people use

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

vimchi

vimchi

A browser Vim tutor, inspired by vim-hero's short-lesson format — small lessons of 1–4 keys, each with one practice challenge — that goes deeper: registers, macros, ex commands, regex, windows, quickfix, and the Neovim plugins people actually run. The lesson plan is in CURRICULUM.md.

The Quotes lesson: the sidebar lists the Text Objects section, and the practice editor is mid-ci", in Insert mode renaming a package.json string to "vimchi" with the remaining text hinted above the cursor

Frontend

npm install
npm run dev        # http://localhost:5317
npm test           # engine + every lesson's reference solution
npm run build      # typecheck + production bundle in dist/
  • src/vim/ — a Vim engine written from scratch in TypeScript (modes, operators, text objects, registers, macros, dot-repeat, undo, Vim regex, ex commands, splits, quickfix, folds) plus plugin emulations in src/vim/plugins/.
  • src/lessons/ — lesson content (sections/*.tsx), the challenge runtime, and the validator. Authoring guide: docs/LESSONS.md; plugin API: docs/PLUGINS.md.
  • src/components/ — the UI.

Checking against real Neovim

npm run check:nvim (needs nvim on PATH) replays lessons and ad-hoc cases in nvim --clean --headless:

  • ok N bad M — every plain-text round's reference solution must leave the goal text in Neovim. This is the gate: the script fails when bad is not 0.
  • extended: ok N bad M — the engine's own result for each round against Neovim's: cursor (Neovim defaults, nostartofline), the unnamed register's text and type, any register the goal names, and the text/cursor after follow-up probes (x, plus p when the register is non-empty) fed after the solution.
  • cases: ok N bad M — the same engine-vs-Neovim comparison for scripts/nvimcheck/cases.json, a list of {id, text, keys, cursor?, name?, options?} repros that are not lesson rounds. Append to it freely.

All three sections gate the run (strict by default); NVIMCHECK_STRICT=0 npm run check:nvim relaxes it to the text gate only.

Seeing what the learner sees

Tests check the text; node scripts/look.mjs checks the screen. It plays a lesson in headless Chromium (starting vite on 5317 if it is not up) and writes screenshots of the practice panel plus an index.md to look/<lesson>/ (gitignored), so a person or an agent can read them:

  • node scripts/look.mjs visual-lines: each round's opening screen, each step, the live hints, and the results, playing the lesson's own solutions.
  • --keys "|||jyy3j2dd\"0p|": play your own keys per round (Vim notation, | between rounds, empty = the solution), e.g. to see what the coach says about a route that skips the lesson's key. Fewer entries than rounds stops after the last one with a shot of the screen as it stands (--keys "f=cl=": round 1 half typed).
  • --rounds 2,5: shots for those rounds only. --out dir: somewhere else.
  • --sweep: every round's opening screen in every lesson, to look/sweep/.

Keys go through the editor's own keydown handler. In dev builds window.__vimchi exposes the lessons, parseKeys and the live session for it. When the app carries the cursor over from the last round, the harness puts it back at the round's start (and scroll) before playing a solution, since solutions start there; your own --keys are played from wherever the cursor was carried.

Browsers reserve Ctrl-W/N/T/Q. The practice editor maps Alt-W/N/T/Q to them, and its "full screen" button uses the Keyboard Lock API to capture the real keys (Chromium).

Running the server

The Go backend in server/ handles GitHub sign-in, syncs lesson runs to SQLite, and serves the built SPA. It needs Go 1.26+ and no cgo.

cd server
cp .env.example .env   # fill in GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET
set -a; . ./.env; set +a
go run ./cmd/vimchi
  • Dev: run npm run dev alongside it; Vite proxies /api and /auth to :8080. Set the GitHub OAuth app's callback to $VIMCHI_BASE_URL/auth/github/callback (the Vite origin).
  • Prod: npm run build, then run the server with VIMCHI_STATIC pointing at dist/, VIMCHI_BASE_URL set to the public origin and VIMCHI_SECURE_COOKIES=1 if TLS terminates at a proxy.

Tests: cd server && go test ./...

The frontend also works on its own as a static site (dist/): without the server, progress stays in the browser's localStorage and sign-in is unavailable.

Deploying

The only instance is the public one, https://vimchi.dev, on a DigitalOcean droplet: see deploy/do/README.md. deploy/do/deploy.sh builds the image here (Dockerfile builds the SPA with node and the server as a static Go binary into one image), ships it over SSH, restarts and smoke-tests. GET /healthz is the uptime probe (200 {"ok":true} when SQLite answers).

docker/docker-compose.yml + docker/up.sh run the same image locally on the nginx-proxy-manager_default network with SQLite bind-mounted at var/ (no host port). The tailnet instance that used them (vimchi.nrsil.io) was torn down on 2026-09-30; they remain as a local/staging recipe. up.sh injects VIMCHI_GITHUB_CLIENT_ID / VIMCHI_GITHUB_CLIENT_SECRET from keys when they exist; without them the tutor runs with sign-in disabled.

Credits

The plugin lessons emulate the default keymaps (LazyVim's where the starters differ, such as mini.surround on gsa / gsd / gsr; flash.nvim is on in every lesson) of mini.surround, mini.ai and nvim-treesitter-textobjects, flash.nvim, telescope.nvim, snacks.explorer and neo-tree, gitsigns.nvim, lazygit, grug-far.nvim and a LuaSnip/blink.cmp-style completion and snippet flow; their code is not included. Fonts (IBM Plex Sans, JetBrains Mono, Space Grotesk) load from Google Fonts under the SIL Open Font License.

Challenge files (src/challenges/corpus/) are short excerpts from these repos, used under their licenses, attributed per file, with their license texts shipped at /THIRD-PARTY-NOTICES.txt (public/THIRD-PARTY-NOTICES.txt): TheAlgorithms/Go (MIT), TheAlgorithms/TypeScript (MIT), charmbracelet/lipgloss (MIT), echasnovski/mini.nvim (MIT), google/uuid (BSD-3-Clause), lewis6991/gitsigns.nvim (MIT), stevearc/oil.nvim (MIT), unjs/ufo (MIT).

License

MIT — see LICENSE.

About

A browser Vim tutor: short lessons, a real Vim engine, and the Neovim plugins people use

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages