LuaSnip Documentation

repository·master·Indexed 26 days ago

https://github.com/l3mon4d3/luasnip

A powerful snippet engine for Neovim supporting tabstops, Lua-based text transformations, conditional expansion, and nested snippets. It can parse LSP-style, VSCode, and SnipMate snippet formats. LuaSnip provides advanced node types like functionNode, dynamicNode, and choiceNode for highly dynamic content, along with a flexible trigger system supporting plain text, Lua patterns, ECMAscript-regex, and vim-regex.

Tokens
29.6K
Snippets
75
Records
152
Agent score
87%

What's inside LuaSnip

  1. Understand LuaSnip Node Types

    master

    LuaSnip snippets are composed of nodes. The primary node types are:

    • textNode (t): Static text.
    • insertNode (i): Editable text that can be jumped to.
    • functionNode (f): Text generated from the contents of other nodes.
    • choiceNode (c): Allows choosing between two nodes (which may contain nested nodes).
    • restoreNode (r): Stores and restores input to nodes.
    • dynamicNode (d): Nodes that are generated based on input.
    • snippetNode (sn): A node that contains other nodes (nested snippets).
    • indent_snippet_node (isn): An indentation-aware snippet node.
  2. Configure selection variables (LS_SELECT_RAW / LS_SELECT_DEDENT)

    master

    Many snippets rely on selected text. To use variables like LS_SELECT_RAW or LS_SELECT_DEDENT, you must populate them by yanking text before expansion.

    By default, this is disabled. You can enable it by:

    1. Setting cut_selection_keys in ls.setup.
    2. Mapping ls.cut_keys to a keybinding.
    3. Manually configuring a keybinding sequence: <Esc> to NORMAL, pre_yank, yank text, then post_yank.

    Recommended Manual Mapping Example: To map <Tab> in visual mode to perform a selection-aware expansion:

    vim.keymap.set("v", "<Tab>", [[<Esc><cmd>lua require("luasnip.util.select").pre_yank("z")<Cr>gv"zs<cmd>lua require('luasnip.util.select').post_yank("z")<Cr>]])
    vim.keymap.set("v", "<Tab>", [[<Esc><cmd>lua require("luasnip.util.select").pre_yank("z")<Cr>gv"zs<cmd>lua require('luasnip.util.select').post_yank("z")<Cr>]])
  3. Configure Keymaps for LuaSnip

    master

    You can configure keymaps using either Vim script or Lua.

    Vim script approach: Uses <Tab> for expanding/jumping forward, <S-Tab> for jumping backward, and <C-E> for navigating choiceNode options.

    Lua approach: A common pattern uses <C-K> to expand, <C-L> to jump forward, <C-J> to jump backward, and <C-E> to change active choices.

    local ls = require("luasnip")
    
    vim.keymap.set({"i"}, "<C-K>", function() ls.expand() end, {silent = true})
    vim.keymap.set({"i", "s"}, "<C-L>", function() ls.jump( 1) end, {silent = true})
    vim.keymap.set({"i", "s"}, "<C-J>", function() ls.jump(-1) end, {silent = true})
    
    vim.keymap.set({"i", "s"}, "<C-E>", function()
    	if ls.choice_active() then
    		ls.change_choice(1)
    	end
    end, {silent = true})
  4. Create self-dependent dynamicNodes

    master

    A dynamicNode can be updated in response to changes made to a node within itself. To implement this successfully:

    1. Use an Optional Noderef: Wrap the internal node reference with opt() (e.g., opt(k("key"))). This is necessary because a dynamicNode will not update if its argument nodes are missing.
    2. Set snippetstring_args: In the dynamicNode options, set snippetstring_args = true. This ensures that snippets expanded inside the dynamicNode are preserved during updates, preventing the loss of jump-points.
    3. Use Unique Keys: Assign unique keys to nodes that trigger updates, or wrap them in a restoreNode, to prevent LuaSnip from losing the cursor position during an update.
    4. Avoid Infinite Loops: Be careful with logic that replaces text with a version of itself that triggers another update (e.g., replacing "a" with "aa").
    ls.snip_expand(s("trig", {
        d(1, function(args) 
            if not args[1] then
                return sn(nil, {i(1, "asdf", {key = "ins"})})
            else
                return sn(nil, {i(1, args[1]:gsub("a", "e"), {key = "ins"})})
            end
        end, {opt(k("ins"))}, { snippetstring_args = true })
    }))
  5. Use Treesitter-Postfix-Snippets to match surrounding nodes

    master

    The treesitter_postfix helper allows you to trigger snippets based on Tree-sitter nodes that surround or precede the trigger. This is useful for transformations like wrapping an expression in a function call (e.g., std::move()).

    You can define matching logic in two ways:

    1. Using a Query: Provide a Tree-sitter query and a capture name.
    2. Using a Function: Manually walk the node tree for maximum flexibility.

    When a match is found, the text of the matched node is available in the snippet environment via snip.env.LS_TSMATCH (as a string array of lines).

    local treesitter_postfix = require("luasnip.extras.treesitter_postfix").treesitter_postfix
    
    treesitter_postfix({
        trig = ".mv",
        matchTSNode = {
            query = "[
                (call_expression)
                (identifier)
                (template_function)
                (subscript_expression)
                (field_expression)
                (user_defined_literal)
            ] @prefix",
            query_lang = "cpp"
        },
    },{
        f(function(_, parent) 
            -- Use LS_TSMATCH to access the matched node text
            local node_content = table.concat(parent.snippet.env.LS_TSMATCH, '\n')
            local replaced_content = ("std::move(%s)"):format(node_content)
            return vim.split(replaced_content, "\n", { trimempty = false })
        end)
    })
  6. Register a command to edit snippet files

    master

    To make editing snippets more convenient, you can register a Vim command to trigger the edit_snippet_files function.

    command! LuaSnipEdit :lua require("luasnip.loaders").edit_snippet_files()
  7. Load SnipMate snippets

    master

    LuaSnip supports a subset of the SnipMate format. It specifically looks for files following the pattern ./{ft}.snippets or ./{ft}/*.snippets.

    SnipMate files can be extended with LuaSnip features like priority and autosnippet options. Note that ${VISUAL} in SnipMate is automatically converted to $TM_SELECTED_TEXT for compatibility.

    Warning: Avoid using both extends <ft2> in a .snippets file and ls.filetype_extend("<ft1>", {"<ft2>"}) in Lua, as this will cause duplicate snippets.

  8. Load VS Code-style snippets

    master
    To use VS Code-style snippets (e.g., from friendly-snippets), use the from_vscode loader. You can load snippets from installed plugins or from a custom directory by providing a paths table. Note that custom directories must contain a package.json file.
  9. Load Snippets from VSCode, SnipMate, or Lua

    master

    LuaSnip supports loading snippets from multiple formats using specialized loaders.

    Loader Interface: require("luasnip.loaders.from_{vscode,snipmate,lua}").{lazy_,}load(opts)

    Options (opts):

    • paths: List of paths or a comma-separated string. If omitted, runtimepath is searched (looking for luasnippets for Lua, snippets for SnipMate, or package.json for VSCode).
    • lazy_paths: Similar to paths, but paths are loaded on creation and do not default to runtimepath if nil.
    • exclude: List of languages to exclude.
    • include: List of languages to include.
    • {override,default}_priority: Priority for snippet loading.
    • fs_event_providers: Mechanisms to watch for file updates. Use autocmd (default) or libuv (for cross-instance updates).

    Loading Modes:

    • load(): Immediately loads snippets.
    • lazy_load(): Defers loading until the filetype is encountered in a buffer.

    Manual Reloading: To manually reload a specific file: require("luasnip.loaders").reload_file(path)

  10. Configure custom filetype loading with load_ft_func

    master

    You can control which snippets are loaded for a buffer by customizing load_ft_func in your setup. This is useful for grouping snippets by framework (e.g., loading React-specific snippets when a file is detected as part of a React project).

    Example: If you have a snippet file react.lua that contains snippets for css, html, and js filetypes, you can use load_ft_func to trigger its loading when a specific buffer condition is met.

    load_ft_func = function(bufnr)
        if "<condition_for_react>" then
            return {"react"}
        else
            return require("luasnip.extras.filetype_functions").from_filetype_load
        }
    end
  11. Create On-The-Fly snippets

    master

    On-the-fly (OTF) snippets allow you to quickly expand text from a register as a snippet. This is useful for repetitive tasks where you don't want to define a permanent snippet.

    Syntax

    • $anytext: A placeholder (insertNode) with the text "anytext". The text acts as a unique key; multiple placeholders with the same key will mirror the first one.
    • $$: Escapes the $ symbol.

    Expansion

    Use require('luasnip.extras.otf').on_the_fly("<register>") to interpret the contents of a specific register as a snippet and expand it immediately. If no register is provided, it defaults to the unnamed register.

    Example Keybindings (Vim/Neovim)

    " Expand from the 'e' register
    vnoremap <c-f>  "ec<cmd>lua require('luasnip.extras.otf').on_the_fly("e")<cr>
    inoremap <c-f>  <cmd>lua require('luasnip.extras.otf').on_the_fly("e")<cr>
    
    " Expand from register 'a'
    vnoremap <c-f>a  "ac<cmd>lua require('luasnip.extras.otf').on_the_fly()<cr>
    inoremap <c-f>a  <cmd>lua require('luasnip.extras.otf').on_the_fly("a")<cr>
    vnoremap <c-f>  "ec<cmd>lua require('luasnip.extras.otf').on_the_fly("e")<cr>
    inoremap <c-f>  <cmd>lua require('luasnip.extras.otf').on_the_fly("e")<cr>
  12. Install LuaSnip

    master

    Install LuaSnip using your preferred Neovim plugin manager. It is highly recommended to install jsregexp to enable lsp-snippet-transformations.

    ### Packer
    ```lua
    use({
    	"L3MON4D3/LuaSnip",
    	-- follow latest release.
    	tag = "v2.*", -- Replace <CurrentMajor> by the latest released major
    	-- install jsregexp (optional!:
    	run = "make install_jsregexp"
    })

    lazy.nvim

    {
    	"L3MON4D3/LuaSnip",
    	-- follow latest release,
    	version = "v2.*", -- Replace <CurrentMajor> by the latest released major
    	-- install jsregexp (optional!).
    	build = "make install_jsregexp"
    }

    vim-plug

    " follow latest release and install jsregexp.
    Plug 'L3MON4D3/LuaSnip', {'tag': 'v2.*', 'do': 'make install_jsregexp'} " Replace <CurrentMajor> by the latest released major