From b3083154c8e70921e320b3e80091a06d5077c1a1 Mon Sep 17 00:00:00 2001 From: Anton Kastritskiy Date: Thu, 2 May 2024 03:35:33 +0100 Subject: [PATCH] [vim] use builtin snippet engine --- nvim/init.lua | 12 -- nvim/lazy-lock.json | 20 ++- nvim/lua/antonk52/completion.lua | 25 ++-- nvim/lua/antonk52/snippets.lua | 228 ++++++++++++++----------------- 4 files changed, 128 insertions(+), 157 deletions(-) diff --git a/nvim/init.lua b/nvim/init.lua index 9715b0c..9f395f0 100644 --- a/nvim/init.lua +++ b/nvim/init.lua @@ -79,16 +79,6 @@ local plugins = { 'antonk52/markdowny.nvim', opts = { filetypes = { 'markdown', 'hgcommit', 'gitcommit' } }, }, - { - 'L3MON4D3/LuaSnip', - tag = 'v2.0.0', - config = function() - require('antonk52.snippets').setup() - vim.api.nvim_del_user_command('LuaSnipUnlinkCurrent') - vim.api.nvim_del_user_command('LuaSnipListAvailable') - end, - event = 'VeryLazy', - }, { 'hrsh7th/nvim-cmp', dependencies = { @@ -98,8 +88,6 @@ local plugins = { 'hrsh7th/cmp-nvim-lsp', 'hrsh7th/cmp-nvim-lua', 'hrsh7th/cmp-nvim-lsp-signature-help', - 'saadparwaiz1/cmp_luasnip', - 'L3MON4D3/LuaSnip', 'zbirenbaum/copilot.lua', }, config = function() diff --git a/nvim/lazy-lock.json b/nvim/lazy-lock.json index 7d68742..d28514c 100644 --- a/nvim/lazy-lock.json +++ b/nvim/lazy-lock.json @@ -1,5 +1,4 @@ { - "LuaSnip": { "branch": "master", "commit": "0b4950a237ce441a6a3a947d501622453f6860ea" }, "barbecue.nvim": { "branch": "main", "commit": "cd7e7da622d68136e13721865b4d919efd6325ed" }, "cmp-buffer": { "branch": "main", "commit": "3022dbc9166796b644a841a02de8dd1cc1d311fa" }, "cmp-cmdline": { "branch": "main", "commit": "d250c63aa13ead745e3a40f61fdd3470efde3923" }, @@ -7,31 +6,30 @@ "cmp-nvim-lsp-signature-help": { "branch": "main", "commit": "3d8912ebeb56e5ae08ef0906e3a54de1c66b92f1" }, "cmp-nvim-lua": { "branch": "main", "commit": "f12408bdb54c39c23e67cab726264c10db33ada8" }, "cmp-path": { "branch": "main", "commit": "91ff86cd9c29299a64f968ebb45846c485725f23" }, - "cmp_luasnip": { "branch": "master", "commit": "05a9ab28b53f71d1aece421ef32fee2cb857a843" }, "colorizer": { "branch": "master", "commit": "85855b38011114929f4058efc97af1059ab3e41d" }, - "conform.nvim": { "branch": "master", "commit": "4660e534bf7678ee0f85879aa75fdcb6855612c2" }, + "conform.nvim": { "branch": "master", "commit": "12b3995537f52ba2810a9857e8ca256881febbda" }, "copilot.lua": { "branch": "master", "commit": "f7612f5af4a7d7615babf43ab1e67a2d790c13a6" }, "dressing.nvim": { "branch": "master", "commit": "5162edb1442a729a885c45455a07e9a89058be2f" }, "git.nvim": { "branch": "main", "commit": "cc116ae91efd307836d24b868916f50a94c6daf9" }, - "github-nvim-theme": { "branch": "main", "commit": "d92e1143e5aaa0d7df28a26dd8ee2102df2cadd8" }, + "github-nvim-theme": { "branch": "main", "commit": "7b97aa55ef9dabce479f74f86b8c61c1464c9d2e" }, "gitignore-grabber.nvim": { "branch": "main", "commit": "365c1a2255f1badf5aa8a963a07f869a81ab6575" }, - "lake.nvim": { "branch": "main", "commit": "3b1966419b63e41ddfe23ba47f0e9cc665f133f8" }, + "lake.nvim": { "branch": "main", "commit": "3a0f3f0616052e8cf5ad681d70e11ea25c02c459" }, "lazy.nvim": { "branch": "main", "commit": "3f13f080434ac942b150679223d54f5ca91e0d52" }, "leap.nvim": { "branch": "main", "commit": "626be4c4ec040aeaf6466c9aae17ee0ab09f1a5b" }, "markdowny.nvim": { "branch": "main", "commit": "9881051876f26998635d436f21de1b2b37c52e6d" }, - "mini.nvim": { "branch": "main", "commit": "04f8d6e0acd5a52d01ec1c392e3947135dbfd8ef" }, + "mini.nvim": { "branch": "main", "commit": "c1af5ec1aa76ce9eb76a10c1613e0e310a98cee4" }, "neodev.nvim": { "branch": "main", "commit": "ce9a2e8eaba5649b553529c5498acb43a6c317cd" }, "npm_scripts.nvim": { "branch": "main", "commit": "14332129b7e916b5b22214e26c7415b9a86ac73c" }, "nvim-cmp": { "branch": "main", "commit": "8f3c541407e691af6163e2447f3af1bd6e17f9a3" }, - "nvim-lspconfig": { "branch": "master", "commit": "7133e85c3df14a387da8942c094c7edddcdef309" }, + "nvim-lspconfig": { "branch": "master", "commit": "aa5f4f4ee10b2688fb37fa46215672441d5cd5d9" }, "nvim-navic": { "branch": "master", "commit": "8649f694d3e76ee10c19255dece6411c29206a54" }, - "nvim-spectre": { "branch": "master", "commit": "026394a8458d62c6b7b305c076ce675420dbaa4c" }, - "nvim-treesitter": { "branch": "master", "commit": "ab3b3ff01028fef83cfb79b651bf65afb76ee062" }, + "nvim-spectre": { "branch": "master", "commit": "4651801ba37a9407b7257287aec45b6653ffc5e9" }, + "nvim-treesitter": { "branch": "master", "commit": "99d3e799b46f5c2a1fc0d7a42d7ab452de48c067" }, "nvim-treesitter-textobjects": { "branch": "master", "commit": "23b820146956b3b681c19e10d3a8bc0cbd9a1d4c" }, "nvim-ts-context-commentstring": { "branch": "main", "commit": "a6382f744f584bbf71d0a563af789af7190aabda" }, "plenary.nvim": { "branch": "master", "commit": "08e301982b9a057110ede7a735dd1b5285eb341f" }, - "schemastore.nvim": { "branch": "main", "commit": "26d27cf72a0164cb5a25d7b7ceedbe9a72511932" }, - "telescope.nvim": { "branch": "master", "commit": "35f94f0ef32d70e3664a703cefbe71bd1456d899" }, + "schemastore.nvim": { "branch": "main", "commit": "6af7d312ad66a056666b1dac803517f051e66ffd" }, + "telescope.nvim": { "branch": "master", "commit": "2d0d057791854decb2c9b6a0b52d43f3900dff40" }, "trouble.nvim": { "branch": "main", "commit": "b9cf677f20bb2faa2dacfa870b084e568dca9572" }, "twoslash-queries.nvim": { "branch": "main", "commit": "e000134c7ca3ea44f1095df3ceea89e485b7bdd5" }, "vim-dirvish": { "branch": "master", "commit": "3851bedb7f191b9a4a5531000b6fc0a8795cc9bb" } diff --git a/nvim/lua/antonk52/completion.lua b/nvim/lua/antonk52/completion.lua index 274dccc..96778b3 100644 --- a/nvim/lua/antonk52/completion.lua +++ b/nvim/lua/antonk52/completion.lua @@ -24,7 +24,7 @@ function M.update_ai_completion(opts) end function M.setup() - local luasnip = require('luasnip') + require('antonk52.snippets').register_source() local mapping = cmp.mapping.preset.insert({ [''] = function(fallback) if AI.is_visible() then @@ -58,22 +58,23 @@ function M.setup() end end, [''] = cmp.mapping.confirm(), - -- U for Undo - [''] = function(fallback) - if not luasnip.jump(-1) then - fallback() - end - end, + [''] = cmp.mapping.confirm({ select = true }), -- O for Open [''] = function(fallback) - if luasnip.expand_or_jumpable() then - luasnip.expand_or_jump() + if require('antonk52.snippets').expand() then + return elseif cmp.visible() then - cmp.confirm() + cmp.confirm({ select = true }) else fallback() end end, + [''] = function() + vim.snippet.jump(-1) + end, + [''] = function() + vim.snippet.jump(1) + end, [''] = cmp.mapping.scroll_docs(-4), [''] = cmp.mapping.scroll_docs(4), }) @@ -81,7 +82,7 @@ function M.setup() cmp.setup({ snippet = { expand = function(arg) - luasnip.lsp_expand(arg.body) + vim.snippet.expand(arg.body) end, }, mapping = mapping, @@ -100,7 +101,7 @@ function M.setup() end, }, sources = { - { name = 'luasnip', keyword_length = 1 }, + { name = 'snip', keyword_length = 2 }, { name = 'nvim_lsp' }, { name = 'nvim_lua' }, { name = 'nvim_lsp_signature_help' }, diff --git a/nvim/lua/antonk52/snippets.lua b/nvim/lua/antonk52/snippets.lua index e227e25..7b5ed31 100644 --- a/nvim/lua/antonk52/snippets.lua +++ b/nvim/lua/antonk52/snippets.lua @@ -1,36 +1,17 @@ +local cmp = require('cmp') local M = {} -local luasnip = require('luasnip') -local parse_snippet = luasnip.parser.parse_snippet -local l = require('luasnip.extras').lambda -local s = luasnip.snippet -local t = luasnip.text_node -local i = luasnip.insert_node +local snippet = function(a, b) + return { a, b } +end function M.lines(tbl) return table.concat(tbl, '\n') end -luasnip.config.set_config({ - history = true, - updateevents = 'TextChanged,TextChangedI', -}) - -local function is_js_test_file() - local file = vim.fn.expand('%') - if file == '' then - return false - end - - local test_file = vim.fn.match(file, '\\(_spec\\|spec\\|Spec\\|-test\\|test\\)\\.\\(js\\|jsx\\|ts\\|tsx\\)$') ~= -1 - local indirect_test_file = vim.fn.match(file, '\\v/\\(__tests__\\|test\\)/.+\\.(js|jsx|ts|tsx)$') ~= -1 - - return test_file or indirect_test_file -end - local javascript_snippets = { - parse_snippet('shebang', '#!/usr/bin/env node'), - parse_snippet( + snippet('shebang', '#!/usr/bin/env node'), + snippet( 'fun', M.lines({ 'function ${1:function_name}(${2:arg}) {', @@ -38,7 +19,7 @@ local javascript_snippets = { '}', }) ), - parse_snippet( + snippet( 'switch', M.lines({ 'switch (${1:condition}) {', @@ -52,98 +33,36 @@ local javascript_snippets = { }) ), - parse_snippet('iif', '/* istanbul ignore file */'), + snippet('iif', '/* istanbul ignore file */'), - parse_snippet('iin', '/* istanbul ignore next */'), + snippet('iin', '/* istanbul ignore next */'), - parse_snippet('fi', "\\$FlowIgnore<'${1:why do you ignore?}'>"), + snippet('fi', "\\$FlowIgnore<'${1:why do you ignore?}'>"), - parse_snippet('ffm', "\\$FlowFixMe<'${1:what is broken?}'>"), + snippet('ffm', "\\$FlowFixMe<'${1:what is broken?}'>"), - parse_snippet('ee', "\\$ExpectError<'${1:why is it expected?}'>"), - - parse_snippet('import', "import ${0:thing} from '${1:package}';"), - parse_snippet('imp', "import ${0:thing} from '${1:package}';"), - - s('useState', { - t('const ['), - i(1, 'state'), - t(', set'), - -- capitalize first char - l( - l._1:gsub('^.', function(c) - return c:upper() - end), - 1 - ), - t('] = useState('), - i(3, 'defaultValue'), - t(');'), - }), - - parse_snippet( - 'useEffect', - M.lines({ - 'useEffect(() => {', - ' ${1:logic}', - '}, [${2:leave_empty_for_componentDidMount}]);', - }) - ), + snippet('ee', "\\$ExpectError<'${1:why is it expected?}'>"), - parse_snippet( - 'useCallback', - M.lines({ - 'useCallback(() => {', - ' ${1:logic}', - '}, [${2:dependencies}]);', - }) - ), - - parse_snippet( - { trig = 'desc', condition = is_js_test_file, show_condition = is_js_test_file }, - M.lines({ - "describe('${1:what are we testing}', () => {", - ' $0', - '});', - }) - ), - parse_snippet( - { trig = 'it', condition = is_js_test_file, show_condition = is_js_test_file }, - M.lines({ - "it('${1:what to test}', () => {", - ' const result = ${2:funcName}(${3:args});', - " const expected = ${4:'what do we expect?'};", - '', - ' expect(result).toEqual(expected);', - '});', - }) - ), - parse_snippet( - { trig = 'mock', condition = is_js_test_file, show_condition = is_js_test_file }, - M.lines({ - "jest.mock('${1:file/path/to/mock}', () => ({", - ' ${2:exportedFunc}: jest.fn($0),', - '}));', - }) - ), + snippet('import', "import ${0:thing} from '${1:package}';"), + snippet('imp', "import ${0:thing} from '${1:package}';"), } M.default_snippets = { all = { - parse_snippet('shebang', '#!/bin sh'), - -- use `function_node` to evaluate lua for snippet body - luasnip.snippet( - 'epoch', - luasnip.function_node(function() - return os.time() .. '' - end) - ), + snippet('shebang', '#!/bin sh'), }, lua = { - parse_snippet('fun', 'function($1) $0 end'), + snippet( + 'fun', + M.lines({ + 'function($1)', + ' $0', + 'end', + }) + ), }, markdown = { - parse_snippet( + snippet( 'table', M.lines({ '| First Header | Second Header |', @@ -152,10 +71,8 @@ M.default_snippets = { '| Content Cell | Content Cell |', }) ), - - parse_snippet('img', [[![${1:alt}]($0)]]), - - parse_snippet( + snippet('img', [[![${1:alt}]($0)]]), + snippet( 'details', M.lines({ '
${1:tldr}', @@ -163,15 +80,6 @@ M.default_snippets = { '
', }) ), - - parse_snippet( - 'todo', - M.lines({ - '## TODO', - '', - '- [ ] $0', - }) - ), }, ['javascript'] = javascript_snippets, ['javascript.jsx'] = javascript_snippets, @@ -181,11 +89,87 @@ M.default_snippets = { ['typescriptreact'] = javascript_snippets, } -function M.setup() - -- load initial snippets - for ft, snippets in pairs(M.default_snippets) do - luasnip.add_snippets(ft, snippets) +local function get_buf_snips() + local ft = vim.bo.filetype + local snips = { M.default_snippets.all[1] } + + if ft and M.default_snippets[ft] then + for _, s in ipairs(M.default_snippets[ft]) do + table.insert(snips, s) + end + end + + return snips +end + +-- cmp source for snippets to show up in completion menu +function M.register_source() + local cmp_source = {} + cmp_source.new = function() + local self = setmetatable({ cache = {} }, { __index = cmp_source }) + return self end + cmp_source.complete = function(self, _, callback) + local bufnr = vim.api.nvim_get_current_buf() + if not self.cache[bufnr] then + local snips = get_buf_snips() + + local items = {} + for _, s in ipairs(snips) do + local trigger = s[1] + table.insert(items, { + word = trigger, + label = trigger, + kind = cmp.lsp.CompletionItemKind.Snippet, + }) + end + + self.cache[bufnr] = items + callback(items) + end + + callback(self.cache[bufnr]) + end + + function cmp_source:execute(completion_item, callback) + M.expand() + callback(completion_item) + end + require('cmp').register_source('snip', cmp_source.new()) +end + +---------------------------------------------------- +-- Helper functions to exapnd a snippet under cussor +---------------------------------------------------- +function M.get_snippet() + local line, col = unpack(vim.api.nvim_win_get_cursor(0)) + local cur_line = vim.api.nvim_buf_get_lines(0, line - 1, line, true) + local line_pre_cursor = cur_line[1]:sub(1, col) + + for _, s in ipairs(get_buf_snips()) do + if vim.endswith(line_pre_cursor, s[1]) then + return s[1], s[2], line, col + end + end + + return nil +end + +function M.expandable() + return M.get_snippet() ~= nil +end + +function M.expand() + local trigger, body, line, col = M.get_snippet() + if not trigger or not line or not col then + return false + end + -- remove trigger + vim.api.nvim_buf_set_text(0, line - 1, col - #trigger, line - 1, col, {}) + vim.api.nvim_win_set_cursor(0, { line, col - #trigger }) + + vim.snippet.expand(body) + return true end return M