From e87d0c7ec30f4c87e6e8acc010fbb020cefe5531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20B=C3=B6hme?= Date: Sat, 10 Feb 2024 18:22:09 +0100 Subject: [PATCH] feat(nvim): add luasnip snippets --- modules/programs/nvim/default.nix | 5 + modules/programs/nvim/plugins/coding.nix | 22 +- modules/programs/nvim/plugins/lua/luasnip.lua | 24 +++ .../programs/nvim/plugins/lua/nvim-cmp.lua | 58 +++--- .../programs/nvim/plugins/snippets/python.lua | 194 ++++++++++++++++++ 5 files changed, 260 insertions(+), 43 deletions(-) create mode 100644 modules/programs/nvim/plugins/lua/luasnip.lua create mode 100644 modules/programs/nvim/plugins/snippets/python.lua diff --git a/modules/programs/nvim/default.nix b/modules/programs/nvim/default.nix index 721b184..6d30aaf 100644 --- a/modules/programs/nvim/default.nix +++ b/modules/programs/nvim/default.nix @@ -19,6 +19,11 @@ in ) ]; + xdg.configFile."nvim/snippets" = { + recursive = true; + source = ./plugins/snippets; + }; + programs.neovim = { enable = true; package = inputs.neovim-nightly-overlay.packages.${pkgs.system}.default; diff --git a/modules/programs/nvim/plugins/coding.nix b/modules/programs/nvim/plugins/coding.nix index c38452c..0cff981 100644 --- a/modules/programs/nvim/plugins/coding.nix +++ b/modules/programs/nvim/plugins/coding.nix @@ -85,26 +85,12 @@ with builtins; { plugin = cmp_luasnip; } { plugin = cmp-spell; } { plugin = cmp-nvim-lsp-signature-help; } - { - plugin = copilot-cmp; - opts = { }; - dependencies = [ - { - plugin = copilot-lua; - opts = { - suggestion = { enabled = false; }; - panel = { enabled = false; }; - }; - conf = /* lua */ '' - require("copilot").setup(opts) - vim.cmd("Copilot disable") - ''; - } - ]; - } { plugin = friendly-snippets; } { plugin = lspkind-nvim; } - { plugin = luasnip; } + { + plugin = luasnip; + conf = readFile ./lua/luasnip.lua; + } ]; } { diff --git a/modules/programs/nvim/plugins/lua/luasnip.lua b/modules/programs/nvim/plugins/lua/luasnip.lua new file mode 100644 index 0000000..c391b09 --- /dev/null +++ b/modules/programs/nvim/plugins/lua/luasnip.lua @@ -0,0 +1,24 @@ +local ls = require("luasnip") +local types = require("luasnip.util.types") + +-- Every unspecified option will be set to the default. +ls.setup({ + history = true, + + -- Update more often, :h events for more info. + update_events = "TextChanged,TextChangedI", + -- Snippets aren't automatically removed if their text is deleted. + -- `delete_check_events` determines on which events (:h events) a check for + -- deleted snippets is performed. + -- This can be especially useful when `history` is enabled. + delete_check_events = "TextChanged", + ext_opts = { + [types.choiceNode] = { + active = { + virt_text = { { "<--", "Error" } }, + }, + }, + }, +}) + +require("luasnip.loaders.from_lua").load({ paths = "~/.config/nvim/snippets" }) diff --git a/modules/programs/nvim/plugins/lua/nvim-cmp.lua b/modules/programs/nvim/plugins/lua/nvim-cmp.lua index 2c2d075..4b9c067 100644 --- a/modules/programs/nvim/plugins/lua/nvim-cmp.lua +++ b/modules/programs/nvim/plugins/lua/nvim-cmp.lua @@ -2,23 +2,12 @@ local cmp = require("cmp") local luasnip = require("luasnip") require("luasnip.loaders.from_vscode").lazy_load() -local has_words_before = function() - if vim.api.nvim_buf_get_option(0, "buftype") == "prompt" then - return false - end - local line, col = unpack(vim.api.nvim_win_get_cursor(0)) - return col ~= 0 and vim.api.nvim_buf_get_text(0, line - 1, 0, line - 1, col, {})[1]:match("^%s*$") == nil -end - cmp.setup({ formatting = { format = require("lspkind").cmp_format({ mode = "symbol", -- show only symbol annotations maxwidth = 50, -- prevent the popup from showing more than provided characters ellipsis_char = "...", -- when popup menu exceed maxwidth, the truncated part would show ellipsis_char instead - symbol_map = { - Copilot = "", - }, }), }, snippet = { @@ -31,36 +20,55 @@ cmp.setup({ [""] = cmp.mapping.scroll_docs(-4), [""] = cmp.mapping.scroll_docs(4), [""] = cmp.mapping.complete(), - [""] = cmp.mapping.abort(), + [""] = cmp.mapping.abort(), [""] = cmp.mapping.confirm({ select = true }), [""] = cmp.mapping(function(fallback) - if cmp.visible() and has_words_before() then + if luasnip.jumpable(1) then + luasnip.jump(1) + elseif cmp.visible() then cmp.select_next_item({ behavior = cmp.SelectBehavior.Select }) - elseif luasnip.expand_or_jumpable() then - luasnip.expand_or_jump() else fallback() end end, { "i", "s" }), [""] = cmp.mapping(function(fallback) - if cmp.visible() then - cmp.select_prev_item() - elseif luasnip.jumpable(-1) then + if luasnip.jumpable(-1) then luasnip.jump(-1) + elseif cmp.visible() then + cmp.select_prev_item({ behavior = cmp.SelectBehavior.Select }) + else + fallback() + end + end, { "i", "s" }), + [""] = cmp.mapping(function(fallback) + if luasnip.choice_active() then + luasnip.change_choice(1) + elseif cmp.visible() then + cmp.select_next_item({ behavior = cmp.SelectBehavior.Select }) + else + fallback() + end + end, { "i", "s" }), + [""] = cmp.mapping(function(fallback) + if luasnip.choice_active() then + luasnip.change_choice(-1) + elseif cmp.visible() then + cmp.select_prev_item({ behavior = cmp.SelectBehavior.Select }) else fallback() end end, { "i", "s" }), }), - sources = { + sources = cmp.config.sources({ { name = "async_path", priority = 1 }, - { name = "buffer", priority = 1 }, - { name = "spell", priority = 1 }, - { name = "luasnip", priority = 2 }, - { name = "copilot", priority = 3 }, - { name = "nvim_lsp", priority = 3 }, + { name = "nvim_lsp", priority = 2 }, { name = "nvim_lsp_signature_help", priority = 3 }, - }, + { name = "luasnip", priority = 4 }, + }, { + { name = "async_path" }, + { name = "buffer" }, + { name = "spell" }, + }), }) -- Set configuration for specific filetype. diff --git a/modules/programs/nvim/plugins/snippets/python.lua b/modules/programs/nvim/plugins/snippets/python.lua new file mode 100644 index 0000000..f370ef4 --- /dev/null +++ b/modules/programs/nvim/plugins/snippets/python.lua @@ -0,0 +1,194 @@ +local ls = require("luasnip") +local s = ls.snippet +local sn = ls.snippet_node +local isn = ls.indent_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 r = ls.restore_node +local events = require("luasnip.util.events") +local ai = require("luasnip.nodes.absolute_indexer") +local extras = require("luasnip.extras") +local l = extras.lambda +local rep = extras.rep +local p = extras.partial +local m = extras.match +local n = extras.nonempty +local dl = extras.dynamic_lambda +local fmt = require("luasnip.extras.fmt").fmt +local fmta = require("luasnip.extras.fmt").fmta +local conds = require("luasnip.extras.expand_conditions") +local postfix = require("luasnip.extras.postfix").postfix +local types = require("luasnip.util.types") +local parse = require("luasnip.util.parser").parse_snippet +local ms = ls.multi_snippet +local k = require("luasnip.nodes.key_indexer").new_key + +local arg_template = [[ + {arg}: {type} +]] + +local function pyarg() + return sn( + nil, + fmt(arg_template, { + arg = i(1, "arg"), + type = i(2, "Any"), + }) + ) +end + +local function pyargs(_, _, _, user_args) + local choices = { + sn(nil, i(1)), + sn(nil, { + not user_args and t(", ") or t(""), + d(1, pyarg), + d(2, pyargs, { user_args = { false } }), + }), + } + -- switch order for first call + if user_args then + local fst, snd = unpack(choices) + choices = { snd, fst } + end + + return sn(nil, c(1, choices)) +end + +local def_template = [[ +def {fname}({args}) -> {rtype}: + """ + {docs} + """ + {final} +]] + +local def = s( + "def", + fmt(def_template, { + fname = i(1, "fname"), + args = d(2, pyargs, nil, { user_args = { true } }), + rtype = i(3, "None"), + docs = i(4, "Documentation"), + final = i(5, "pass"), + }, { priority = 1001 }) +) + +local defs_template = [[ +def {mname}(self, {args}) -> {rtype}: + """ + {docs} + """ + {final} +]] + +local defs = s( + "defs", + fmt(defs_template, { + mname = i(1, "mname"), + args = d(2, pyargs, nil, { user_args = { true } }), + rtype = i(3, "None"), + docs = i(4, "Documentation"), + final = i(5, "pass"), + }, { priority = 1001 }) +) + +local enum_template = [[ +for {i}, {value} in enumerate({iter}): + {final} +]] + +local dot_enum = postfix(".enum", { + d(1, function(_, parent) + return sn( + 1, + fmt(enum_template, { + i = i(1, "i"), + value = i(2, "value"), + iter = t(parent.env.POSTFIX_MATCH), + final = i(3, "pass"), + }) + ) + end), +}) + +local enum = s( + "enum", + fmt(enum_template, { + i = i(1, "i"), + value = i(2, "value"), + iter = i(3, "iter"), + final = i(4, "pass"), + }) +) + +local for_template = [[ +for {item} in {iter}: + {final} +]] + +local dot_for = postfix(".for", { + d(1, function(_, parent) + return sn( + 1, + fmt(for_template, { + item = i(1, "item"), + iter = t(parent.env.POSTFIX_MATCH), + final = i(2, "pass"), + }) + ) + end), +}) + +local items_template = [[ +for {key}, {value} in {iter}: + {final} +]] + +local dot_items = postfix(".items", { + d(1, function(_, parent) + return sn( + 1, + fmt(items_template, { + key = i(1, "key"), + value = i(2, "value"), + iter = t(parent.env.POSTFIX_MATCH), + final = i(3, "pass"), + }) + ) + end), +}) + +local try_template = [[ +try: + {raises} +except {exception} as {ename}: + {final} +]] + +local dot_try = postfix(".try", { + d(1, function(_, parent) + return sn( + 1, + fmt(try_template, { + raises = t(parent.env.POSTFIX_MATCH), + exception = i(1, "Exception"), + ename = i(2, "e"), + final = i(3, "pass"), + }) + ) + end), +}) + +return { + def, + defs, + dot_enum, + dot_for, + dot_items, + dot_try, + enum, +}