neovim/neovim
Snippets
Purpose
vim.snippet parses LSP snippet grammar strings, expands them at the cursor with placeholders, and lets the user tab through the placeholders. It is invoked automatically when an LSP completion item carries insertTextFormat = Snippet, and is callable directly via vim.snippet.expand(body).
Directory layout
runtime/lua/vim/
├── snippet.lua (~22k bytes) Public API + state machine
runtime/lua/vim/lsp/
└── _snippet_grammar.lua (~6k bytes) Pure-Lua LSP snippet parserKey abstractions
| Function | Description |
|---|---|
vim.snippet.expand(body) |
Parse and insert a snippet at the cursor. |
vim.snippet.active() |
Is a snippet currently expanded? |
vim.snippet.jump(direction) |
Move to next or previous placeholder. |
vim.snippet.exit() |
End the current snippet session. |
The grammar
LSP snippet syntax is a restricted superset of TextMate snippets:
${1:placeholder} -- placeholder
${1|one,two,three|} -- choice
${1:nested ${2:inner}} -- nested tabstops
$0 -- final cursor
\$ \\ \} -- escapes
${VAR_NAME} -- variableThe parser in lsp/_snippet_grammar.lua is a hand-rolled descent parser that returns an AST of nodes: text, tabstop, placeholder, choice, variable. It is intentionally stricter than VS Code's parser to avoid silent corruption of text that looks but isn't a snippet.
How it works
graph LR
Body["foo(${1:arg})$0"] --> Parse[parse → AST]
Parse --> Insert[Insert text at cursor<br/>compute placeholder ranges]
Insert --> Mark[Place extmarks at each tabstop]
Mark --> Sel[Select placeholder 1]
Sel --> Type[User types]
Type --> Sync[Sync to other tabstops with same id]
Type --> Tab[Tab → next placeholder]
Tab --> Sel
Tab -.->|done| Exit[Exit at $0]expand(body):
- Parses the body to an AST.
- Computes the initial text — the AST flattened, with placeholders rendered to their default values.
- Inserts the text into the buffer at the cursor (using
nvim_buf_set_text). - Walks the AST to extract every tabstop occurrence; places extmarks at each occurrence's start and end.
- Selects the first placeholder by entering Visual mode with the cursor at its end.
Subsequent jump(1) / jump(-1) moves through tabstops in order. When the user types over a placeholder, the snippet engine records the new text and propagates it to every other extmark with the same tabstop id. When the user reaches $0 (or the last tabstop and there is no $0), the snippet session ends.
State
The state lives in module-locals in snippet.lua. There is one active snippet at a time per buffer; entering an outer snippet via a placeholder of an outer snippet pushes the inner one onto the stack. Exiting an inner snippet returns to the outer.
Integration points
- LSP completion —
lsp/completion.luachecksinsertTextFormat; when it'sSnippet, the editor callsvim.snippet.expand(item.insertText). - Code actions — LSP code actions can return a workspace edit with snippet text; the engine handles those identically.
- Text objects —
vim.snippetdoes not register text objects. The<Tab>mapping for "jump" is suggested in:help vim.snippetbut not installed by default. - Extmarks — every tabstop is an extmark. Edits inside the placeholder are tracked via
nvim_buf_attach.
Entry points for modification
- Support a new variable. Add it to the table in
snippet.luanear_resolve_variable. Variables includeTM_SELECTED_TEXT,CURRENT_YEAR,CLIPBOARD,WORKSPACE_NAME,LINE_COMMENT, etc. Several are stubbed; expanding the set is a small change. - Change the default
<Tab>behavior. It's a user mapping, not built in. The recommended map appears in:help vim.snippet.jump. - Tweak placeholder syncing. The propagation logic is in the autocmd installed by
expand. It handles overlapping ranges via the marktree.
Key source files
| File | Purpose |
|---|---|
runtime/lua/vim/snippet.lua |
Public API + state machine |
runtime/lua/vim/lsp/_snippet_grammar.lua |
LSP snippet parser |
runtime/lua/vim/lsp/completion.lua |
Caller from completion |
runtime/doc/lua.txt (snippet section) |
User docs |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.