modules/completion: rewrite

This commit is contained in:
diniamo 2024-10-06 12:42:35 +02:00
commit 130f64d1ee
20 changed files with 331 additions and 362 deletions

View file

@ -3,246 +3,176 @@
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) entryAnywhere;
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;
vsnipEnable = config.vim.snippets.vsnip.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')
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
autopairs.nvim-autopairs.setupOpts.map_cr = false;
local cmp = require('cmp')
autocomplete.nvim-cmp.setupOpts = {
sources = map (s: {name = s;}) (attrNames cfg.sources);
local feedkey = function(key, mode)
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(key, true, true, true), mode, true)
end
# 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()";
};
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)
formatting.format = cfg.format;
};
vim.fn.feedkeys(termcode, 'n')
end
end
'')
(mkSetLuaBinding mappings.previous ''
function()
local cmp = require('cmp')
pluginRC.nvim-cmp = mkIf cfg.enable (entryAnywhere ''
local cmp = require("cmp")
cmp.setup(${toLuaObject cfg.setupOpts})
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.nvim-autopairs.enable
''
cmp.event:on('confirm_done', require("nvim-autopairs.completion.cmp").on_confirm_done({ map_char = { text = "" } }))
''
}
})
${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;
keymaps = [
{
mode = ["i" "c"];
key = mappings.complete.value;
lua = true;
action = "require('cmp').complete";
desc = mappings.complete.description;
}
{
mode = "i";
key = mappings.confirm.value;
lua = true;
action = let
defaultKeys =
if config.vim.autopairs.nvim-autopairs.enable
then "require('nvim-autopairs').autopairs_cr()"
else "vim.api.nvim_replace_termcodes(${toLuaObject mappings.confirm.value}, true, false, true)";
in ''
function()
if not require('cmp').confirm({ select = true }) then
vim.fn.feedkeys(${defaultKeys}, 'n')
end
end
'';
desc = mappings.confirm.description;
}
{
mode = ["i" "s"];
key = mappings.next.value;
lua = true;
action = ''
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
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_next_item()
${
optionalString vsnipEnable
''
elseif vim.fn['vsnip#available'](1) == 1 then
feedkey("<Plug>(vsnip-expand-or-jump)", "")
''
}
elseif has_words_before() then
cmp.complete()
else
vim.fn.feedkeys(vim.api.nvim_replace_termcodes(${toLuaObject mappings.next.value}, true, false, true), 'n')
end
end
'';
desc = mappings.next.description;
}
{
mode = ["i" "s"];
key = mappings.previous.value;
lua = true;
action = ''
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()
${
optionalString vsnipEnable
''
elseif vim.fn['vsnip#available'](-1) == 1 then
feedkeys("<Plug>(vsnip-jump-prev)", "")
''
}
end
end
'';
desc = mappings.previous.description;
}
{
mode = ["i" "c"];
key = mappings.close.value;
lua = true;
action = "require('cmp').mapping.abort()";
desc = mappings.close.description;
}
{
mode = ["i" "c"];
key = mappings.scrollDocsUp.value;
lua = true;
action = "require('cmp').mapping.scroll_docs(-4)";
desc = mappings.scrollDocsUp.description;
}
{
mode = ["i" "c"];
key = mappings.scrollDocsDown.value;
lua = true;
action = "require('cmp').mapping.scroll_docs(4)";
desc = mappings.scrollDocsDown.description;
}
];
};
};
}

View file

@ -1,4 +1,4 @@
_: {
{
imports = [
./config.nix
./nvim-cmp.nix

View file

@ -1,72 +1,75 @@
{lib, ...}: let
inherit (lib.options) mkEnableOption mkOption literalMD;
{
lib,
config,
...
}: let
inherit (lib.options) mkEnableOption mkOption literalExpression literalMD;
inherit (lib.types) str attrsOf nullOr;
inherit (lib.generators) mkLuaInline;
inherit (lib.nvim.binds) mkMappingOption;
inherit (lib.types) enum attrsOf nullOr str bool;
inherit (lib.nvim.types) mkPluginSetupOption luaInline;
inherit (lib.nvim.lua) toLuaObject;
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" // {default = false;};
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.
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>";
};
If [](#opt-vim.lsp.lspkind.enable) is true, then the function
will be called before modifications from lspkind.
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` because of internal reasons.
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
```
'';
};
};
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]";
}
'';
};
};
}