13自定义代码段LuaSnip入门
欢迎观看系列视频第13期,粉丝留言的想了解如何自定义代码段。那么今天它来了,本期主要跟大家聊聊如何自定义代码段。
- 视频与目录
- 正片
- 什么是LuaSnip
- 特征
- 安装与配置LuaSnip
- nvim-cmp升级引发的BUG
- 基于friendly-snippets自定义代码段
- git clone代码
- 配置LuaSnip
- 升级导致的BUG
- 自定义代码段实践
- LuaSnip 官方代码段写法
- 相关链接
用 Lua 编写的 Neovim 代码段引擎。 https://github.com/L3MON4D3/LuaSnip
特征以下内容是机器翻译结果。
- 制表位
- 使用 Lua 函数的文本转换
- 有条件的扩展
- 定义嵌套代码段
- 特定于文件类型的片段
- 选择
- 动态片段创建
- 正则表达式触发器
- 自动触发的片段
- 快速地
- 解析LSP 样式代码段(但不支持正则表达式转换)
- 使用nvim-compe(或其继承者nvim-cmp(需要cmp_luasnip))扩展 LSP-Snippets
- 片段历史记录(跳回旧片段)
总结:如果你的lua写的6,这个是神器一般的存在。
安装与配置LuaSnip略,详情请看之前的视频教程。 《06_[nvim0.5+从0单排]_内置LSP 自动补全、语法检查、code action、代码段—TypeScript篇》 https://www.bilibili.com/video/BV19T4y1Z7VB/
这期实现配置完以后,大家会发现代码段是已经有了。那是因为我们安装了friendly-snippets
提供的数据。 虽然说这里面的代码段数据已经非常的丰富了。如果用于实际开发,显示还是不够的。如果能够自己定义就会好很多了。
在做这个期视频的过程中,我升级到最新的nvim-cmp。升级最新版本后我之前的分享的配置是需要微调的调整的。 吐槽一下,版本升级直接导致配置文件都要修改。这就很难受了。
主要是tab按键的配置要调整成下面的代码。
[''] = function(fallback)
if cmp.visible() then
cmp.select_next_item()
elseif luasnip.expand_or_jumpable() then
vim.fn.feedkeys(vim.api.nvim_replace_termcodes('luasnip-expand-or-jump', true, true, true), '')
else
fallback()
end
end,
[''] = function(fallback)
if cmp.visible() then
cmp.select_prev_item()
elseif luasnip.jumpable(-1) then
vim.fn.feedkeys(vim.api.nvim_replace_termcodes('luasnip-jump-prev', true, true, true), '')
else
fallback()
end
end,
为了方便大家。我把现在最新的配置文件贴出来方便大家使用。
~/.config/nvim/after/plugin/nvim-cmp.lua
最新内容如下:
local status, nvim_lsp = pcall(require, "lspconfig")
if (not status) then
return
end
--local nvim_lsp = require('lspconfig')
--typescript支持
require("lspconf.typescript")
--json支持
--require("lspconf.json")
--lua
require("lspconf.lua")
--普通的语言支持
--require("lspconf.common")
-- Set completeopt to have a better completion experience
vim.o.completeopt = "menuone,noselect"
-- luasnip setup
local luasnip = require "luasnip"
local lspkind = require("lspkind")
-- nvim-cmp setup
local cmp = require "cmp"
-- 自动提示1 详情信息
local cmpFormat1 = function(entry, vim_item)
-- fancy icons and a name of kind
vim_item.kind = require("lspkind").presets.default[vim_item.kind] .. " " .. vim_item.kind
-- set a name for each source
vim_item.menu =
({
buffer = "[Buffer]",
nvim_lsp = "[LSP]",
ultisnips = "[UltiSnips]",
nvim_lua = "[Lua]",
cmp_tabnine = "[TabNine]",
look = "[Look]",
path = "[Path]",
spell = "[Spell]",
calc = "[Calc]",
emoji = "[Emoji]"
})[entry.source.name]
return vim_item
end
-- 自动提示2 简洁信息
local cmpFormat2 = function(entry, vim_item)
vim_item.kind = lspkind.presets.default[vim_item.kind]
return vim_item
end
-- 自动提示3 详情信息
local cmpFormat3 = function(entry, vim_item)
-- fancy icons and a name of kind
vim_item.kind = require("lspkind").presets.default[vim_item.kind] .. ""
-- set a name for each source
vim_item.menu =
({
buffer = "[Buffer]",
nvim_lsp = "",
ultisnips = "[UltiSnips]",
nvim_lua = "[Lua]",
cmp_tabnine = "[TabNine]",
look = "[Look]",
path = "[Path]",
spell = "[Spell]",
calc = "[Calc]",
emoji = "[Emoji]"
})[entry.source.name]
return vim_item
end
------修复2021年10月12日 nvim-cmp.luaattempt to index field 'menu' (a nil value)---------
--重写插件方法,为了实现function 后,自动追加()
local keymap = require("cmp.utils.keymap")
cmp.confirm = function(option)
option = option or {}
local e = cmp.core.view:get_selected_entry() or (option.select and cmp.core.view:get_first_entry() or nil)
if e then
cmp.core:confirm(
e,
{
behavior = option.behavior
},
function()
local myContext = cmp.core:get_context({reason = cmp.ContextReason.TriggerOnly})
cmp.core:complete(myContext)
--function() 自动增加()
if
e and e.resolved_completion_item and
(e.resolved_completion_item.kind == 3 or e.resolved_completion_item.kind == 2)
then
vim.api.nvim_feedkeys(keymap.t("()"), "n", true)
end
end
)
return true
else
if vim.fn.complete_info({"selected"}).selected ~= -1 then
keymap.feedkeys(keymap.t(""), "n")
return true
end
return false
end
end
---------------
cmp.setup {
formatting = {
format = cmpFormat1
},
snippet = {
expand = function(args)
require("luasnip").lsp_expand(args.body)
end
},
mapping = {
[""] = cmp.mapping.select_prev_item(),
[""] = cmp.mapping.select_next_item(),
[""] = cmp.mapping.scroll_docs(-4),
[""] = cmp.mapping.scroll_docs(4),
[""] = cmp.mapping.complete(),
[""] = cmp.mapping.close(),
[""] = cmp.mapping.confirm {
behavior = cmp.ConfirmBehavior.Replace,
select = false
},
[""] = function(fallback)
if cmp.visible() then
cmp.select_next_item()
elseif luasnip.expand_or_jumpable() then
vim.fn.feedkeys(vim.api.nvim_replace_termcodes("luasnip-expand-or-jump", true, true, true), "")
else
fallback()
end
end,
[""] = function(fallback)
if cmp.visible() then
cmp.select_prev_item()
elseif luasnip.jumpable(-1) then
vim.fn.feedkeys(vim.api.nvim_replace_termcodes("luasnip-jump-prev", true, true, true), "")
else
fallback()
end
end
},
sources = {
{name = "nvim_lsp"},
{name = "luasnip"}, --{name = "nvim_lua"},
{
name = "buffer",
opts = {
get_bufnrs = function()
return vim.api.nvim_list_bufs()
end
}
},
--{name = "look"},
{name = "path"}
--{name = "cmp_tabnine"},
--{name = "calc"},
--{name = "spell"},
--{name = "emoji"}
}
}
基于friendly-snippets自定义代码段
git clone代码
下面目录结构,是我的个人喜好。可以根据自己的喜好修改。后面的配置需要这个路径。
创建目录
mkdir -p ~/.config/nvim/other
cd目录
cd ~/.config/nvim/other
clone 代码
git clone https://github.com/rafamadriz/friendly-snippets.git
下载慢的朋友可以尝试
git clone https://hub.fastgit.org/rafamadriz/friendly-snippets.git
以上操作完成以后,我们得到了这样的一个目录,后续我们会用的到。
~/.config/nvim/other/friendly-snippets
配置LuaSnip
修改如下配置文件(如果前面你的环境搭建是参考我的教程做的话,就是这个路径)
~/.config/nvim/after/plugin/snippets.lua
- 注释下面一行代码(约270行)
require("luasnip/loaders/from_vscode").load()
- 在文件末尾加入
require("luasnip/loaders/from_vscode").load({paths = {"~/.config/nvim/other/friendly-snippets/"}}) -- Load snippets from my-snippets folder
这样就可以启用我们自定义的目录的代码段源了。有很多小伙伴看到这里应该已经知道如何定义代码段了吧。没有多难,复制粘贴的方式就可以搞定了。这个方法适合大部分的使用者。
升级导致的BUG如果你的LuaSnip没有升级按上面操作完全是没有问题的。为了严谨点。我把所有的插件都升级了。发现原来的配置文件也需要升级不然在使用的过程中会遇到报红的情况。
现在(2021年10月20日)最新的配置文件如下:
local ls = require("luasnip")
-- some shorthands...
local s = ls.snippet
local sn = ls.snippet_node
local t = ls.text_node
local i = ls.insert_node
local f = ls.function_node
local c = ls.choice_node
local d = ls.dynamic_node
local l = require("luasnip.extras").lambda
local r = require("luasnip.extras").rep
local p = require("luasnip.extras").partial
local m = require("luasnip.extras").match
local n = require("luasnip.extras").nonempty
local dl = require("luasnip.extras").dynamic_lambda
local fmt = require("luasnip.extras.fmt").fmt
local fmta = require("luasnip.extras.fmt").fmta
local types = require("luasnip.util.types")
local conds = require("luasnip.extras.conditions")
-- Every unspecified option will be set to the default.
ls.config.set_config(
{
history = true,
-- Update more often, :h events for more info.
updateevents = "TextChanged,TextChangedI",
ext_opts = {
[types.choiceNode] = {
active = {
virt_text = {{"choiceNode", "Comment"}}
}
}
},
-- treesitter-hl has 100, use something higher (default is 200).
ext_base_prio = 300,
-- minimal increase in priority.
ext_prio_increase = 1,
enable_autosnippets = true
}
)
-- args is a table, where 1 is the text in Placeholder 1, 2 the text in
-- placeholder 2,...
local function copy(args)
return args[1]
end
-- 'recursive' dynamic snippet. Expands to some text followed by itself.
local rec_ls
rec_ls = function()
return sn(
nil,
c(
1,
{
-- Order is important, sn(...) first would cause infinite loop of expansion.
t(""),
sn(nil, {t({"", "\t\\item "}), i(1), d(2, rec_ls, {})})
}
)
)
end
-- complicated function for dynamicNode.
local function jdocsnip(args, _, old_state)
local nodes = {
t({"/**", " * "}),
i(1, "A short Description"),
t({"", ""})
}
-- These will be merged with the snippet; that way, should the snippet be updated,
-- some user input eg. text can be referred to in the new snippet.
local param_nodes = {}
if old_state then
nodes[2] = i(1, old_state.descr:get_text())
end
param_nodes.descr = nodes[2]
-- At least one param.
if string.find(args[2][1], ", ") then
vim.list_extend(nodes, {t({" * ", ""})})
end
local insert = 2
for indx, arg in ipairs(vim.split(args[2][1], ", ", true)) do
-- Get actual name parameter.
arg = vim.split(arg, " ", true)[2]
if arg then
local inode
-- if there was some text in this parameter, use it as static_text for this new snippet.
if old_state and old_state[arg] then
inode = i(insert, old_state["arg" .. arg]:get_text())
else
inode = i(insert)
end
vim.list_extend(nodes, {t({" * @param " .. arg .. " "}), inode, t({"", ""})})
param_nodes["arg" .. arg] = inode
insert = insert + 1
end
end
if args[1][1] ~= "void" then
local inode
if old_state and old_state.ret then
inode = i(insert, old_state.ret:get_text())
else
inode = i(insert)
end
vim.list_extend(nodes, {t({" * ", " * @return "}), inode, t({"", ""})})
param_nodes.ret = inode
insert = insert + 1
end
if vim.tbl_count(args[3]) ~= 1 then
local exc = string.gsub(args[3][2], " throws ", "")
local ins
if old_state and old_state.ex then
ins = i(insert, old_state.ex:get_text())
else
ins = i(insert)
end
vim.list_extend(nodes, {t({" * ", " * @throws " .. exc .. " "}), ins, t({"", ""})})
param_nodes.ex = ins
insert = insert + 1
end
vim.list_extend(nodes, {t({" */"})})
local snip = sn(nil, nodes)
-- Error on attempting overwrite.
snip.old_state = param_nodes
return snip
end
-- Make sure to not pass an invalid command, as io.popen() may write over nvim-text.
local function bash(_, _, command)
local file = io.popen(command, "r")
local res = {}
for line in file:lines() do
table.insert(res, line)
end
return res
end
-- Returns a snippet_node wrapped around an insert_node whose initial
-- text value is set to the current date in the desired format.
local date_input = function(args, state, fmt)
local fmt = fmt or "%Y-%m-%d"
return sn(nil, i(1, os.date(fmt)))
end
ls.snippets = {
-- When trying to expand a snippet, luasnip first searches the tables for
-- each filetype specified in 'filetype' followed by 'all'.
-- If ie. the filetype is 'lua.c'
-- - luasnip.lua
-- - luasnip.c
-- - luasnip.all
-- are searched in that order.
all = {
-- trigger is fn.
s(
"fn",
{
-- Simple static text.
t("//Parameters: "),
-- function, first parameter is the function, second the Placeholders
-- whose text it gets as input.
f(copy, 2),
t({"", "function "}),
-- Placeholder/Insert.
i(1),
t("("),
-- Placeholder with initial text.
i(2, "int foo"),
-- Linebreak
t({") {", "\t"}),
-- Last Placeholder, exit Point of the snippet. EVERY 'outer' SNIPPET NEEDS Placeholder 0.
i(0),
t({"", "}"})
}
),
s(
"class",
{
-- Choice: Switch between two different Nodes, first parameter is its position, second a list of nodes.
c(
1,
{
t("public "),
t("private ")
}
),
t("class "),
i(2),
t(" "),
c(
3,
{
t("{"),
-- sn: Nested Snippet. Instead of a trigger, it has a position, just like insert-nodes. !!! These don't expect a 0-node!!!!
-- Inside Choices, Nodes don't need a position as the choice node is the one being jumped to.
sn(
nil,
{
t("extends "),
i(1),
t(" {")
}
),
sn(
nil,
{
t("implements "),
i(1),
t(" {")
}
)
}
),
t({"", "\t"}),
i(0),
t({"", "}"})
}
),
-- Use a dynamic_node to interpolate the output of a
-- function (see date_input above) into the initial
-- value of an insert_node.
s(
"novel",
{
t("It was a dark and stormy night on "),
d(1, date_input, {}, "%A, %B %d of %Y"),
t(" and the clocks were striking thirteen.")
}
),
-- Parsing snippets: First parameter: Snippet-Trigger, Second: Snippet body.
-- Placeholders are parsed into choices with 1. the placeholder text(as a snippet) and 2. an empty string.
-- This means they are not SELECTed like in other editors/Snippet engines.
ls.parser.parse_snippet("lspsyn", "Wow! This ${1:Stuff} really ${2:works. ${3:Well, a bit.}}"),
-- When wordTrig is set to false, snippets may also expand inside other words.
ls.parser.parse_snippet({trig = "te", wordTrig = false}, "${1:cond} ? ${2:true} : ${3:false}"),
-- When regTrig is set, trig is treated like a pattern, this snippet will expand after any number.
ls.parser.parse_snippet({trig = "%d", regTrig = true}, "A Number!!"),
-- Using the condition, it's possible to allow expansion only in specific cases.
s(
"cond",
{
t("will only expand in c-style comments")
},
{
condition = function(line_to_cursor, matched_trigger, captures)
-- optional whitespace followed by //
return line_to_cursor:match("%s*//")
end
}
),
-- there's some built-in conditions in "luasnip.extras.conditions".
s(
"cond2",
{
t("will only expand at the beginning of the line")
},
{
condition = conds.line_begin
}
),
-- The last entry of args passed to the user-function is the surrounding snippet.
s(
{trig = "a%d", regTrig = true},
f(
function(_, snip)
return "Triggered with " .. snip.trigger .. "."
end,
{}
)
),
-- It's possible to use capture-groups inside regex-triggers.
s(
{trig = "b(%d)", regTrig = true},
f(
function(_, snip)
return "Captured Text: " .. snip.captures[1] .. "."
end,
{}
)
),
s(
{trig = "c(%d+)", regTrig = true},
{
t("will only expand for even numbers")
},
{
condition = function(line_to_cursor, matched_trigger, captures)
return tonumber(captures[1]) % 2 == 0
end
}
),
-- Use a function to execute any shell command and print its text.
s("bash", f(bash, {}, "ls")),
-- Short version for applying String transformations using function nodes.
s(
"transform",
{
i(1, "initial text"),
t({"", ""}),
-- lambda nodes accept an l._1,2,3,4,5, which in turn accept any string transformations.
-- This list will be applied in order to the first node given in the second argument.
l(l._1:match("[^i]*$"):gsub("i", "o"):gsub(" ", "_"):upper(), 1)
}
),
s(
"transform2",
{
i(1, "initial text"),
t("::"),
i(2, "replacement for e"),
t({"", ""}),
-- Lambdas can also apply transforms USING the text of other nodes:
l(l._1:gsub("e", l._2), {1, 2})
}
),
s(
{trig = "trafo(%d+)", regTrig = true},
{
-- env-variables and captures can also be used:
l(l.CAPTURE1:gsub("1", l.TM_FILENAME), {})
}
),
-- Set store_selection_keys = "" (for example) in your
-- luasnip.config.setup() call to access TM_SELECTED_TEXT. In
-- this case, select a URL, hit Tab, then expand this snippet.
s(
"link_url",
{
t('
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?