{ config, pkgs, lib, ... }: let cfg = config.programs.neovim; toLua = lib.generators.toLua { }; buildPluginConfig = p: let pluginConfig = if builtins.hasAttr "plugin" p then p else { plugin = p; }; name = pluginConfig.plugin.name; config = if (builtins.isNull pluginConfig.config or null) then "" else pluginConfig.config; mappings = if (builtins.isNull pluginConfig.mappings or null) then "" else generateMappings pluginConfig.mappings; mergedConfig = config + mappings; in [ { plugin = pluginConfig.plugin; config = lib.modules.mkIf (mergedConfig != "") '' Plugins[${toLua name}] = {} Plugins[${toLua name}].setup = function() ${lib.strings.concatMapStrings (line: if line == "" then "" else " ${line}\n") (lib.strings.splitString "\n" mergedConfig) } end Plugins[${toLua name}].setup() ''; type = "lua"; } ] ++ ( lib.lists.concatMap buildPluginConfig pluginConfig.dependencies or [ ] ); lspPluginConfig = with pkgs.vimPlugins; { plugin = nvim-lspconfig; dependencies = [ nvim-cmp cmp-nvim-lsp cmp_luasnip luasnip ]; config = (builtins.readFile ./lsp-config.lua) + '' local capabilities = vim.lsp.protocol.make_client_capabilities() capabilities.workspace.didChangeWatchedFiles.dynamicRegistration = true local lspconfig = require('lspconfig') local util = require('lspconfig.util') local on_attach = function (client, bufnr) local function buf_set_option(...) vim.api.nvim_buf_set_option(bufnr, ...) end local map = function (lhs, rhs) print('set mapping ' .. lhs) end -- Enable completion triggered by buf_set_option('omnifunc', 'v:lua.vim.lsp.omnifunc') -- Mappings. -- See `:help vim.lsp.*` for documentation on any of the below functions ${lib.strings.concatLines (lib.attrsets.mapAttrsToList (name: value: '' local buf_${name} = vim.lsp.buf[${toLua name}] if buf_${name} then vim.keymap.set('n', ${toLua value}, buf_${name}, { buffer = bufnr }) end '' ) cfg.lsp.mappings.buf)} ${lib.strings.concatLines (lib.attrsets.mapAttrsToList (name: value: '' local diagnostic_${name} = vim.lsp.buf[${toLua name}] if diagnostic_${name} then vim.keymap.set('n', ${toLua value}, diagnostic_${name}, { buffer = bufnr }) end '') cfg.lsp.mappings.diagnostic)} end vim.env.PATH = ${toLua (lib.makeBinPath (map (s: s.package) (builtins.filter (s: builtins.hasAttr "package" s) cfg.lsp.servers)) + ":")} .. vim.env.PATH local servers = ${toLua (map ( { name, config ? {}, rootPattern ? null, ... }: { inherit name; config = config // (if (builtins.isNull rootPattern) then {} else { inherit rootPattern; }); } ) cfg.lsp.servers)} for _, server in ipairs(servers) do local server_config = server.config server_config.on_attach = on_attach server_config.capabilities = capabilities server_config.flags = { debounce_text_changes = 150 } if server_config.rootPattern then server_config.root_dir = util.root_pattern( unpack(server_config.rootPattern) ) end lspconfig[server.name].setup(server_config) end ''; }; treesitterPluginConfig = with pkgs.vimPlugins; { plugin = nvim-treesitter.withPlugins cfg.withTreesitterPlugins; config = '' require('nvim-treesitter.configs').setup(${toLua { highlight = { enable = true; additional_vim_regex_highlighting = false; }; }}); ''; }; formatterPluginConfig = with pkgs.vimPlugins; { plugin = formatter-nvim; config = '' local formatter = require('formatter') local util = require('formatter.util') formatter.setup({ filetype = { ${lib.strings.concatMapStringsSep ",\n " ({ exe, args, stdin, no_append, filetypes, ... }: lib.strings.concatMapStringsSep ",\n " (filetype: '' [${toLua filetype}] = { function () return { exe = ${toLua exe}, args = { ${lib.strings.concatMapStringsSep ",\n " (arg: if arg == "<>" then "util.escape_path(util.get_current_buffer_file_path())" else toLua arg ) (args "<>")} }, stdin = ${toLua stdin}, no_append = ${toLua no_append}, } end } '') filetypes ) cfg.formatters} } }) ${generateAutoCommand { event = "BufWritePost"; pattern = lib.lists.concatMap ({ globs, ... }: globs) cfg.formatters; command = "FormatWrite"; group = "FormatAutogroup"; }} ''; }; customTypes = let inherit (lib) mkOption mkEnableOption types; in { mapping = types.submodule { options = { rhs = mkOption { type = types.str; }; lua = mkEnableOption { }; options = mkOption { type = types.nullOr ( types.submodule { options = { buffer = mkOption { type = types.nullOr (types.either types.int types.bool); default = null; }; nowait = mkOption { type = types.nullOr types.bool; default = null; }; silent = mkOption { type = types.nullOr types.bool; default = null; }; expr = mkOption { type = types.nullOr types.bool; default = null; }; script = mkOption { type = types.nullOr types.bool; default = null; }; unique = mkOption { type = types.nullOr types.bool; default = null; }; replace_keycodes = mkOption { type = types.nullOr types.bool; default = null; }; }; } ); default = { }; }; }; }; modeMappings = types.attrsOf (types.either types.str customTypes.mapping); mappings = types.submodule { options = { normal = mkOption { type = customTypes.modeMappings; default = { }; }; insert = mkOption { type = customTypes.modeMappings; default = { }; }; visual = mkOption { type = customTypes.modeMappings; default = { }; }; command = mkOption { type = customTypes.modeMappings; default = { }; }; select = mkOption { type = customTypes.modeMappings; default = { }; }; }; }; plugin = types.either types.package (types.submodule { options = { plugin = mkOption { type = types.package; }; dependencies = mkOption { type = types.listOf customTypes.plugin; default = [ ]; }; mappings = mkOption { type = customTypes.mappings; default = { }; }; config = mkOption { type = types.nullOr types.str; default = null; }; }; }); autoCommand = types.submodule { options = { event = mkOption { type = types.either types.str (types.listOf types.str); }; pattern = mkOption { type = types.either types.str (types.listOf types.str); }; command = mkOption { type = types.str; }; }; }; lspServer = types.submodule { options = { name = mkOption { type = types.str; }; config = mkOption { type = types.attrs; default = { }; }; package = mkOption { type = types.nullOr types.package; default = null; }; rootPattern = mkOption { type = types.nullOr (types.listOf types.str); default = null; }; }; }; formatter = types.submodule { options = { filetypes = mkOption { type = types.listOf types.str; }; globs = mkOption { type = types.listOf types.str; }; exe = mkOption { type = types.either types.path types.str; }; args = mkOption { type = types.functionTo (types.listOf types.str); default = _: [ ]; }; stdin = mkEnableOption { }; no_append = mkEnableOption { }; }; }; }; generateMappings = mappings: lib.strings.concatLines ( lib.attrsets.mapAttrsToList (mode: modeMappings: lib.strings.concatLines ( lib.attrsets.mapAttrsToList (name: value: let mapping = { lhs = name; lua = false; options = { }; } // (if builtins.isString value then { rhs = value; } else value); args = [ (toLua (builtins.substring 0 1 mode)) (toLua mapping.lhs) (if mapping.lua then mapping.rhs else toLua mapping.rhs) ( toLua ( builtins.listToAttrs ( builtins.filter ({ value, ... }: !isNull (value)) (lib.attrsets.attrsToList mapping.options) ) ) ) ]; in "vim.keymap.set(${lib.strings.concatStringsSep ", " args})" ) modeMappings ) ) mappings ); generateAutoCommand = { event, pattern, command, group ? null }: '' vim.api.nvim_create_autocmd(${toLua event}, { pattern = ${toLua pattern}, command = ${toLua command}, group = ${if isNull group then toLua group else '' vim.api.nvim_create_augroup(${toLua group}, { clear = true }) ''}, }) ''; generateAutoCommands = autoCommands: lib.strings.concatLines ( map generateAutoCommand autoCommands ); generateSignDefinitions = signs: lib.strings.concatLines ( lib.attrsets.mapAttrsToList (name: value: let hl = "DiagnosticSign${lib.strings.toUpper (builtins.substring 0 1 name)}${builtins.substring 1 (-1) name}"; in "vim.fn.sign_define(${toLua hl}, ${toLua { text = value; texthl = hl; numhl = ""; }})" ) signs ); in { options.programs.neovim = let inherit (lib) mkOption types; in { mappings = mkOption { type = customTypes.mappings; default = { }; }; plug = mkOption { type = types.listOf customTypes.plugin; default = [ ]; }; leader = mkOption { type = types.str; default = "\\"; }; options = mkOption { type = types.attrs; default = { }; }; snippets = mkOption { type = types.attrsOf types.path; default = { }; }; autoCommands = mkOption { type = types.listOf customTypes.autoCommand; default = [ ]; }; env = mkOption { type = types.attrsOf types.str; default = { }; }; signs = { error = mkOption { type = types.str; default = ""; }; warning = mkOption { type = types.str; default = ""; }; info = mkOption { type = types.str; default = ""; }; hint = mkOption { type = types.str; default = ""; }; }; lsp = { servers = mkOption { type = types.listOf customTypes.lspServer; default = [ ]; }; mappings = { buf = mkOption { type = types.attrsOf types.str; default = { }; }; diagnostic = mkOption { type = types.attrsOf types.str; default = { }; }; }; }; withTreesitterPlugins = mkOption { type = types.functionTo (types.listOf types.package); default = _: [ ]; }; formatters = mkOption { type = types.listOf customTypes.formatter; default = [ ]; }; }; config = { programs.neovim = { extraLuaConfig = lib.strings.concatLines [ "local Plugins = {}" (generateMappings cfg.mappings) (generateAutoCommands cfg.autoCommands) (lib.strings.concatLines (lib.attrsets.mapAttrsToList (name: value: "vim.env.${name} = ${toLua value}") cfg.env)) (lib.strings.concatLines (lib.attrsets.mapAttrsToList (name: value: "vim.opt.${name} = ${toLua value}") cfg.options)) (generateSignDefinitions cfg.signs) "vim.g.mapleader = ${toLua cfg.leader}" '' local undodir = ${toLua ( if builtins.hasAttr "undodir" cfg.options then cfg.options.undodir else "${config.xdg.cacheHome}/nvim/undo" )} vim.opt.undodir = undodir vim.fn.mkdir(undodir, 'p') '' ]; plugins = lib.lists.concatMap buildPluginConfig ( cfg.plug ++ [ lspPluginConfig treesitterPluginConfig formatterPluginConfig ] ); vimAlias = lib.mkForce false; vimdiffAlias = lib.mkForce false; }; xdg.configFile = ( lib.attrsets.mapAttrs' (name: value: { name = "nvim/${name}"; value = { source = value; }; }) cfg.snippets ); home.shellAliases = let nvim = ''nvim --listen "$XDG_RUNTIME_DIR/nvimsocket"''; in { inherit nvim; vim = nvim; vimdiff = "${nvim} -d"; }; }; }