您当前的位置: 首页 >  vim

ITKEY_

暂无认证

  • 0浏览

    0关注

    732博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

13_[nvim0.5+从0单排]_ 自定义代码段LuaSnip入门

ITKEY_ 发布时间:2021-10-20 18:56:47 ,浏览量:0

视频与目录 项目值教程目录https://blog.csdn.net/lxyoucan/article/details/120641546视频全屏https://www.bilibili.com/video/BV1iL4y1B7gH/视频

13自定义代码段LuaSnip入门

正片

欢迎观看系列视频第13期,粉丝留言的想了解如何自定义代码段。那么今天它来了,本期主要跟大家聊聊如何自定义代码段。

文章目录
  • 视频与目录
  • 正片
  • 什么是LuaSnip
  • 特征
  • 安装与配置LuaSnip
  • nvim-cmp升级引发的BUG
  • 基于friendly-snippets自定义代码段
    • git clone代码
    • 配置LuaSnip
      • 升级导致的BUG
    • 自定义代码段实践
  • LuaSnip 官方代码段写法
  • 相关链接

什么是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按键的配置要调整成下面的代码。

[''] = 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('            
关注
打赏
1665243900
查看更多评论
0.0481s