7.8 KiB
7.8 KiB
Custom Snippets Guide
This guide explains how to create and use custom code snippets with LuaSnip.
Overview
Snippets are code templates that expand when you type a trigger and press <Tab>. Your config supports:
- Lua snippets: Full power of Lua for dynamic snippets
- VS Code snippets: JSON format in
snippets/vscode/
Snippet Locations
~/.config/nvim/snippets/
├── all.lua # Global snippets (all filetypes)
├── typescript.lua # TypeScript/JavaScript
├── python.lua # Python
├── go.lua # Go
└── vscode/ # VS Code format snippets (optional)
Quick Start
1. Open Snippet File
-- For TypeScript snippets:
nvim ~/.config/nvim/snippets/typescript.lua
2. Add a Simple Snippet
local ls = require("luasnip")
local s = ls.snippet
local t = ls.text_node
local i = ls.insert_node
return {
s("cl", { t("console.log("), i(1), t(")") }),
}
3. Reload Snippets
<leader>cS Reload all snippets
4. Use Snippet
Type cl and press <Tab> to expand.
Snippet Anatomy
Basic Structure
s(trigger, nodes, opts)
- trigger: What you type to activate
- nodes: The content (text, placeholders, etc.)
- opts: Optional settings
Node Types
| Node | Function | Purpose |
|---|---|---|
t(text) |
Text node | Static text |
i(index, default) |
Insert node | Tab stop/placeholder |
c(index, choices) |
Choice node | Multiple options |
f(func, args) |
Function node | Dynamic content |
d(index, func, args) |
Dynamic node | Complex dynamic content |
rep(index) |
Repeat node | Mirror another node |
Examples by Complexity
1. Simple Text Replacement
-- Expands "todo" to "// TODO: "
s("todo", { t("// TODO: "), i(0) })
2. Multiple Tab Stops
-- Arrow function with parameters and body
s("af", {
t("const "),
i(1, "name"),
t(" = ("),
i(2),
t(") => {"),
t({"", " "}),
i(0),
t({"", "}"}),
})
Tab order: name → parameters → body
3. Using fmt() for Cleaner Syntax
local fmt = require("luasnip.extras.fmt").fmt
s("fn", fmt([[
function {}({}) {{
{}
}}
]], { i(1, "name"), i(2), i(0) }))
Note: {{ and }} escape braces in fmt.
4. Choice Node
-- Choose between const, let, var
s("var", {
c(1, {
t("const"),
t("let"),
t("var"),
}),
t(" "),
i(2, "name"),
t(" = "),
i(0),
})
Press <C-n> / <C-p> to cycle choices.
5. Function Node (Dynamic)
-- Automatically generate return type
s("fn", {
t("function "),
i(1, "name"),
t("("),
i(2),
t("): "),
f(function(args)
-- Generate return type based on function name
local name = args[1][1]
if name:match("^is") then return "boolean" end
if name:match("^get") then return "string" end
return "void"
end, {1}),
t({" {", " "}),
i(0),
t({"", "}"}),
})
6. Repeat Node
-- Class with constructor that uses the class name
s("class", {
t("class "),
i(1, "Name"),
t({" {", " constructor("}),
i(2),
t({") {", " "}),
i(0),
t({"", " }", "", "}", ""}),
t("export default "),
rep(1), -- Repeats the class name
t(";"),
})
7. Dynamic Node
-- Generate multiple parameters dynamically
s("params", {
t("function("),
d(1, function()
-- Could read from clipboard, analyze code, etc.
return sn(nil, { i(1, "param1"), t(", "), i(2, "param2") })
end),
t(")"),
})
Language-Specific Examples
TypeScript
-- snippets/typescript.lua
local ls = require("luasnip")
local s = ls.snippet
local t = ls.text_node
local i = ls.insert_node
local c = ls.choice_node
local fmt = require("luasnip.extras.fmt").fmt
return {
-- Console log
s("cl", { t("console.log("), i(1), t(")") }),
-- Console log with label
s("cll", fmt('console.log("{}: ", {})', { i(1, "label"), rep(1) })),
-- Arrow function
s("af", fmt("const {} = ({}) => {{\n {}\n}}", { i(1, "name"), i(2), i(0) })),
-- React functional component
s("rfc", fmt([[
export function {}({}: {}) {{
return (
<div>
{}
</div>
)
}}
]], { i(1, "Component"), i(2, "props"), i(3, "Props"), i(0) })),
-- useState hook
s("us", fmt("const [{}, set{}] = useState({})", {
i(1, "state"),
f(function(args)
local name = args[1][1]
return name:sub(1,1):upper() .. name:sub(2)
end, {1}),
i(2, "initialValue"),
})),
-- useEffect hook
s("ue", fmt([[
useEffect(() => {{
{}
}}, [{}])
]], { i(1), i(2) })),
-- Try-catch
s("tc", fmt([[
try {{
{}
}} catch (error) {{
{}
}}
]], { i(1), i(2, "console.error(error)") })),
}
Python
-- snippets/python.lua
local ls = require("luasnip")
local s = ls.snippet
local t = ls.text_node
local i = ls.insert_node
local fmt = require("luasnip.extras.fmt").fmt
return {
-- Main block
s("main", fmt([[
if __name__ == "__main__":
{}
]], { i(0) })),
-- Function with docstring
s("def", fmt([[
def {}({}):
"""{}"""
{}
]], { i(1, "name"), i(2), i(3, "Description"), i(0) })),
-- Class
s("class", fmt([[
class {}:
"""{}"""
def __init__(self, {}):
{}
]], { i(1, "Name"), i(2, "Description"), i(3), i(0) })),
-- Async function
s("adef", fmt([[
async def {}({}):
{}
]], { i(1, "name"), i(2), i(0) })),
-- Try-except
s("try", fmt([[
try:
{}
except {} as e:
{}
]], { i(1), i(2, "Exception"), i(3, "raise") })),
-- Context manager
s("with", fmt([[
with {}({}) as {}:
{}
]], { i(1, "open"), i(2), i(3, "f"), i(0) })),
}
Go
-- snippets/go.lua
local ls = require("luasnip")
local s = ls.snippet
local t = ls.text_node
local i = ls.insert_node
local c = ls.choice_node
local fmt = require("luasnip.extras.fmt").fmt
return {
-- Error handling
s("iferr", fmt([[
if err != nil {{
return {}
}}
]], { c(1, { t("err"), i(nil, "nil, err") }) })),
-- Function
s("fn", fmt([[
func {}({}) {} {{
{}
}}
]], { i(1, "name"), i(2), i(3, "error"), i(0) })),
-- Method
s("meth", fmt([[
func ({} *{}) {}({}) {} {{
{}
}}
]], { i(1, "r"), i(2, "Receiver"), i(3, "Method"), i(4), i(5, "error"), i(0) })),
-- Struct
s("st", fmt([[
type {} struct {{
{}
}}
]], { i(1, "Name"), i(0) })),
-- Interface
s("iface", fmt([[
type {} interface {{
{}
}}
]], { i(1, "Name"), i(0) })),
-- Test function
s("test", fmt([[
func Test{}(t *testing.T) {{
{}
}}
]], { i(1, "Name"), i(0) })),
-- Table-driven test
s("ttest", fmt([[
func Test{}(t *testing.T) {{
tests := []struct {{
name string
{}
}}{{
{{}},
}}
for _, tt := range tests {{
t.Run(tt.name, func(t *testing.T) {{
{}
}})
}}
}}
]], { i(1, "Name"), i(2, "// fields"), i(0) })),
}
VS Code Format Snippets
You can also use JSON snippets in snippets/vscode/:
// snippets/vscode/typescript.json
{
"Console Log": {
"prefix": "cl",
"body": ["console.log($1)"],
"description": "Console log"
},
"Arrow Function": {
"prefix": "af",
"body": [
"const ${1:name} = (${2:params}) => {",
" $0",
"}"
],
"description": "Arrow function"
}
}
Tips
Trigger Naming
Use short, memorable triggers:
cl→ console.logfn→ functioniferr→ if err != nil
Tab Stop Order
i(1)→ First tab stopi(2)→ Second tab stopi(0)→ Final cursor position (always last)
Default Values
i(1, "default") -- Shows "default", selected for replacement
Testing Snippets
- Edit snippet file
<leader>cS- Reload- Open file of that type
- Type trigger +
<Tab>
Debugging Snippets
If snippet doesn't work:
- Check Lua syntax (
:luafile %) - Verify filetype matches
- Check trigger isn't conflicting
Reloading
<leader>cS Reload all custom snippets
Changes take effect immediately after reload.