├── .lua-format ├── LICENSE ├── README.md ├── example-config.nix ├── flake.nix ├── lib.nix ├── module.nix ├── nix-lspconfig.lua └── test └── init.vim /.lua-format: -------------------------------------------------------------------------------- 1 | indent_width: 2 2 | continuation_indent_width: 2 3 | tab_width: 2 4 | extra_sep_at_table_end: true 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Christian Kögler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nix2NVimRc is a set of 2 | [Nix](https://nixos.org/manual/nix/stable/) functions and Nixpkgs module specification 3 | to generate a [Neovim](https://neovim.io/) configuration (nvimrc) 4 | from a given module configuration. 5 | 6 | The module provides options to configure following items: 7 | 8 | | Neovim configuration item | Nix2NVimRC module option | used Neovim Lua API | 9 | |---|---|---| 10 | | Neovim [option](https://neovim.io/doc/user/options.html) | `opts. = ` | `vim.opt[''] = `| 11 | | Neovim keymap (see also helper function `toKeymap`)| `keymaps[]` | [`vim.keymap.set()`](https://neovim.io/doc/user/lua.html#vim.keymap.set()) | 12 | | Neovim global variable | `vars. = ` | `vim.api.nvim_set_var(, )` | 13 | | Neovim [treesitter](https://neovim.io/doc/user/treesitter.html) | `treesitter.parsers. = ` | `vim.treesitter.language.add(, { path: })` | 14 | | Neovim [LSP](https://neovim.io/doc/user/lsp.html) via plugin [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) | `lspconfig.` | passed to [nix-lspconfig.lua](./nix-lspconfig.lua) | 15 | | Lua plugin setup | `setup.` |`require('...').setup()`| 16 | | Vim expression or file | `vim[]` | `vim.cmd()` | 17 | | Lua expression or file | `lua[]` | inlined | 18 | | Vim plugin as Nix package | `plugins[]` | handled similar to `nixpkgs.vimUtils.packDir` | 19 | 20 | ## Example 21 | 22 | The file `./example-config.nix` contains a minimalistic example configuration. 23 | To generate and use this configuration, execute following steps: 24 | 25 | ```sh 26 | nix-build -E 'with import { }; writeText "init.lua" ((import ./lib.nix).toRc pkgs ./example-config.nix)' 27 | nvim -u NORC --cmd "luafile ./result" 28 | ``` 29 | 30 | To see a more sophisticated example, go to repository [ck3d-nvim-configs](https://github.com/ck3d/ck3d-nvim-configs). 31 | 32 | ## Design Goals 33 | 34 | 1. Output only Lua code for NVim 35 | 2. Keep dependency to `nixpkgs` as small as possible: 36 | - `lib.`: `optional`, `optionals`, `evalModules`, `types`, `mkOption`, and `toposort` 37 | - `pkgs.`: `linkfarm` 38 | 3. No dependency in the Nix Flake to avoid a lock file 39 | 40 | ## Alternative Projects 41 | 42 | - [nixvim](https://github.com/nix-community/nixvim) 43 | - [nix2vim](https://github.com/gytis-ivaskevicius/nix2vim) 44 | -------------------------------------------------------------------------------- /example-config.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | nix2nvimrc, 5 | ... 6 | }: 7 | let 8 | parsers = lib.mapAttrs' ( 9 | n: v: lib.nameValuePair (lib.removePrefix "tree-sitter-" n) "${v}/parser" 10 | ) pkgs.tree-sitter.builtGrammars; 11 | in 12 | { 13 | configs = { 14 | treesitter = { 15 | treesitter.parsers = { 16 | inherit (parsers) nix; 17 | }; 18 | plugins = [ pkgs.vimPlugins.nvim-treesitter ]; 19 | setup.modulePath = "nvim-treesitter.configs"; 20 | setup.args.highlight.enable = true; 21 | }; 22 | telescope = { 23 | plugins = with pkgs.vimPlugins; [ 24 | telescope-nvim 25 | plenary-nvim 26 | popup-nvim 27 | ]; 28 | setup.args = { }; 29 | keymaps = map (nix2nvimrc.toKeymap { silent = true; }) [ 30 | [ 31 | [ 32 | "n" 33 | "v" 34 | ] 35 | "y" 36 | "\"+y" 37 | { } 38 | ] 39 | [ 40 | "n" 41 | "ff" 42 | "Telescope find_files" 43 | { } 44 | ] 45 | [ 46 | "n" 47 | "fg" 48 | (nix2nvimrc.luaExpr "require'telescope.builtin'.live_grep") 49 | { } 50 | ] 51 | ]; 52 | }; 53 | lspconfig = { 54 | after = [ "telescope" ]; 55 | plugins = [ pkgs.vimPlugins.nvim-lspconfig ]; 56 | lspconfig.servers.nixd.config.cmd = [ "${pkgs.nixd}/bin/nixd" ]; 57 | vim = [ ./test/init.vim ]; 58 | }; 59 | empty = { }; 60 | plugin_only.plugins = [ pkgs.vimPlugins.vim-speeddating ]; 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "nix2nvimrc flake"; 3 | outputs = 4 | { self }: 5 | { 6 | lib = import ./lib.nix; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /lib.nix: -------------------------------------------------------------------------------- 1 | let 2 | toLua = 3 | with builtins; 4 | val: 5 | if val == null then 6 | "nil" 7 | else if isString val || isBool val || isInt val || isFloat val then 8 | toJSON val 9 | else if isPath val then 10 | "assert(loadfile('${val}'))()" 11 | else if isAttrs val then 12 | if val ? type && val.type == "derivation" then 13 | toLua "${val}" 14 | else if val ? type && val.type == "lua" then 15 | val.expression 16 | else 17 | "{" + (concatStringsSep "," (map (k: "[${toLua k}]=" + (toLua val.${k})) (attrNames val))) + "}" 18 | else if isList val then 19 | "{" + (concatStringsSep "," (map toLua val)) + "}" 20 | else 21 | throw "type convertion is not implemented"; 22 | 23 | modules = pkgs: [ 24 | { 25 | _module.args = { 26 | inherit pkgs; 27 | }; 28 | } 29 | ./module.nix 30 | ]; 31 | in 32 | { 33 | inherit toLua modules; 34 | 35 | luaExpr = lua: { 36 | type = "lua"; 37 | expression = lua; 38 | }; 39 | 40 | toLuaFn = fn: args: "${fn}(${builtins.concatStringsSep "," (map toLua args)})"; 41 | 42 | toKeymap = 43 | def_opts: 44 | builtins.foldl' (f: f) ( 45 | mode: lhs: rhs: opts: { 46 | inherit mode lhs rhs; 47 | opts = def_opts // opts; 48 | } 49 | ); 50 | 51 | toRc = pkgs: config: (pkgs.lib.evalModules { modules = (modules pkgs) ++ [ config ]; }).config.out; 52 | } 53 | -------------------------------------------------------------------------------- /module.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | nix2nvimrc, 6 | ... 7 | }: 8 | let 9 | inherit (lib) types mkOption; 10 | 11 | # copy from 12 | # https://github.com/NixOS/nixpkgs/blob/63aa55f6f54c1ebd9fdf5746bdbe39fe229c74ff/pkgs/applications/editors/vim/plugins/vim-utils.nix#L171 13 | vimFarm = 14 | prefix: name: drvs: 15 | let 16 | mkEntryFromDrv = drv: { 17 | name = "${prefix}/${lib.getName drv}"; 18 | path = drv; 19 | }; 20 | in 21 | pkgs.linkFarm name (map mkEntryFromDrv drvs); 22 | 23 | exprType = types.submodule { 24 | options = { 25 | type = mkOption { type = types.enum [ "lua" ]; }; 26 | expression = mkOption { type = types.str; }; 27 | }; 28 | }; 29 | 30 | keymapType = types.submodule { 31 | options = { 32 | mode = mkOption { type = types.either types.str (types.listOf types.str); }; 33 | lhs = mkOption { type = types.str; }; 34 | rhs = mkOption { type = types.either types.str exprType; }; 35 | opts = mkOption { type = types.attrs; }; 36 | }; 37 | }; 38 | 39 | expressions = { 40 | lua = mkOption { 41 | type = types.listOf types.str; 42 | default = [ ]; 43 | description = "Lua expressions to execute"; 44 | }; 45 | vim = mkOption { 46 | type = types.listOf (types.either types.str types.path); 47 | default = [ ]; 48 | description = "Vim expressions or files to execute"; 49 | }; 50 | }; 51 | 52 | luaFunctionCallType = 53 | name: 54 | types.submodule { 55 | options = { 56 | modulePath = mkOption { 57 | type = types.nullOr types.str; 58 | default = null; 59 | description = "Module path used to function"; 60 | }; 61 | function = mkOption { 62 | type = types.str; 63 | default = name; 64 | }; 65 | args = mkOption { 66 | type = types.anything; 67 | default = { }; 68 | description = "Argument passed to function"; 69 | }; 70 | }; 71 | }; 72 | 73 | configType = types.submodule { 74 | options = { 75 | after = mkOption { 76 | type = types.listOf types.str; 77 | default = [ ]; 78 | description = "This configuration has to be executed after given configurations."; 79 | }; 80 | setup = mkOption { 81 | type = types.nullOr (luaFunctionCallType "setup"); 82 | default = null; 83 | description = "Lua plugin setup"; 84 | }; 85 | plugins = mkOption { 86 | type = types.listOf types.package; 87 | default = [ ]; 88 | description = "Vim plugin packages"; 89 | }; 90 | vars = mkOption { 91 | type = types.attrs; 92 | default = { }; 93 | description = "Vim global variables"; 94 | }; 95 | opts = mkOption { 96 | type = types.attrs; 97 | default = { }; 98 | description = "Vim options"; 99 | }; 100 | keymaps = mkOption { 101 | type = types.listOf keymapType; 102 | default = [ ]; 103 | }; 104 | treesitter.parsers = mkOption { 105 | type = types.attrsOf types.path; 106 | default = { }; 107 | description = "Attribute set where name is the language and the the value a path to the parser."; 108 | }; 109 | lspconfig = mkOption { 110 | type = types.nullOr lspconfigType; 111 | default = null; 112 | }; 113 | } // expressions; 114 | }; 115 | 116 | lspconfigType = types.submodule { 117 | options = { 118 | servers = mkOption { 119 | type = types.attrsOf ( 120 | types.submodule { 121 | options = { 122 | config = mkOption { 123 | type = types.attrs; 124 | default = { }; 125 | }; 126 | }; 127 | } 128 | ); 129 | }; 130 | capabilities = mkOption { 131 | type = types.either types.attrs types.path; 132 | default = nix2nvimrc.luaExpr "vim.lsp.protocol.make_client_capabilities()"; 133 | }; 134 | keymaps = mkOption { 135 | type = types.listOf keymapType; 136 | default = [ ]; 137 | }; 138 | opts = mkOption { 139 | type = types.attrs; 140 | default = { }; 141 | description = "Vim options"; 142 | }; 143 | on_attach = mkOption { 144 | type = types.either types.attrs types.path; 145 | default = nix2nvimrc.luaExpr "function(client, bufnr) end"; 146 | }; 147 | }; 148 | }; 149 | in 150 | { 151 | options = { 152 | configs = mkOption { 153 | type = types.attrsOf configType; 154 | description = "NVim configurations"; 155 | }; 156 | 157 | enableFns = mkOption { 158 | type = types.listOf (types.functionTo types.bool); 159 | default = [ ]; 160 | description = "Enable functions for a configuration module"; 161 | }; 162 | 163 | out = mkOption { 164 | internal = true; 165 | type = types.str; 166 | }; 167 | packPath = mkOption { 168 | internal = true; 169 | type = types.path; 170 | }; 171 | opt = mkOption { 172 | internal = true; 173 | type = types.attrsOf types.anything; 174 | }; 175 | var = mkOption { 176 | internal = true; 177 | type = types.attrsOf types.anything; 178 | }; 179 | config = mkOption { 180 | internal = true; 181 | type = types.attrsOf types.anything; 182 | }; 183 | }; 184 | 185 | config = 186 | let 187 | inherit (lib) optional optionals toposort; 188 | inherit (nix2nvimrc) toLuaFn toLua; 189 | 190 | configs = 191 | let 192 | res = toposort (a: b: builtins.elem a.name b.after) ( 193 | map (name: config.configs.${name} // { inherit name; }) ( 194 | builtins.filter ( 195 | name: 196 | builtins.foldl' (last: enableFn: last && (enableFn config.configs.${name})) true config.enableFns 197 | ) (builtins.attrNames config.configs) 198 | ) 199 | ); 200 | in 201 | res.result or (throw "Config has a cyclic dependency"); 202 | 203 | packages = builtins.foldl' (a: b: a ++ b.plugins) [ ] configs; 204 | 205 | packdirFolder = "pack/nix-nvimconfig/start"; 206 | in 207 | { 208 | packPath = vimFarm packdirFolder "packdir-start" packages; 209 | 210 | out = 211 | let 212 | vim2str = vim: if builtins.isPath vim then "source ${vim}" else vim; 213 | lspconfigWrapper = "lspconfigWrapper"; 214 | 215 | lspUsed = builtins.any (c: c.lspconfig != null) configs; 216 | 217 | configToLua = 218 | c: 219 | [ "-- ${c.name} (${builtins.concatStringsSep " " (map (p: p.name) c.plugins)})" ] 220 | ++ (map ( 221 | k: 222 | toLuaFn "vim.api.nvim_set_var" [ 223 | k 224 | c.vars.${k} 225 | ] 226 | ) (builtins.attrNames c.vars)) 227 | ++ (map ( 228 | m: 229 | toLuaFn "vim.keymap.set" [ 230 | m.mode 231 | m.lhs 232 | m.rhs 233 | m.opts 234 | ] 235 | ) c.keymaps) 236 | ++ (map (k: "vim.opt[${toLua k}] = ${toLua c.opts.${k}}") (builtins.attrNames c.opts)) 237 | ++ (optional (c.setup != null) ( 238 | toLuaFn "require'${ 239 | if c.setup.modulePath != null then c.setup.modulePath else c.name 240 | }'.${c.setup.function}" [ c.setup.args ] 241 | )) 242 | ++ c.lua 243 | ++ (map (v: toLuaFn "vim.cmd" [ v ]) (map vim2str c.vim)) 244 | ++ (map ( 245 | l: 246 | toLuaFn "vim.treesitter.language.add" [ 247 | l 248 | { path = c.treesitter.parsers.${l}; } 249 | ] 250 | ) (builtins.attrNames c.treesitter.parsers)) 251 | ++ (optional (c.lspconfig != null) (toLuaFn lspconfigWrapper [ c.lspconfig ])); 252 | 253 | init_lua = 254 | [ "-- generated by nix2nvimrc" ] 255 | ++ optionals (packages != [ ]) [ 256 | "vim.opt.packpath:append('${config.packPath}')" 257 | "vim.opt.runtimepath:append('${config.packPath}')" 258 | ] 259 | ++ optional lspUsed "local ${lspconfigWrapper} = ${toLua ./nix-lspconfig.lua}" 260 | ++ builtins.concatMap ( 261 | c: 262 | let 263 | lua = configToLua c; 264 | in 265 | if builtins.length lua <= 1 && c.plugins == [ ] then 266 | builtins.trace "Warning: configuration '${c.name}' has no configuration" [ ] 267 | else 268 | lua 269 | ) configs 270 | ++ [ "-- vim: set filetype=lua:" ]; 271 | in 272 | builtins.concatStringsSep "\n" init_lua; 273 | 274 | opt = builtins.foldl' (a: b: a // b.opts) { } configs; 275 | var = builtins.foldl' (a: b: a // b.vars) { } configs; 276 | config = config.configs; 277 | 278 | _module.args.nix2nvimrc = import ./lib.nix; 279 | }; 280 | } 281 | -------------------------------------------------------------------------------- /nix-lspconfig.lua: -------------------------------------------------------------------------------- 1 | return function(cfg) 2 | for name, lsp in pairs(cfg.servers) do 3 | local config = { 4 | on_attach = function(client, bufnr) 5 | for _, keymap in ipairs(cfg.keymaps) do 6 | vim.keymap.set(keymap.mode, keymap.lhs, keymap.rhs, 7 | vim.tbl_extend('force', keymap.opts, { buffer = bufnr })) 8 | end 9 | 10 | for option, value in pairs(cfg.opts) do 11 | -- TODO: https://neovim.io/doc/user/deprecated.html#nvim_buf_set_option() 12 | vim.api.nvim_buf_set_option(bufnr, option, value) 13 | end 14 | 15 | cfg.on_attach(client, bufnr) 16 | end, 17 | capabilities = cfg.capabilities, 18 | } 19 | if lsp.config ~= nil then 20 | config = vim.tbl_extend('force', config, lsp.config) 21 | end 22 | require 'lspconfig'[name].setup(config) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/init.vim: -------------------------------------------------------------------------------- 1 | " 2 | --------------------------------------------------------------------------------