snippet
基本配置
以下是我的 LuaSnip 插件配置,包含了基本功能的设定和与 nvim-cmp
的集成。
lua
├── plugins
│ ├── init.lua
│ └── lang-core
│ └── snippet.lua # 配置 snippet 引擎
└── snippets
└── lua_snippets # lua 配置的 snippets
├── lua.lua
└── python.lua
lang-core/snippet.lua
lang-core/snippet.lua
return {
{
"L3MON4D3/LuaSnip",
build = (not jit.os:find("Windows"))
and "echo 'NOTE: jsregexp is optional, so not a big deal if it fails to build'; make install_jsregexp"
or nil,
dependencies = {
{
"rafamadriz/friendly-snippets",
config = function()
require("luasnip.loaders.from_vscode").lazy_load()
end,
},
{
"nvim-cmp",
dependencies = {
"saadparwaiz1/cmp_luasnip",
},
opts = function(_, opts)
opts.snippet = {
expand = function(args)
require("luasnip").lsp_expand(args.body)
end,
}
table.insert(opts.sources, { name = "luasnip" })
end,
},
},
opts = function(_, opts)
local ls = require("luasnip")
local types = require("luasnip.util.types")
ls.setup({
keep_roots = true,
link_roots = true,
link_children = true,
update_events = "TextChanged,TextChangedI",
delete_check_events = "TextChanged",
ext_opts = {
[types.choiceNode] = {
active = {
virt_text = { { "choiceNode", "Comment" } },
},
},
},
ext_base_prio = 300,
ext_prio_increase = 1,
enable_autosnippets = true,
store_selection_keys = "<Tab>",
})
require("luasnip.loaders.from_lua").lazy_load({
paths = { vim.fn.stdpath("config") .. "/lua/snippets/lua_snippets" },
})
require("luasnip.loaders.from_vscode").lazy_load({
paths = { vim.fn.stdpath("config") .. "/lua/snippets" },
})
end,
keys = {
{
"<tab>",
function()
return require("luasnip").jumpable(1) and "<Plug>luasnip-jump-next" or "<tab>"
end,
expr = true, silent = true, mode = "i",
},
{ "<tab>", function() require("luasnip").jump(1) end, mode = "s" },
{ "<s-tab>", function() require("luasnip").jump(-1) end, mode = { "i", "s" } },
},
},
{ "chrisgrieser/nvim-scissors", dependencies = {
"L3MON4D3/LuaSnip",
} },
}
如何用 lua 编写 snippet
配置: 加载特定目录下面的 snippet
lang-core/snippet.lua
require("luasnip.loaders.from_lua").lazy_load({
paths = { vim.fn.stdpath("config") .. "/lua/snippets/lua_snippets" },
})
-
require("luasnip.loaders.from_lua")
:- 这个模块提供了一个方式来加载使用 Lua 格式编写的 Snippet。这表明你正准备通过 Lua 语言来定义自己的 Snippet,而不仅仅是依赖于已有的 Snippet 库。
-
.lazy_load({...})
:- 这是一个用于延迟加载 Snippet 的方法。通过这种方式,在 Neovim 启动时不马上加载所有 Snippet,而是在使用它们的时候再进行加载。通过这种方式,可以加快文本编辑器的启动速度,同时确保在实际需要时才读入 Snippet。
-
paths
字段:paths
属性是用来告诉luasnip
从哪个目录加载 Snippet 的。具体到这段代码中:vim.fn.stdpath("config")
: 这个函数返回 Neovim 配置文件所在的标准路径(通常是~/.config/nvim
)。.. "/lua/snippets/lua_snippets"
:这是连接路径字符串,指向一个具体用于存放 Lua Snippet 的文件夹。也就是说,自定义的 Snippet 会位于~/.config/nvim/lua/snippets/lua_snippets
目录下。
Lua snippet 的示例
我用 lua 写 snippet 主要是它可以实现 postfix snippets.
local ls = require("luasnip")
local s = ls.s
local t = ls.t
local i = ls.i
local fmt = require("luasnip.extras.fmt").fmt
local rep = require("luasnip.extras").rep
local sn = ls.snippet_node
local d = ls.dynamic_node
local postfix = require("luasnip.extras.postfix").postfix
local append = postfix({
trig = ".append", -- 触发词
}, {
d(1, function(_, parent) -- 动态节点
local matched = parent.snippet.env.POSTFIX_MATCH -- 获取已输入的内容
return sn(nil, fmt("table.insert({}, {})", { t({ matched }), i(1) })) -- 返回新的 Snippet
end),
})
-
s
(Snippet):s(id, nodes)
:用于定义整个 Snippet 的节点。id
可以是nil
或者是一个唯一标识符,nodes
是该 Snippet 包含的节点列表。这个节点是主节点,通常会由多个其他节点组成。
-
t
(Text Node):t(nodes)
:表示文本节点,用于插入静态文本。这个节点不会被编辑,它只是用于展示定向的文本内容。例如,t("Hello, World!")
将简单地插入文本"Hello, World!"
。
-
i
(Insert Node):i(pos)
:定义一个插入节点,允许用户在 Snippet 被扩展时在指定位置输入文本。pos
表示该插入节点的编号标识。如果有多个插入节点,可以为它们指定不同的编号,以便切换光标到相应位置。
-
fmt
(Formatting) 方法:fmt("template", { ... })
:这是一个辅助函数,用于格式化 Snippet 内容,使得在 Snippet 中插入多个值时更方便。使用模板字符串,其中的{}
表示插入相应内容的占位符,可以与其他节点(如t
,i
等)配合使用。例如,fmt("Hello, {name}, you are {age} years old", { t({name}), i(1) })
是在指定位置插入用户输入的模板字符串。
-
rep
(Repeat Node):rep(1)
:表示重复节点,允许用户在 Snippet 被扩展后重新输入以供后续使用。其参数是节点的编号,通过不断调用rep
,可以使得 Snippet 能够在用户需要重复某个片段时,提高代码的书写效率。
-
sn
(Snippet Node):sn(nil, nodes)
:用于创建一个 Snippet 节点,id
设置为nil
(也可以为其他 ID)允许 Snippet 少数结构嵌套分解,能够更灵活地构建代码结构。
-
d
(Dynamic Node):d(pos, func)
:定义了一个动态节点,其内容由给定的func
函数动态生成,这个函数会在 Snippet 展开时被调用。参数pos
决定此节点的编号。该节点的返回值可以包括多个不同的其他节点。这样,当用户输入 Snippet 的触发词时,动态节点的内容可以根据用户上下文或之前输入动态修改。
-
postfix
:postfix({ trig = "some.trigger", ... })
: 用于创建 postfix Snippet,触发词为用户在某个文本后加上指定后缀后的行为。在你的代码中,它用于定义一个名为.append
的后缀 Snippet。一个此类节点通常允许用户在文本后追加一个特定功能。
关键部分解释
-
Postfix Snippet 概念:
- Postfix Snippet 是一种特殊类型的 Snippet,当用户在某段代码后添加指定的触发关键字时触发。在这里,触发词是
.append
,这意味着用户将在某些文本后输入.append
才会激活此 Snippet。
- Postfix Snippet 是一种特殊类型的 Snippet,当用户在某段代码后添加指定的触发关键字时触发。在这里,触发词是
-
trig = ".append"
:- 这表示当用户在文本后键入
.append
时,这个 Snippet 将被启用。
- 这表示当用户在文本后键入
-
d(1, function(_, parent) ... end)
:d
代表一个动态节点。这个节点在 Snippet 被扩展时会执行指定的函数。1
是这个动态节点的编号,方便程序内部管理多个节点。function(_, parent)
是回调函数,其中parent
代表 Snippet 的父级(也就是包含这个动态节点的 Snippet)。函数中matched
变量将获取用于触发 Snippet 的已经输入文本,存储在parent.snippet.env.POSTFIX_MATCH
中。
-
local matched = parent.snippet.env.POSTFIX_MATCH
:- 通过
parent
获取到当前上下文中POSTFIX_MATCH
变量的值,matched
代表用户在输入触发文本时已经输入的内容。
- 通过
-
return sn(nil, fmt("table.insert({}, {})", { t({ matched }), i(1) }))
:sn(nil, ...)
:创建一个新的 Snippet 节点,nil
表示没有特定的 UID。fmt("table.insert({}, {})", { t({ matched }), i(1) })
:使用fmt
函数构建 Snippet 的内容,生成一个格式化字符串:table.insert({}, {})
是 Lua 中用来将元素插入表(数组)的常用函数。t({ matched })
将把matched
作为文本节点插入到第一个位置{}
。i(1)
将会在第二个{}
位置创建一个插入节点,让用户在这里输入要插入到表中的新元素。