diff --git a/docs/manual/release-notes/rl-0.9.md b/docs/manual/release-notes/rl-0.9.md index 8efbfc285..b4e4b2500 100644 --- a/docs/manual/release-notes/rl-0.9.md +++ b/docs/manual/release-notes/rl-0.9.md @@ -192,6 +192,11 @@ - Lazyload noice.nvim and nvim-web-devicons on `DeferredUIEnter` - Allow nulling treesitter packages for various language modules, filter `null` values in `vim.treesitter.grammars`. +- Added {option}`vim.filetype` module option for registering custom filetypes + via `vim.filetype.add()`, placed in the DAG before `lazyConfigs` so plugins + can lazy-load on registered filetypes. +- Moved `neovim/mappings` to `neovim/init/mappings.nix` to consolidate core + Neovim init options. [jfeo](https://github.com/jfeo): diff --git a/modules/modules.nix b/modules/modules.nix index 654cd5cee..636058058 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -10,7 +10,6 @@ # such as spellchecking, mappings, and the init script (init.vim). neovim = map (p: ./neovim + "/${p}") [ "init" - "mappings" ]; # Individual plugin modules, separated by the type of plugin. diff --git a/modules/neovim/init/default.nix b/modules/neovim/init/default.nix index 3f1950856..e831fab63 100644 --- a/modules/neovim/init/default.nix +++ b/modules/neovim/init/default.nix @@ -5,8 +5,10 @@ ./clipboard.nix ./debug.nix ./diagnostics.nix + ./filetype.nix ./highlight.nix ./lsp.nix + ./mappings.nix ./spellcheck.nix ./util.nix ]; diff --git a/modules/neovim/init/filetype.nix b/modules/neovim/init/filetype.nix new file mode 100644 index 000000000..f08626fd5 --- /dev/null +++ b/modules/neovim/init/filetype.nix @@ -0,0 +1,123 @@ +{ + config, + lib, + ... +}: let + inherit (lib.options) mkOption; + inherit (lib.types) nullOr either oneOf attrsOf str listOf submodule ints; + inherit (lib.nvim.types) luaInline; + inherit (lib.nvim.dag) entryBefore; + inherit (lib.nvim.lua) toLuaObject; + + cfg = config.vim; + + # vim.filetype.add() is quite robust, but this makes for a very + # complex type that we have to handle. It takes a string, a Lua function + # or a dictionary with the priority of the extension. + ftOptionType = attrsOf (oneOf [ + str # "filetype" + luaInline # `function(path, bufnr) ... end` + + # { 'dosini', { priority = 10 } }, + (listOf (either (either str luaInline) (submodule { + options = { + priority = mkOption { + type = ints.unsigned; + description = '' + `vim.filetype.add()` can take an optional priority value to resolve + conflicts where a filetype is registered by multiple patterns. When + priority is specified, file with the higher priority value will be + matched first on conflict. + ''; + }; + }; + }))) + ]); +in { + options.vim.filetype = mkOption { + type = submodule { + options = { + extension = mkOption { + type = nullOr ftOptionType; + default = null; + description = "register a new filetype by extension"; + }; + + filename = mkOption { + type = nullOr ftOptionType; + default = null; + description = "register a new filetype by file name"; + }; + + pattern = mkOption { + type = nullOr ftOptionType; + default = null; + description = "register a new filetype by pattern"; + }; + }; + }; + + default = {}; + example = { + filename = { + ".foorc" = "toml"; + "/etc/foo/config" = "toml"; + "todo.txt" = "todotxt"; + }; + + pattern = { + ".*%.scm" = "query"; + ".*README.(%a+)" = '' + function(path, bufnr, ext) + if ext == 'md' then + return 'markdown' + elseif ext == 'rst' then + return 'rst' + end + end, + ''; + }; + + extension = { + mdx = "markdown"; + bar = lib.generators.mkLuaInline '' + { + bar = function(path, bufnr) + if some_condition() then + return 'barscript', function(bufnr) + -- Set a buffer variable + vim.b[bufnr].barscript_version = 2 + end + end + return 'bar' + end, + } + ''; + }; + }; + + description = '' + Additional filetypes to be registered through `vim.filetype.add()` + + Filetype mappings can be added either by extension or by filename. The + key can be either the "tail" or the full file path. The full file path + is checked first, followed by the file name. If a match is not found + using the filename, then the filename is matched against the list of + Lua patterns (sorted by priority) until a match is found. + + If a pattern matching does not find a filetype, then the file extension + is used. + + See `:h vim.filetype.add()` for more details. + ''; + }; + + config = { + # XXX: some plugins can be loaded on filetype, and unless the filetypes + # are registered first, chances are custom filetypes will not be usable + # for lazy-loading on ft. + vim.luaConfigRC.filetype = entryBefore ["lazyConfigs"] '' + vim.filetype.add(${toLuaObject cfg.filetype}) + ''; + }; +} diff --git a/modules/neovim/mappings/options.nix b/modules/neovim/init/mappings.nix similarity index 73% rename from modules/neovim/mappings/options.nix rename to modules/neovim/init/mappings.nix index de19b54d8..0f6651011 100644 --- a/modules/neovim/mappings/options.nix +++ b/modules/neovim/init/mappings.nix @@ -1,19 +1,32 @@ -{lib, ...}: let - inherit (lib.options) mkEnableOption mkOption literalMD; +{ + config, + lib, + ... +}: let + inherit (lib.modules) mkMerge; + inherit (lib.options) mkOption literalMD; inherit (lib.types) either str listOf attrsOf nullOr submodule; + inherit (lib.attrsets) mapAttrsToList; + inherit (lib.lists) flatten; + inherit (lib.trivial) pipe; + inherit (lib.options) mkEnableOption; inherit (lib.nvim.config) mkBool; mapConfigOptions = { desc = mkOption { type = nullOr str; default = null; - description = "A description of this keybind, to be shown in which-key, if you have it enabled."; + description = '' + Description for the keybind, to be shown in which-key, if you have enabled + in the module system. + ''; }; action = mkOption { type = str; description = "The command to execute."; }; + lua = mkBool false '' If true, `action` is considered to be lua code. Thus, it will not be wrapped in `""`. @@ -55,6 +68,22 @@ }); default = {}; }; + + legacyMapModes = { + normal = ["n"]; + insert = ["i"]; + select = ["s"]; + visual = ["v"]; + terminal = ["t"]; + normalVisualOp = ["n" "v" "o"]; + visualOnly = ["n" "x"]; + operator = ["o"]; + insertCommand = ["i" "c"]; + lang = ["l"]; + command = ["c"]; + }; + + cfg = config.vim; in { options.vim = { vendoredKeymaps.enable = @@ -101,4 +130,27 @@ in { command = legacyMapOption "command-line"; }; }; + + config = { + vim.keymaps = mkMerge [ + ( + pipe cfg.maps + [ + (mapAttrsToList ( + oldMode: keybinds: + mapAttrsToList ( + key: bind: + bind + // { + inherit key; + mode = legacyMapModes.${oldMode}; + } + ) + keybinds + )) + flatten + ] + ) + ]; + }; } diff --git a/modules/neovim/mappings/config.nix b/modules/neovim/mappings/config.nix deleted file mode 100644 index a62a6ca2d..000000000 --- a/modules/neovim/mappings/config.nix +++ /dev/null @@ -1,49 +0,0 @@ -{ - config, - lib, - ... -}: let - inherit (lib.modules) mkMerge; - inherit (lib.trivial) pipe; - inherit (lib.attrsets) mapAttrsToList; - inherit (lib.lists) flatten; - - legacyMapModes = { - normal = ["n"]; - insert = ["i"]; - select = ["s"]; - visual = ["v"]; - terminal = ["t"]; - normalVisualOp = ["n" "v" "o"]; - visualOnly = ["n" "x"]; - operator = ["o"]; - insertCommand = ["i" "c"]; - lang = ["l"]; - command = ["c"]; - }; - - cfg = config.vim; -in { - config = { - vim.keymaps = mkMerge [ - ( - pipe cfg.maps - [ - (mapAttrsToList ( - oldMode: keybinds: - mapAttrsToList ( - key: bind: - bind - // { - inherit key; - mode = legacyMapModes.${oldMode}; - } - ) - keybinds - )) - flatten - ] - ) - ]; - }; -} diff --git a/modules/neovim/mappings/default.nix b/modules/neovim/mappings/default.nix deleted file mode 100644 index fe9e1b8e6..000000000 --- a/modules/neovim/mappings/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{ - imports = [ - ./config.nix - ./options.nix - ]; -}