视频与目录
项目 | 值 |
---|---|
教程目录 | https://blog.csdn.net/lxyoucan/article/details/120641546 |
视频全屏 | https://www.bilibili.com/video/BV1iL4y1B7gH/ |
视频 |
13自定义代码段LuaSnip入门 |
正片
欢迎观看系列视频第13期,粉丝留言的想了解如何自定义代码段。那么今天它来了,本期主要跟大家聊聊如何自定义代码段。
文章目录
什么是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升级引发的BUG
在做这个期视频的过程中,我升级到最新的nvim-cmp。升级最新版本后我之前的分享的配置是需要微调的调整的。
吐槽一下,版本升级直接导致配置文件都要修改。这就很难受了。
主要是tab按键的配置要调整成下面的代码。
['<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('<Plug>luasnip-expand-or-jump', true, true, true), '')
else
fallback()
end
end,
['<S-Tab>'] = function(fallback)
if cmp.visible() then
cmp.select_prev_item()
elseif luasnip.jumpable(-1) then
vim.fn.feedkeys(vim.api.nvim_replace_termcodes('<Plug>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("()<Left>"), "n", true)
end
end
)
return true
else
if vim.fn.complete_info({
"selected"}).selected ~= -1 then
keymap.feedkeys(keymap.t("<C-y>"), "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 = {
["<C-p>"] = cmp.mapping.select_prev_item(),
["<C-n>"] = cmp.mapping.select_next_item(),
["<C-d>"] = cmp.mapping.scroll_docs(-4),
["<C-f>"] = cmp.mapping.scroll_docs(4),
["<C-Space>"] = cmp.mapping.complete(),
["<C-e>"] = cmp.mapping.close(),
["<CR>"] = cmp.mapping.confirm {
behavior = cmp.ConfirmBehavior.Replace,
select = false
},
["<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("<Plug>luasnip-expand-or-jump", true, true, true), "")
else
fallback()
end
end,
["<S-Tab>"] = function(fallback)
if cmp.visible() then
cmp.select_prev_item()
elseif luasnip.jumpable(-1) then
vim.fn.feedkeys(vim.api.nvim_replace_termcodes("<Plug>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 = "<Tab>" (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('<a href="'),
f(
function(_, snip)
return snip.env.TM_SELECTED_TEXT[1] or {
}
end,
{
}
),
t('">'),
i(1),
t("</a>"),
i(0)
}
),
-- Shorthand for repeating the text in a given node.
s("repeat", {
i(1, "text"), t({
"", ""}), r(1)}),
-- Directly insert the ouput from a function evaluated at runtime.
s("part", p(os.date, "%Y")),
-- use matchNodes to insert text based on a pattern/function/lambda-evaluation.
s(
"mat",
{
i(1, {
"sample_text"}),
t(": "),
m(1, "%d", "contains a number", "no number :(")
}
),
-- The inserted text defaults to the first capture group/the entire
-- match if there are none
s(
"mat2",
{
i(1, {
"sample_text"}),
t(": "),
m(1, "[abc][abc][abc]")
}
),
-- It is even possible to apply gsubs' or other transformations
-- before matching.
s(
"mat3",
{
i(1, {
"sample_text"}),
t(": "),
m(1, l._1:gsub("[123]", ""):match("%d"), "contains a number that isn't 1, 2 or 3!")
}
),
-- `match` also accepts a function, which in turn accepts a string
-- (text in node, \n-concatted) and returns any non-nil value to match.
-- If that value is a string, it is used for the default-inserted text.
s(
"mat4",
{
i(1, {
"sample_text"}),
t(": "),
m(
1,
function(text)
return (#text % 2 == 0 and text) or nil
end
)
}
),
-- The nonempty-node inserts text depending on whether the arg-node is
-- empty.
s(
"nempty",
{
i(1, "sample_text"),
n(1, "i(1) is not empty!")
}
),
-- dynamic lambdas work exactly like regular lambdas, except that they
-- don't return a textNode, but a dynamicNode containing one insertNode.
-- This makes it easier to dynamically set preset-text for insertNodes.
s(
"dl1",
{
i(1, "sample_text"),
t({
":", ""}),
dl(2, l._1, 1)
}
),
-- Obviously, it's also possible to apply transformations, just like lambdas.
s(
"dl2",
{
i(1, "sample_text"),
i(2, "sample_text_2"),
t({
"", ""}),
dl(3, l._1:gsub("\n", " linebreak ") .. l._2, {
1, 2})
}
),
-- Alternative printf-like notation for defining snippets. It uses format
-- string with placeholders similar to the ones used with Python's .format().
s(
"fmt1",
fmt(
"To {title} {} {}.",
{
i(2, "Name"),
i(3, "Surname"),
title = c(1, {
t("Mr."), t("Ms.")})
}
)
),
-- To escape delimiters use double them, e.g. `{}` -> `{
{}}`.
-- Multi-line format strings by default have empty first/last line removed.
-- Indent common to all lines is also removed. Use the third `opts` argument
-- to control this behaviour.
s(
"fmt2",
fmt(
[[
foo({1}, {3}) {
{
return {2} * {4}
}}
]],
{
i(1, "x"),
r(1),
i(2, "y"),
r(2)
}
)
),
-- Empty placeholders are numbered automatically starting from 1 or the last
-- value of a numbered placeholder. Named placeholders do not affect numbering.
s(
"fmt3",
fmt(
"{} {a} {} {1} {}",
{
t("1"),
t("2"),
a = t("A")
}
)
),
-- The delimiters can be changed from the default `{}` to something else.
s("fmt4", fmt("foo() { return []; }", i(1, "x"), {
delimiters = "[]"})),
-- `fmta` is a convenient wrapper that uses `<>` instead of `{}`.
s("fmt5", fmta("foo() { return <>; }", i(1, "x"))),
-- By default all args must be used. Use strict=false to disable the check
s("fmt6", fmt("use {} only", {
t("this"), t("not this")}, {
strict = false}))
},
java = {
-- Very long example for a java class.
s(
"fn",
{
d(6, jdocsnip, {
2, 4, 5}),
t({
"", ""}),
c(
1,
{
t("public "),
t("private ")
}
),
c(
2,
{
t("void"),
t("String"),
t("char"),
t("int"),
t("double"),
t("boolean"),
i(nil, "")
}
),
t(" "),
i(3, "myFunc"),
t("("),
i(4),
t(")"),
c(
5,
{
t(""),
sn(
nil,
{
t({
"", " throws "}),
i(1)
}
)
}
),
t({
" {", "\t"}),
i(0),
t({
"", "}"})
}
)
},
tex = {
-- rec_ls is self-referencing. That makes this snippet 'infinite' eg. have as many
-- \item as necessary by utilizing a choiceNode.
s(
"ls",
{
t({
"\\begin{itemize}", "\t\\item "}),
i(1),
d(2, rec_ls, {
}),
t({
"", "\\end{itemize}"})
}
)
}
}
-- autotriggered snippets have to be defined in a separate table, luasnip.autosnippets.
ls.autosnippets = {
all = {
s(
"autotrigger",
{
t("autosnippet")
}
)
}
}
-- in a lua file: search lua-, then c-, then all-snippets.
ls.filetype_extend("lua", {
"c"})
-- in a cpp file: search c-snippets, then all-snippets only (no cpp-snippets!!).
ls.filetype_set("cpp", {
"c"})
--[[
-- Beside defining your own snippets you can also load snippets from "vscode-like" packages
-- that expose snippets in json files, for example <https://github.com/rafamadriz/friendly-snippets>.
-- Mind that this will extend `ls.snippets` so you need to do it after your own snippets or you
-- will need to extend the table yourself instead of setting a new one.
]]
--require("luasnip/loaders/from_vscode").load({include = {"python"}}) -- Load only python snippets
-- The directories will have to be structured like eg. <https://github.com/rafamadriz/friendly-snippets> (include
-- a similar `package.json`)
require("luasnip/loaders/from_vscode").load({
paths = {
"~/.config/nvim/other/friendly-snippets/"}}) -- Load snippets from my-snippets folder
-- You can also use lazy loading so you only get in memory snippets of languages you use
--require("luasnip/loaders/from_vscode").lazy_load() -- You can pass { paths = "./my-snippets/"} as well
自定义代码段实践
大家使用的开发语言的各不相同,既然大家都看到我这个教程了。大部分人都会接触到lua脚本,那么我就以lua的脚本自定义代码段为例做个演示。
我们先编辑这个文件
~/.config/nvim/other/friendly-snippets/package.json
这个文件可以理解是不同的开发语言的代码段库配置文件。
我们找到lua相关的配置
{
"language": "lua",
"path": "./snippets/lua.json"
}
其实就是配置lua语言代码段文件的路径。我们把它复制并修改成如下内容:
{
"language": "lua",
"path": "./itkey/lua.json"
}
既然要自定义我们就来创建一下,自定义代码段的目录吧。
我的路径仅供参考:
mkdir -p ~/.config/nvim/other/friendly-snippets/itkey
定义一个代码段
nvim ~/.config/nvim/other/friendly-snippets/itkey/lua.json
内容如下:
{
"lxyoucan": {
"prefix": "itkey",
"body": ["function ${1:name}($2)", "\t${3:print('祝您身体健康,万事如意!')}", "end"]
}
}
LuaSnip 官方代码段写法
需要具备一定的lua知识才能掌握,功能非常的强大。但是写起来比较费劲。而且没有上面的代码段通用性好。可移植性差。
本篇只在带大家入门,如下有需要讲这方面的内容。评论区留言,想要看的人多,我会考虑出一期的。
说这么多都是借口,主要原因是我自己也还没有研究透彻。