Skip to content

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.

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