mirror of
				https://github.com/NotAShelf/nvf.git
				synced 2025-10-31 11:02:37 +00:00 
			
		
		
		
	treewide: rewrite autocompletion module and related stuff (#404)
* modules/completion: rewrite
* treewide: remove vsnip, add luasnip
* nvim-cmp: add default sorting
* nvim-cmp: load after luasnip
* lib: fix docs for mergelessListOf
* docs: add changelog entires for rewrite
* deprecations: add rewrite deprecations
* nvim-cmp: clarify in format description
* docs: fix option reference in release notes
* treewide: remove reduant `// {default = false;}`s
* luasnip: add missing `{option}` for option reference
* deprecations: add entry for vsnip
* nvim-autopairs: use multiline string
* nvim-dap: use outer attribute
	
	
This commit is contained in:
		
					parent
					
						
							
								ef413736e9
							
						
					
				
			
			
				commit
				
					
						7dbd1cd8d1
					
				
			
		
					 33 changed files with 512 additions and 483 deletions
				
			
		|  | @ -3,246 +3,115 @@ | |||
|   config, | ||||
|   ... | ||||
| }: let | ||||
|   inherit (builtins) toJSON; | ||||
|   inherit (lib.modules) mkIf mkMerge; | ||||
|   inherit (lib.attrsets) attrNames mapAttrsToList; | ||||
|   inherit (lib.strings) concatMapStringsSep concatStringsSep optionalString; | ||||
|   inherit (lib.nvim.binds) addDescriptionsToMappings mkSetLuaBinding; | ||||
|   inherit (lib.nvim.dag) entryAnywhere entryAfter; | ||||
|   inherit (lib.modules) mkIf; | ||||
|   inherit (lib.strings) optionalString; | ||||
|   inherit (lib.generators) mkLuaInline; | ||||
|   inherit (lib.nvim.binds) addDescriptionsToMappings; | ||||
|   inherit (lib.nvim.dag) entryAfter; | ||||
|   inherit (lib.nvim.lua) toLuaObject; | ||||
|   inherit (builtins) attrNames; | ||||
| 
 | ||||
|   cfg = config.vim.autocomplete; | ||||
|   lspkindEnabled = config.vim.lsp.enable && config.vim.lsp.lspkind.enable; | ||||
| 
 | ||||
|   self = import ./nvim-cmp.nix {inherit lib;}; | ||||
|   mappingDefinitions = self.options.vim.autocomplete.mappings; | ||||
|   cfg = config.vim.autocomplete.nvim-cmp; | ||||
|   luasnipEnable = config.vim.snippets.luasnip.enable; | ||||
| 
 | ||||
|   self = import ./nvim-cmp.nix {inherit lib config;}; | ||||
|   mappingDefinitions = self.options.vim.autocomplete.nvim-cmp.mappings; | ||||
|   mappings = addDescriptionsToMappings cfg.mappings mappingDefinitions; | ||||
| 
 | ||||
|   builtSources = | ||||
|     concatMapStringsSep | ||||
|     "\n" | ||||
|     (n: "{ name = '${n}'},") | ||||
|     (attrNames cfg.sources); | ||||
| 
 | ||||
|   builtMaps = | ||||
|     concatStringsSep | ||||
|     "\n" | ||||
|     (mapAttrsToList | ||||
|       (n: v: | ||||
|         if v == null | ||||
|         then "" | ||||
|         else "${n} = '${v}',") | ||||
|       cfg.sources); | ||||
| 
 | ||||
|   dagPlacement = | ||||
|     if lspkindEnabled | ||||
|     then entryAfter ["lspkind"] | ||||
|     else entryAnywhere; | ||||
| in { | ||||
|   config = mkIf cfg.enable { | ||||
|     vim.startPlugins = [ | ||||
|       "nvim-cmp" | ||||
|       "cmp-buffer" | ||||
|       "cmp-vsnip" | ||||
|       "cmp-path" | ||||
|       "vim-vsnip" | ||||
|     ]; | ||||
|     vim = { | ||||
|       startPlugins = [ | ||||
|         "nvim-cmp" | ||||
|         "cmp-buffer" | ||||
|         "cmp-path" | ||||
|       ]; | ||||
| 
 | ||||
|     vim.autocomplete.sources = { | ||||
|       "nvim-cmp" = null; | ||||
|       "vsnip" = "[VSnip]"; | ||||
|       "buffer" = "[Buffer]"; | ||||
|       "crates" = "[Crates]"; | ||||
|       "path" = "[Path]"; | ||||
|       "copilot" = "[Copilot]"; | ||||
|     }; | ||||
|       autocomplete.nvim-cmp.sources = { | ||||
|         nvim-cmp = null; | ||||
|         buffer = "[Buffer]"; | ||||
|         path = "[Path]"; | ||||
|       }; | ||||
| 
 | ||||
|     vim.maps.insert = mkMerge [ | ||||
|       (mkSetLuaBinding mappings.complete '' | ||||
|         require('cmp').complete | ||||
|       '') | ||||
|       (let | ||||
|         defaultKeys = | ||||
|           if config.vim.autopairs.enable | ||||
|           then "require('nvim-autopairs').autopairs_cr()" | ||||
|           else "vim.api.nvim_replace_termcodes(${toJSON mappings.confirm.value}, true, false, true)"; | ||||
|       in | ||||
|         mkSetLuaBinding mappings.confirm '' | ||||
|           function() | ||||
|             if not require('cmp').confirm({ select = true }) then | ||||
|               vim.fn.feedkeys(${defaultKeys}, 'n') | ||||
|       autocomplete.nvim-cmp.setupOpts = { | ||||
|         sources = map (s: {name = s;}) (attrNames cfg.sources); | ||||
| 
 | ||||
|         # TODO: try to get nvim-cmp to follow global border style | ||||
|         window = mkIf config.vim.ui.borders.enable { | ||||
|           completion = mkLuaInline "cmp.config.window.bordered()"; | ||||
|           documentation = mkLuaInline "cmp.config.window.bordered()"; | ||||
|         }; | ||||
| 
 | ||||
|         formatting.format = cfg.format; | ||||
|       }; | ||||
| 
 | ||||
|       pluginRC.nvim-cmp = mkIf cfg.enable (entryAfter ["autopairs" "luasnip"] '' | ||||
|         local luasnip = require("luasnip") | ||||
|         local cmp = require("cmp") | ||||
|         cmp.setup(${toLuaObject cfg.setupOpts}) | ||||
|       ''); | ||||
| 
 | ||||
|       # `cmp` and `luasnip` are defined above, in the `nvim-cmp` section | ||||
|       autocomplete.nvim-cmp.setupOpts.mapping = { | ||||
|         ${mappings.complete.value} = mkLuaInline "cmp.mapping.complete()"; | ||||
|         ${mappings.close.value} = mkLuaInline "cmp.mapping.abort()"; | ||||
|         ${mappings.scrollDocsUp.value} = mkLuaInline "cmp.mapping.scroll_docs(-4)"; | ||||
|         ${mappings.scrollDocsDown.value} = mkLuaInline "cmp.mapping.scroll_docs(4)"; | ||||
| 
 | ||||
|         ${mappings.confirm.value} = mkLuaInline '' | ||||
|           cmp.mapping(function(fallback) | ||||
|             if cmp.visible() then | ||||
|               ${ | ||||
|             if luasnipEnable | ||||
|             then '' | ||||
|               if luasnip.expandable() then | ||||
|                 luasnip.expand() | ||||
|               else | ||||
|                 cmp.confirm({ select = true }) | ||||
|               end | ||||
|             '' | ||||
|             else "cmp.confirm({ select = true })" | ||||
|           } | ||||
|             else | ||||
|                 fallback() | ||||
|             end | ||||
|           end | ||||
|         '') | ||||
|       (mkSetLuaBinding mappings.next '' | ||||
|         function() | ||||
|           local has_words_before = function() | ||||
|             local line, col = unpack(vim.api.nvim_win_get_cursor(0)) | ||||
|             return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil | ||||
|           end | ||||
|           end) | ||||
|         ''; | ||||
| 
 | ||||
|           local cmp = require('cmp') | ||||
|         ${mappings.next.value} = mkLuaInline '' | ||||
|           cmp.mapping(function(fallback) | ||||
|             local has_words_before = function() | ||||
|               local line, col = unpack(vim.api.nvim_win_get_cursor(0)) | ||||
|               return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil | ||||
|             end | ||||
| 
 | ||||
|           local feedkey = function(key, mode) | ||||
|             vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(key, true, true, true), mode, true) | ||||
|           end | ||||
|             if cmp.visible() then | ||||
|               cmp.select_next_item() | ||||
|               ${optionalString luasnipEnable '' | ||||
|             elseif luasnip.locally_jumpable(1) then | ||||
|               luasnip.jump(1) | ||||
|           ''} | ||||
|             elseif has_words_before() then | ||||
|               cmp.complete() | ||||
|             else | ||||
|               fallback() | ||||
|             end | ||||
|           end) | ||||
|         ''; | ||||
| 
 | ||||
|           if cmp.visible() then | ||||
|             cmp.select_next_item() | ||||
|           elseif vim.fn['vsnip#available'](1) == 1 then | ||||
|             feedkey("<Plug>(vsnip-expand-or-jump)", "") | ||||
|           elseif has_words_before() then | ||||
|             cmp.complete() | ||||
|           else | ||||
|             local termcode = vim.api.nvim_replace_termcodes(${toJSON mappings.next.value}, true, false, true) | ||||
| 
 | ||||
|             vim.fn.feedkeys(termcode, 'n') | ||||
|           end | ||||
|         end | ||||
|       '') | ||||
|       (mkSetLuaBinding mappings.previous '' | ||||
|         function() | ||||
|           local cmp = require('cmp') | ||||
| 
 | ||||
|           local feedkey = function(key, mode) | ||||
|             vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(key, true, true, true), mode, true) | ||||
|           end | ||||
| 
 | ||||
|           if cmp.visible() then | ||||
|             cmp.select_prev_item() | ||||
|           elseif vim.fn['vsnip#available'](-1) == 1 then | ||||
|             feedkeys("<Plug>(vsnip-jump-prev)", "") | ||||
|           end | ||||
|         end | ||||
|       '') | ||||
|       (mkSetLuaBinding mappings.close '' | ||||
|         require('cmp').mapping.abort() | ||||
|       '') | ||||
|       (mkSetLuaBinding mappings.scrollDocsUp '' | ||||
|         require('cmp').mapping.scroll_docs(-4) | ||||
|       '') | ||||
|       (mkSetLuaBinding mappings.scrollDocsDown '' | ||||
|         require('cmp').mapping.scroll_docs(4) | ||||
|       '') | ||||
|     ]; | ||||
| 
 | ||||
|     vim.maps.command = mkMerge [ | ||||
|       (mkSetLuaBinding mappings.complete '' | ||||
|         require('cmp').complete | ||||
|       '') | ||||
|       (mkSetLuaBinding mappings.close '' | ||||
|         require('cmp').mapping.close() | ||||
|       '') | ||||
|       (mkSetLuaBinding mappings.scrollDocsUp '' | ||||
|         require('cmp').mapping.scroll_docs(-4) | ||||
|       '') | ||||
|       (mkSetLuaBinding mappings.scrollDocsDown '' | ||||
|         require('cmp').mapping.scroll_docs(4) | ||||
|       '') | ||||
|     ]; | ||||
| 
 | ||||
|     vim.maps.select = mkMerge [ | ||||
|       (mkSetLuaBinding mappings.next '' | ||||
|         function() | ||||
|           local cmp = require('cmp') | ||||
|           local has_words_before = function() | ||||
|             local line, col = unpack(vim.api.nvim_win_get_cursor(0)) | ||||
|             return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil | ||||
|           end | ||||
| 
 | ||||
|           local feedkey = function(key, mode) | ||||
|             vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(key, true, true, true), mode, true) | ||||
|           end | ||||
| 
 | ||||
|           if cmp.visible() then | ||||
|             cmp.select_next_item() | ||||
|           elseif vim.fn['vsnip#available'](1) == 1 then | ||||
|             feedkey("<Plug>(vsnip-expand-or-jump)", "") | ||||
|           elseif has_words_before() then | ||||
|             cmp.complete() | ||||
|           else | ||||
|             local termcode = vim.api.nvim_replace_termcodes(${toJSON mappings.next.value}, true, false, true) | ||||
| 
 | ||||
|             vim.fn.feedkeys(termcode, 'n') | ||||
|           end | ||||
|         end | ||||
|       '') | ||||
|       (mkSetLuaBinding mappings.previous '' | ||||
|         function() | ||||
|           local cmp = require('cmp') | ||||
| 
 | ||||
|           local feedkey = function(key, mode) | ||||
|             vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(key, true, true, true), mode, true) | ||||
|           end | ||||
| 
 | ||||
|           if cmp.visible() then | ||||
|             cmp.select_prev_item() | ||||
|           elseif vim.fn['vsnip#available'](-1) == 1 then | ||||
|             feedkeys("<Plug>(vsnip-jump-prev)", "") | ||||
|           end | ||||
|         end | ||||
|       '') | ||||
|     ]; | ||||
| 
 | ||||
|     # TODO: alternative snippet engines to vsnip | ||||
|     # https://github.com/hrsh7th/nvim-cmp/blob/main/doc/cmp.txt#L82 | ||||
|     vim.pluginRC.completion = mkIf (cfg.type == "nvim-cmp") (dagPlacement '' | ||||
|       local nvim_cmp_menu_map = function(entry, vim_item) | ||||
|         -- name for each source | ||||
|         vim_item.menu = ({ | ||||
|           ${builtMaps} | ||||
|         })[entry.source.name] | ||||
|         return vim_item | ||||
|       end | ||||
| 
 | ||||
|       ${optionalString lspkindEnabled '' | ||||
|         lspkind_opts.before = ${cfg.formatting.format} | ||||
|       ''} | ||||
| 
 | ||||
|       local cmp = require'cmp' | ||||
|       cmp.setup({ | ||||
|         ${optionalString config.vim.ui.borders.enable '' | ||||
|         -- explicitly enabled by setting ui.borders.enable = true | ||||
|         -- TODO: try to get nvim-cmp to follow global border style | ||||
|         window = { | ||||
|           completion = cmp.config.window.bordered(), | ||||
|           documentation = cmp.config.window.bordered(), | ||||
|         }, | ||||
|       ''} | ||||
| 
 | ||||
|         snippet = { | ||||
|           expand = function(args) | ||||
|             vim.fn["vsnip#anonymous"](args.body) | ||||
|           end, | ||||
|         }, | ||||
| 
 | ||||
|         sources = { | ||||
|           ${builtSources} | ||||
|         }, | ||||
| 
 | ||||
|         completion = { | ||||
|           completeopt = 'menu,menuone,noinsert', | ||||
|           ${optionalString (!cfg.alwaysComplete) "autocomplete = false"} | ||||
|         }, | ||||
| 
 | ||||
|         formatting = { | ||||
|           format = | ||||
|       ${ | ||||
|         if lspkindEnabled | ||||
|         then "lspkind.cmp_format(lspkind_opts)" | ||||
|         else cfg.formatting.format | ||||
|       }, | ||||
|         } | ||||
|       }) | ||||
|       ${optionalString (config.vim.autopairs.enable && config.vim.autopairs.type == "nvim-autopairs") '' | ||||
|         local cmp_autopairs = require('nvim-autopairs.completion.cmp') | ||||
|         cmp.event:on('confirm_done', cmp_autopairs.on_confirm_done({ map_char = { text = ""} })) | ||||
|       ''} | ||||
|     ''); | ||||
| 
 | ||||
|     vim.snippets.vsnip.enable = | ||||
|       if (cfg.type == "nvim-cmp") | ||||
|       then true | ||||
|       else config.vim.snippets.vsnip.enable; | ||||
|         ${mappings.previous.value} = mkLuaInline '' | ||||
|           cmp.mapping(function(fallback) | ||||
|             if cmp.visible() then | ||||
|               cmp.select_prev_item() | ||||
|               ${optionalString luasnipEnable '' | ||||
|             elseif luasnip.locally_jumpable(-1) then | ||||
|               luasnip.jump(-1) | ||||
|           ''} | ||||
|             else | ||||
|               fallback() | ||||
|             end | ||||
|           end) | ||||
|         ''; | ||||
|       }; | ||||
|     }; | ||||
|   }; | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| _: { | ||||
| { | ||||
|   imports = [ | ||||
|     ./config.nix | ||||
|     ./nvim-cmp.nix | ||||
|  |  | |||
|  | @ -1,72 +1,103 @@ | |||
| {lib, ...}: let | ||||
|   inherit (lib.options) mkEnableOption mkOption literalMD; | ||||
| { | ||||
|   lib, | ||||
|   config, | ||||
|   ... | ||||
| }: let | ||||
|   inherit (lib.options) mkEnableOption mkOption literalExpression literalMD; | ||||
|   inherit (lib.types) str attrsOf nullOr either; | ||||
|   inherit (lib.generators) mkLuaInline; | ||||
|   inherit (lib.nvim.binds) mkMappingOption; | ||||
|   inherit (lib.types) enum attrsOf nullOr str bool; | ||||
|   inherit (lib.nvim.types) mkPluginSetupOption luaInline mergelessListOf; | ||||
|   inherit (lib.nvim.lua) toLuaObject; | ||||
|   inherit (builtins) isString; | ||||
| 
 | ||||
|   cfg = config.vim.autocomplete.nvim-cmp; | ||||
| in { | ||||
|   options.vim = { | ||||
|     autocomplete = { | ||||
|       enable = mkEnableOption "autocomplete" // {default = false;}; | ||||
| 
 | ||||
|       alwaysComplete = mkOption { | ||||
|         type = bool; | ||||
|         description = "Automatically show completion."; | ||||
|         default = true; | ||||
|       }; | ||||
| 
 | ||||
|       mappings = { | ||||
|         complete = mkMappingOption "Complete [nvim-cmp]" "<C-Space>"; | ||||
|         confirm = mkMappingOption "Confirm [nvim-cmp]" "<CR>"; | ||||
|         next = mkMappingOption "Next item [nvim-cmp]" "<Tab>"; | ||||
|         previous = mkMappingOption "Previous item [nvim-cmp]" "<S-Tab>"; | ||||
|         close = mkMappingOption "Close [nvim-cmp]" "<C-e>"; | ||||
|         scrollDocsUp = mkMappingOption "Scroll docs up [nvim-cmp]" "<C-d>"; | ||||
|         scrollDocsDown = mkMappingOption "Scroll docs down [nvim-cmp]" "<C-f>"; | ||||
|       }; | ||||
| 
 | ||||
|       type = mkOption { | ||||
|         type = enum ["nvim-cmp"]; | ||||
|         default = "nvim-cmp"; | ||||
|         description = "Set the autocomplete plugin. Options: [nvim-cmp]"; | ||||
|       }; | ||||
| 
 | ||||
|       sources = mkOption { | ||||
|   options.vim.autocomplete.nvim-cmp = { | ||||
|     enable = mkEnableOption "nvim-cmp"; | ||||
|     setupOpts = mkPluginSetupOption "the autocomplete plugin" { | ||||
|       completion.completeopt = mkOption { | ||||
|         type = str; | ||||
|         default = "menu,menuone,noinsert"; | ||||
|         description = '' | ||||
|           Attribute set of source names for nvim-cmp. | ||||
|           A comma-separated list of options for completion. | ||||
| 
 | ||||
|           If an attribute set is provided, then the menu value of | ||||
|           `vim_item` in the format will be set to the value (if | ||||
|           utilizing the `nvim_cmp_menu_map` function). | ||||
| 
 | ||||
|           Note: only use a single attribute name per attribute set | ||||
|         ''; | ||||
|         type = attrsOf (nullOr str); | ||||
|         default = {}; | ||||
|         example = '' | ||||
|           {nvim-cmp = null; buffer = "[Buffer]";} | ||||
|           See `:help completeopt` for the complete list. | ||||
|         ''; | ||||
|       }; | ||||
| 
 | ||||
|       formatting = { | ||||
|         format = mkOption { | ||||
|           description = '' | ||||
|             The function used to customize the appearance of the completion menu. | ||||
|       sorting.comparators = mkOption { | ||||
|         type = mergelessListOf (either str luaInline); | ||||
|         default = [ | ||||
|           "offset" | ||||
|           "exact" | ||||
|           "score" | ||||
|           "kind" | ||||
|           "length" | ||||
|           "sort_text" | ||||
|         ]; | ||||
|         description = '' | ||||
|           The comparator functions used for sorting completions. | ||||
| 
 | ||||
|             If [](#opt-vim.lsp.lspkind.enable) is true, then the function | ||||
|             will be called before modifications from lspkind. | ||||
| 
 | ||||
|             Default is to call the menu mapping function. | ||||
|           ''; | ||||
|           type = str; | ||||
|           default = "nvim_cmp_menu_map"; | ||||
|           example = literalMD '' | ||||
|             ```lua | ||||
|             function(entry, vim_item) | ||||
|               return vim_item | ||||
|             end | ||||
|             ``` | ||||
|           ''; | ||||
|         }; | ||||
|           You can either pass a valid inline lua function | ||||
|           (see `:help cmp-config.sorting.comparators`), | ||||
|           or a string, in which case the builtin comparator with that name will | ||||
|           be used. | ||||
|         ''; | ||||
|         apply = map ( | ||||
|           c: | ||||
|             if isString c | ||||
|             then mkLuaInline ("cmp.config.compare." + c) | ||||
|             else c | ||||
|         ); | ||||
|       }; | ||||
|     }; | ||||
| 
 | ||||
|     mappings = { | ||||
|       complete = mkMappingOption "Complete [nvim-cmp]" "<C-Space>"; | ||||
|       confirm = mkMappingOption "Confirm [nvim-cmp]" "<CR>"; | ||||
|       next = mkMappingOption "Next item [nvim-cmp]" "<Tab>"; | ||||
|       previous = mkMappingOption "Previous item [nvim-cmp]" "<S-Tab>"; | ||||
|       close = mkMappingOption "Close [nvim-cmp]" "<C-e>"; | ||||
|       scrollDocsUp = mkMappingOption "Scroll docs up [nvim-cmp]" "<C-d>"; | ||||
|       scrollDocsDown = mkMappingOption "Scroll docs down [nvim-cmp]" "<C-f>"; | ||||
|     }; | ||||
| 
 | ||||
|     format = mkOption { | ||||
|       type = luaInline; | ||||
|       default = mkLuaInline '' | ||||
|         function(entry, vim_item) | ||||
|           vim_item.menu = (${toLuaObject cfg.sources})[entry.source.name] | ||||
|           return vim_item | ||||
|         end | ||||
|       ''; | ||||
|       defaultText = literalMD '' | ||||
|         ```lua | ||||
|         function(entry, vim_item) | ||||
|           vim_item.menu = (''${toLuaObject config.vim.autocomplete.nvim-cmp.sources})[entry.source.name] | ||||
|           return vim_item | ||||
|         end | ||||
|         ``` | ||||
|       ''; | ||||
|       description = '' | ||||
|         The function used to customize the completion menu entires. This is | ||||
|         outside of `setupOpts` to allow for an easier integration with | ||||
|         lspkind.nvim. | ||||
| 
 | ||||
|         See `:help cmp-config.formatting.format`. | ||||
|       ''; | ||||
|     }; | ||||
| 
 | ||||
|     sources = mkOption { | ||||
|       type = attrsOf (nullOr str); | ||||
|       default = {}; | ||||
|       description = "The list of sources used by nvim-cmp"; | ||||
|       example = literalExpression '' | ||||
|         { | ||||
|           nvim-cmp = null; | ||||
|           buffer = "[Buffer]"; | ||||
|         } | ||||
|       ''; | ||||
|     }; | ||||
|   }; | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 diniamo
				diniamo