A Vim9-native Lean 4 environment. lean.vim began as a port of
lean.nvim and keeps its command
names, mapping conventions, and configuration shape, so shared dotfiles and
muscle memory carry over — but the two projects have since diverged: this
one is self-contained where lean.nvim composes with the Neovim ecosystem,
adds some behavior of its own, and deliberately stops short of the
interactive widget UI provided by lean.nvim. See
Relationship to lean.nvim for the specifics.
It runs Lean's language server directly over Vim jobs and channels; Neovim and an external LSP client are not required.
This project owes an enormous debt to Julian Berman and the lean.nvim community. See Acknowledgements for our thanks and the specific upstream work that made the original port possible.
The initial port was reviewed against lean.nvim main at
e05c0f821412337259b98cc732ff0cf6ac7afe0c (2026-07-30); development has been
independent since.
The most recent upstream review covered
ba5663698b76677c22f5602cdbb37ab4bade1072 (2026-10-15). The October update
incorporates its Unicode data, infoview folding, and sign-column improvements;
exact source revisions remain recorded in NOTICE.
New here? Start with the usage guide for a task-oriented
tour, or :help lean-vim9 for the full reference.
- Vim 9.2, or a late Vim 9.1 that provides
uri_encode(), built with+vim9script,+job,+channel,+popupwin,+textprop,+signs, and+timers(the plugin checks at startup and reports clearly when a build is too old) - Lean 4 on
PATH(normally installed withelan) lakeonPATHfor Lake projects
The live suite is checked against Lean 4.33.1, 4.34.1, and the current stable release (4.35.0 at this update), using Vim 9.2.0850. Lint, the offline suite, and live Lean 4.35.0 checks also pass with Vim 9.2.1202.
Put this checkout on Vim's runtimepath. Vim's native package layout is the
simplest option:
mkdir -p ~/.vim/pack/plugins/start
git clone https://github.com/SamuelSchlesinger/lean.vim.git ~/.vim/pack/plugins/start/lean.vimEnable filetype plugins and syntax in .vimrc if they are not already enabled:
filetype plugin indent on
syntax enableOpening a *.lean file starts lake serve for a Lake project and
lean --server for a standalone file. The project root is the nearest
lakefile.toml, lakefile.lean, lean-toolchain, or .git marker.
Set g:lean_config before the plugin loads. Suggested mappings are opt-in:
let g:lean_config = {
\ 'mappings': v:true,
\ 'infoview': {
\ 'autoopen': v:true,
\ 'orientation': 'auto',
\ 'width': 50,
\ 'height': 12,
\ },
\ 'abbreviations': {
\ 'leader': '\',
\ 'extra': {'wknight': '♘'},
\ },
\ }The main options are:
| Option | Default | Meaning |
|---|---|---|
mappings |
v:false |
Enable the suggested buffer-local mappings |
abbreviations.enable |
v:true |
Expand Lean Unicode abbreviations |
abbreviations.leader |
\ |
Single-character prefix used for abbreviations |
completion.enable |
v:true |
Install the LSP omnifunc for Lean buffers |
completion.autotrigger |
v:false |
As-you-type popup completion (identifier characters and .); off by default to spare the server while typing |
completion.set_completeopt |
v:true |
Apply completeopt=menuone,noinsert,noselect,popup buffer-locally |
inlay_hints.enable |
v:true |
Render LSP inlay hints as virtual text over the visible range |
infoview.autoopen |
v:true |
Open the plain-text infoview for Lean buffers |
infoview.orientation |
auto |
auto, vertical, or horizontal |
infoview.update_cooldown |
50 |
Throttle cursor-driven refreshes in milliseconds (0 disables throttling) |
infoview.no_goals_text |
Goals accomplished 🎉 |
Text shown when the goal list is empty |
loogle.enable |
v:false |
Allow the Loogle commands to query loogle.lean-lang.org (the only network feature) |
lsp.enable |
v:true |
Start the built-in Lean-specific LSP client |
lsp.command |
[] |
Override server argv, or supply a root-to-argv function |
lsp.change_delay |
50 |
Trailing debounce for edit bursts; isolated edits flush immediately |
progress_bars.enable |
v:true |
Show processing ranges in the sign column |
semantic_highlighting.enable |
v:true |
Render LSP semantic tokens as text properties |
semantic_highlighting.links |
{} |
Per-token-type highlight overrides; variable and property are unstyled by default |
signs.enable |
v:true |
Render diagnostic and Lean goal signs |
The original infoview.update_delay name remains accepted as an alias for
infoview.update_cooldown.
Run :help lean-vim9-configuration for all accepted keys.
The usage guide walks through all of this in workflow order; the highlights follow.
The default suggested mappings follow lean.nvim where there is an equivalent:
| Mapping | Action |
|---|---|
<LocalLeader>i |
Toggle the infoview |
<LocalLeader>p |
Pause or resume infoview updates |
<LocalLeader>x / <LocalLeader>c |
Add / clear goal pins |
<LocalLeader>dx / <LocalLeader>dc |
Set / clear a textual diff pin |
<LocalLeader>dd |
Toggle automatic diff pins |
<LocalLeader><Tab> |
Move between source and infoview |
<LocalLeader>\ |
Reverse-lookup the symbol under the cursor |
<LocalLeader>r |
Restart elaboration for the current file |
<LocalLeader>g |
Show the current goal in a popup |
<LocalLeader>a |
Select a code action |
K |
Show hover information |
All mappings target <Plug> names, so users can bind their own keys without
enabling the suggested set.
The most useful commands are:
:LeanGoal,:LeanTermGoal,:LeanLineDiagnostics,:LeanDiagnosticsList,:LeanHover:LeanInfoviewToggle,:LeanInfoviewAddPin,:LeanInfoviewClearPins:LeanRestartFile,:LeanRestartServer,:LeanStatus,:LeanHealth:LeanDefinition,:LeanDeclaration,:LeanReferences,:LeanRename:LeanCodeAction,:LeanSorryFill,:LeanOutline,:LeanWorkspaceSymbols:LeanModuleImports,:LeanModuleImportedBy,:LeanSearchPaths:LeanInlayHintsToggle,:LeanLoogle/:LeanLooglePopup(opt-in),:LeanAbbreviationsReverseLookup
Completion works out of the box with <C-x><C-o>. Enable
completion.autotrigger for identifier and dot completion as you type.
Both modes run asynchronously, load documentation for the selected item,
and apply its text edits as one undoable change. Theorems are tagged t in
the menu. Inlay hints render as
virtual text over the visible portion of the file and follow scrolling.
Inside the infoview, <CR> jumps the source window to the entry under the
cursor (position header, pin, or diagnostic), and the view is syntax
highlighted. A statusline component is available with
set statusline+=%{lean#StatuslineProgress()}.
Unicode abbreviations expand on Space, Tab, Enter, or leaving Insert mode. The full abbreviation table is synchronized from lean.nvim's VS Code data. While an abbreviation is in progress, completion stays out of the way. Lean buffers reserve a sign column to keep diagnostics from shifting the text. The infoview uses manual folding, so source-window folding rules cannot hide goals or diagnostics.
lean.vim began as a faithful port and stays compatible on the surface:
command names, <LocalLeader> mapping conventions, and configuration keys
mirror lean.nvim wherever both projects implement a feature. Core editing is
implemented directly in Vim9: server lifecycle, document synchronization,
completion, inlay hints, goals, term goals, diagnostics, progress, semantic
tokens, hover/navigation, code actions, workspace edits, pins,
abbreviations, syntax, indentation, snippets, document/workspace symbols,
and module queries.
Since the initial port the projects have diverged in both directions.
Where lean.vim goes its own way:
- Self-contained where lean.nvim delegates. Completion (async omnifunc
plus as-you-type popup) and inlay hints are built in; lean.nvim relies on
Neovim's LSP client and a completion plugin for those. The LSP transport
itself is a small, auditable Vim9 client rather than
vim.lsp. - Rendering scaled for large files. Progress signs and inlay hints decorate only the visible range and follow scrolling, and a failed server start backs off instead of retrying on every window switch.
- Additions without an upstream equivalent:
:LeanHealth, a Telescope-free opt-in Loogle commands, andlean#StatuslineProgress().
Where lean.nvim remains ahead — deliberately not emulated here:
- ProofWidgets, clickable subexpressions, widget tooltips, and terminal
graphics need Neovim's extmark and Lua UI substrate. The infoview uses
Lean's stable
$/lean/plainGoaland$/lean/plainTermGoalrequests, with plain text, syntax highlighting, and<CR>jumps instead of an interactive render tree. Live pins track source edits and refresh their goals through the same plain-goal interface. - Telescope and satellite.nvim integrations are replaced by quickfix, location lists, and the sign column.
Widget commands exist for command-name compatibility and explain this boundary when invoked.
The transport is intentionally self-contained — depending on a second Vim LSP plugin would tie goal requests, restart behavior, and Lean-specific notifications to a third party's extension hooks.
autoload/lean/lsp.vim: transport, project sessions, document synchronization, and the stable client API.documents.vimandrequests.vim: document lifetimes, revision snapshots, request cancellation, and checks before delivering asynchronous results.decorations.vim,semantic.vim, andworkspace.vim: diagnostic/progress rendering, semantic highlighting, and checked workspace-edit transactions.infoview.vim,infoview_render.vim, andpins.vim: view state and windows, pure text rendering, and live source anchors.completion.vim,inlayhints.vim,abbreviations.vim, andeditor.vim: editing features;health.vimandloogle.vim: diagnostics and opt-in search.autoload/lean.vim: commands, mappings, and feature lifecycle coordination.
The import graph is acyclic and checked by make lint. See
ARCHITECTURE.md for ownership rules and extension points.
make lint # compile every Vim9 module
make test # fake-server transport and editor integration
make test-live # end-to-end check against the installed Lean server
make license-check # verify pinned third-party files and required notices
make hooks # run lint and the offline suite before commits and pushesCI runs make lint, make test, and make test-live on Linux and macOS for
every push and pull request, with Lean 4.33.1, 4.34.1, and stable
(.github/workflows/test.yml). The stable job picks up new Lean releases
without changing a project's lean-toolchain. make hooks points
core.hooksPath at .githooks/ so the same lint, license, and headless
test checks run locally before every commit and push (skip with --no-verify
when needed). Local hooks keep the live tests
optional, since they need an installed Lean toolchain.
Use ELAN_TOOLCHAIN=leanprover/lean4:v4.35.0 make test-live to select an
installed toolchain. Set LEAN_VIM_TEST_VIM=/path/to/vim to check another Vim
build, or LEAN_VIM_TEST_TIMEOUT to override the per-script timeout in seconds.
For an uninstalled Vim source build, also set VIMRUNTIME to its runtime/
directory.
The fake server supports named scenarios and explicit release of held replies; asynchronous tests can choose delivery order without server sleeps. The headless runner catches script errors and limits each test to two minutes, terminating the test's process group on Linux and macOS. See test/support/README.md for the harness.
The headless tests cover split JSON-RPC frames, full pipes in both directions, file reloads, initialization and graceful shutdown, request cancellation, failed-start backoff, diagnostics, progress (including large-file sign capping), goals, term goals, semantic tokens, completion (async popup, UTF-16 textEdits, cancellation, resolve, abbreviation interplay), inlay hints, infoview jumping, document and workspace symbols, UTF-16 incremental changes, restart races, workspace-edit preflight, live pin tracking, paused pins, stale-edit rejection, crash recovery across buffers, tab and ftplugin lifecycle, indentation, and Unicode abbreviation insertion. The live test checks tactic and term goals (including Unicode lambda proofs and edits during document debouncing), live pin movement and goal refresh, completion acceptance, diagnostic updates, Lake startup, and search paths against real Lean processes.
The port and the retained lean.nvim syntax/snippet material are MIT-licensed. The Unicode abbreviation data is Apache-2.0. See NOTICE for exact file provenance and ACKNOWLEDGEMENTS.md for project credit.