diff --git a/flake.nix b/flake.nix index d5a7b709..0fd3cc8c 100644 --- a/flake.nix +++ b/flake.nix @@ -31,7 +31,7 @@ }; homeManagerModules = { - nvf = import ./flake/modules/home-manager.nix {inherit lib self;}; + nvf = import ./flake/modules/home-manager.nix self.packages lib; default = self.homeManagerModules.nvf; neovim-flake = lib.warn '' @@ -42,7 +42,7 @@ }; nixosModules = { - nvf = import ./flake/modules/nixos.nix {inherit lib self;}; + nvf = import ./flake/modules/nixos.nix self.packages lib; default = self.nixosModules.nvf; neovim-flake = lib.warn '' diff --git a/flake/modules/home-manager.nix b/flake/modules/home-manager.nix index 715f7537..77b8448c 100644 --- a/flake/modules/home-manager.nix +++ b/flake/modules/home-manager.nix @@ -1,28 +1,22 @@ # Home Manager module -{ - self, - lib, -}: { +packages: lib: { config, pkgs, ... }: let - inherit (self) packages inputs; inherit (lib) maintainers; inherit (lib.modules) mkIf mkAliasOptionModule; inherit (lib.lists) optional; inherit (lib.options) mkOption mkEnableOption literalExpression; - inherit (lib.types) anything bool submoduleWith; + inherit (lib.types) attrsOf anything bool; + inherit (lib.nvim) neovimConfiguration; + inherit (lib.nvim.types) anythingConcatLists; cfg = config.programs.nvf; - nvfModule = submoduleWith { - description = "Nvf module"; - class = "nvf"; - specialArgs = { - inherit pkgs lib inputs; - }; - modules = import ../../modules/modules.nix {inherit pkgs lib;}; + neovimConfigured = neovimConfiguration { + inherit pkgs; + modules = [cfg.settings]; }; in { imports = [ @@ -61,7 +55,7 @@ in { }; settings = mkOption { - type = nvfModule; + type = attrsOf anythingConcatLists; default = {}; description = "Attribute set of nvf preferences."; example = literalExpression '' @@ -84,7 +78,7 @@ in { }; config = mkIf cfg.enable { - programs.nvf.finalPackage = cfg.settings.vim.build.finalPackage; + programs.nvf.finalPackage = neovimConfigured.neovim; home = { sessionVariables = mkIf cfg.defaultEditor {EDITOR = "nvim";}; diff --git a/flake/modules/nixos.nix b/flake/modules/nixos.nix index ecc173a1..022b3d94 100644 --- a/flake/modules/nixos.nix +++ b/flake/modules/nixos.nix @@ -1,28 +1,22 @@ # NixOS module -{ - self, - lib, -}: { +packages: lib: { config, pkgs, ... }: let - inherit (self) inputs packages; inherit (lib) maintainers; inherit (lib.modules) mkIf mkOverride mkAliasOptionModule; inherit (lib.lists) optional; inherit (lib.options) mkOption mkEnableOption literalExpression; - inherit (lib.types) anything bool submoduleWith; + inherit (lib.types) attrsOf anything bool; + inherit (lib.nvim) neovimConfiguration; + inherit (lib.nvim.types) anythingConcatLists; cfg = config.programs.nvf; - nvfModule = submoduleWith { - description = "Nvf module"; - class = "nvf"; - specialArgs = { - inherit pkgs lib inputs; - }; - modules = import ../../modules/modules.nix {inherit pkgs lib;}; + neovimConfigured = neovimConfiguration { + inherit pkgs; + modules = [cfg.settings]; }; in { imports = [ @@ -61,7 +55,7 @@ in { }; settings = mkOption { - type = nvfModule; + type = attrsOf anythingConcatLists; default = {}; description = "Attribute set of nvf preferences."; example = literalExpression '' @@ -84,7 +78,7 @@ in { }; config = mkIf cfg.enable { - programs.nvf.finalPackage = cfg.settings.vim.build.finalPackage; + programs.nvf.finalPackage = neovimConfigured.neovim; environment = { variables.EDITOR = mkIf cfg.defaultEditor (mkOverride 900 "nvim"); diff --git a/lib/types/custom.nix b/lib/types/custom.nix index c42cd2ce..3d4a2bcb 100644 --- a/lib/types/custom.nix +++ b/lib/types/custom.nix @@ -1,8 +1,57 @@ {lib}: let - inherit (lib.options) mergeEqualOption; - inherit (lib.strings) isString stringLength match; - inherit (lib.types) listOf mkOptionType; + inherit (lib.options) showOption showFiles getFiles mergeOneOption mergeEqualOption; + inherit (lib.strings) isString isStringLike; + inherit (lib.types) anything attrsOf listOf mkOptionType; + inherit (lib.nvim.types) anythingConcatLists; + inherit (builtins) typeOf isAttrs any head concatLists stringLength match; in { + # HACK: Does this break anything in our case? + # A modified version of the nixpkgs anything type that concatenates lists + # This isn't the default because the order in which the lists are concatenated depends on the order in which the modules are imported, + # which makes it non-deterministic + anythingConcatLists = + anything + // { + merge = loc: defs: let + getType = value: + if isAttrs value && isStringLike value + then "stringCoercibleSet" + else typeOf value; + + # Throw an error if not all defs have the same type + checkType = getType (head defs).value; + commonType = + if any (def: getType def.value != checkType) defs + then throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}" + else checkType; + + mergeFunctions = { + # Recursively merge attribute sets + set = (attrsOf anythingConcatLists).merge; + + # Overridden behavior for lists, that concatenates lists + list = _: defs: concatLists (map (e: e.value) defs); + + # This means it's a package, only accept a single definition + stringCoercibleSet = mergeOneOption; + + # This works by passing the argument to the functions, + # and merging their returns values instead + lambda = loc: defs: arg: + anythingConcatLists.merge + (loc ++ [""]) + (map (def: { + inherit (def) file; + value = def.value arg; + }) + defs); + }; + in + # Merge the defs with the correct function from above, if available + # otherwise only allow equal values + (mergeFunctions.${commonType} or mergeEqualOption) loc defs; + }; + mergelessListOf = elemType: let super = listOf elemType; in diff --git a/lib/types/default.nix b/lib/types/default.nix index c1c16715..73b35956 100644 --- a/lib/types/default.nix +++ b/lib/types/default.nix @@ -11,5 +11,5 @@ in { inherit (typesDag) dagOf; inherit (typesPlugin) pluginsOpt extraPluginType mkPluginSetupOption luaInline pluginType borderType; inherit (typesLanguage) diagnostics mkGrammarOption; - inherit (customTypes) char hexColor mergelessListOf; + inherit (customTypes) anythingConcatLists char hexColor mergelessListOf; } diff --git a/modules/default.nix b/modules/default.nix index a207e0d1..a2f8730d 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -9,8 +9,9 @@ extraModules ? [], configuration ? {}, }: let - inherit (lib.strings) toString; - inherit (lib.lists) concatLists; + inherit (pkgs) vimPlugins; + inherit (lib.strings) isString toString; + inherit (lib.lists) filter map concatLists; # import modules.nix with `check`, `pkgs` and `lib` as arguments # check can be disabled while calling this file is called @@ -20,12 +21,7 @@ # evaluate the extended library with the modules # optionally with any additional modules passed by the user module = lib.evalModules { - specialArgs = - extraSpecialArgs - // { - inherit inputs; - modulesPath = toString ./.; - }; + specialArgs = extraSpecialArgs // {modulesPath = toString ./.;}; modules = concatLists [ nvimModules modules @@ -40,11 +36,102 @@ extraModules)) ]; }; + + # alias to the internal configuration + vimOptions = module.config.vim; + + noBuildPlug = {pname, ...} @ attrs: let + src = inputs."plugin-${attrs.pname}"; + in + { + version = src.shortRev or src.shortDirtyRev or "dirty"; + outPath = src; + passthru.vimPlugin = false; + } + // attrs; + + # build a vim plugin with the given name and arguments + # if the plugin is nvim-treesitter, warn the user to use buildTreesitterPlug + # instead + buildPlug = attrs: let + src = inputs."plugin-${attrs.pname}"; + in + pkgs.vimUtils.buildVimPlugin ( + { + version = src.shortRev or src.shortDirtyRev or "dirty"; + inherit src; + } + // attrs + ); + + buildTreesitterPlug = grammars: vimPlugins.nvim-treesitter.withPlugins (_: grammars); + + pluginBuilders = { + nvim-treesitter = buildTreesitterPlug vimOptions.treesitter.grammars; + flutter-tools-patched = buildPlug { + pname = "flutter-tools"; + patches = [../patches/flutter-tools.patch]; + }; + }; + + buildConfigPlugins = plugins: + map ( + plug: + if (isString plug) + then pluginBuilders.${plug} or (noBuildPlug {pname = plug;}) + else plug + ) (filter (f: f != null) plugins); + + # built (or "normalized") plugins that are modified + builtStartPlugins = buildConfigPlugins vimOptions.startPlugins; + builtOptPlugins = map (package: package // {optional = true;}) (buildConfigPlugins vimOptions.optPlugins); + + # additional Lua and Python3 packages, mapped to their respective functions + # to conform to the format mnw expects. end user should + # only ever need to pass a list of packages, which are modified + # here + extraLuaPackages = ps: map (x: ps.${x}) vimOptions.luaPackages; + extraPython3Packages = ps: map (x: ps.${x}) vimOptions.python3Packages; + + # Wrap the user's desired (unwrapped) Neovim package with arguments that'll be used to + # generate a wrapped Neovim package. + neovim-wrapped = inputs.mnw.lib.wrap pkgs { + neovim = vimOptions.package; + plugins = builtStartPlugins ++ builtOptPlugins; + appName = "nvf"; + extraBinPath = vimOptions.extraPackages; + initLua = vimOptions.builtLuaConfigRC; + luaFiles = vimOptions.extraLuaFiles; + + inherit (vimOptions) viAlias vimAlias withRuby withNodeJs withPython3; + inherit extraLuaPackages extraPython3Packages; + }; + + dummyInit = pkgs.writeText "nvf-init.lua" vimOptions.builtLuaConfigRC; + # Additional helper scripts for printing and displaying nvf configuration + # in your commandline. + printConfig = pkgs.writers.writeDashBin "nvf-print-config" "cat ${dummyInit}"; + printConfigPath = pkgs.writers.writeDashBin "nvf-print-config-path" "echo -n ${dummyInit}"; in { inherit (module) options config; inherit (module._module.args) pkgs; # Expose wrapped neovim-package for userspace # or module consumption. - neovim = module.config.vim.build.finalPackage; + neovim = pkgs.symlinkJoin { + name = "nvf-with-helpers"; + paths = [neovim-wrapped printConfig printConfigPath]; + postBuild = "echo Helpers added"; + + # Allow evaluating vimOptions, i.e., config.vim from the packages' passthru + # attribute. For example, packages.x86_64-linux.neovim.passthru.neovimConfig + # will return the configuration in full. + passthru.neovimConfig = vimOptions; + + meta = + neovim-wrapped.meta + // { + description = "Wrapped Neovim package with helper scripts to print the config (path)"; + }; + }; } diff --git a/modules/modules.nix b/modules/modules.nix index 6e05c592..bc441e96 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -49,7 +49,6 @@ # using the configuration passed in `neovim` and `plugins` modules. wrapper = map (p: ./wrapper + "/${p}") [ "build" - "environment" "rc" "warnings" "lazy" diff --git a/modules/wrapper/build/config.nix b/modules/wrapper/build/config.nix index ee5b4fda..97788bd4 100644 --- a/modules/wrapper/build/config.nix +++ b/modules/wrapper/build/config.nix @@ -1,116 +1,13 @@ { - inputs, - lib, config, - pkgs, + lib, ... -} -: let - inherit (pkgs) vimPlugins; - inherit (lib.strings) isString; - inherit (lib.lists) filter map; - inherit (builtins) path; +}: let + inherit (lib.attrsets) attrValues; - # alias to the internal configuration - vimOptions = config.vim; - - noBuildPlug = pname: let - input = inputs."plugin-${pname}"; - version = input.shortRev or input.shortDirtyRev or "dirty"; - in { - # vim.lazy.plugins relies on pname, so we only set that here - # version isn't needed for anything, but inherit it anyway for correctness - inherit pname version; - outPath = path { - name = "${pname}-0-unstable-${version}"; - path = input.outPath; - }; - passthru.vimPlugin = false; - }; - - # build a vim plugin with the given name and arguments - # if the plugin is nvim-treesitter, warn the user to use buildTreesitterPlug - # instead - buildPlug = attrs: let - input = inputs."plugin-${attrs.pname}"; - in - pkgs.vimUtils.buildVimPlugin ( - { - version = input.shortRev or input.shortDirtyRev or "dirty"; - src = input.outPath; - } - // attrs - ); - - buildTreesitterPlug = grammars: vimPlugins.nvim-treesitter.withPlugins (_: grammars); - - pluginBuilders = { - nvim-treesitter = buildTreesitterPlug vimOptions.treesitter.grammars; - flutter-tools-patched = buildPlug { - pname = "flutter-tools"; - patches = [./patches/flutter-tools.patch]; - }; - }; - - buildConfigPlugins = plugins: - map ( - plug: - if (isString plug) - then pluginBuilders.${plug} or (noBuildPlug plug) - else plug - ) (filter (f: f != null) plugins); - - # built (or "normalized") plugins that are modified - builtStartPlugins = buildConfigPlugins vimOptions.startPlugins; - builtOptPlugins = map (package: package // {optional = true;}) (buildConfigPlugins vimOptions.optPlugins); - - # additional Lua and Python3 packages, mapped to their respective functions - # to conform to the format mnw expects. end user should - # only ever need to pass a list of packages, which are modified - # here - extraLuaPackages = ps: map (x: ps.${x}) vimOptions.luaPackages; - extraPython3Packages = ps: map (x: ps.${x}) vimOptions.python3Packages; - - # Wrap the user's desired (unwrapped) Neovim package with arguments that'll be used to - # generate a wrapped Neovim package. - neovim-wrapped = inputs.mnw.lib.wrap pkgs { - neovim = vimOptions.package; - plugins = builtStartPlugins ++ builtOptPlugins; - appName = "nvf"; - extraBinPath = vimOptions.extraPackages; - initLua = vimOptions.builtLuaConfigRC; - luaFiles = vimOptions.extraLuaFiles; - - inherit (vimOptions) viAlias vimAlias withRuby withNodeJs withPython3; - inherit extraLuaPackages extraPython3Packages; - }; - - dummyInit = pkgs.writeText "nvf-init.lua" vimOptions.builtLuaConfigRC; - # Additional helper scripts for printing and displaying nvf configuration - # in your commandline. - printConfig = pkgs.writers.writeDashBin "nvf-print-config" "cat ${dummyInit}"; - printConfigPath = pkgs.writers.writeDashBin "nvf-print-config-path" "echo -n ${dummyInit}"; - - # Expose wrapped neovim-package for userspace - # or module consumption. - neovim = pkgs.symlinkJoin { - name = "nvf-with-helpers"; - paths = [neovim-wrapped printConfig printConfigPath]; - postBuild = "echo Helpers added"; - - # Allow evaluating vimOptions, i.e., config.vim from the packages' passthru - # attribute. For example, packages.x86_64-linux.neovim.passthru.neovimConfig - # will return the configuration in full. - passthru.neovimConfig = vimOptions; - - meta = - neovim-wrapped.meta - // { - description = "Wrapped Neovim package with helper scripts to print the config (path)"; - }; - }; + cfg = config.vim; in { - config.vim.build = { - finalPackage = neovim; + config = { + vim.startPlugins = map (x: x.package) (attrValues cfg.extraPlugins); }; } diff --git a/modules/wrapper/build/default.nix b/modules/wrapper/build/default.nix index 0ebefe45..fe9e1b8e 100644 --- a/modules/wrapper/build/default.nix +++ b/modules/wrapper/build/default.nix @@ -1,6 +1,6 @@ { imports = [ - ./options.nix ./config.nix + ./options.nix ]; } diff --git a/modules/wrapper/build/options.nix b/modules/wrapper/build/options.nix index fa1db61e..6a8d085f 100644 --- a/modules/wrapper/build/options.nix +++ b/modules/wrapper/build/options.nix @@ -1,12 +1,144 @@ -{lib, ...}: let - inherit (lib.types) package; - inherit (lib.options) mkOption; +{ + pkgs, + lib, + ... +}: let + inherit (lib.options) mkOption mkEnableOption literalMD; + inherit (lib.types) package bool str listOf attrsOf; + inherit (lib.nvim.types) pluginsOpt extraPluginType; in { - options.vim.build = { - finalPackage = mkOption { + options.vim = { + package = mkOption { type = package; - readOnly = true; - description = "final output package"; + default = pkgs.neovim-unwrapped; + description = '' + The neovim package to use for the wrapper. This + corresponds to the package that will be wrapped + with your plugins and settings. + + ::: {.warning} + You will need to use an unwrapped package for this + option to work as intended. Using an already wrapped + package here may yield undesirable results. + ::: + ''; + }; + + viAlias = mkOption { + type = bool; + default = true; + description = "Enable the `vi` alias for `nvim`"; + }; + + vimAlias = mkOption { + type = bool; + default = true; + description = "Enable the `vim` alias for `nvim`"; + }; + + startPlugins = pluginsOpt { + default = ["plenary-nvim"]; + example = '' + [pkgs.vimPlugins.telescope-nvim] + ''; + + description = '' + List of plugins to load on startup. This is used + internally to add plugins to Neovim's runtime. + + To add additional plugins to your configuration, consider + using the [{option}`vim.extraPlugins`](#opt-vim.extraPlugins) + option. + ''; + }; + + optPlugins = pluginsOpt { + default = []; + example = '' + [pkgs.vimPlugins.vim-ghost] + ''; + description = '' + List of plugins to optionally load on startup. + + This option has the same type definition as {option}`vim.startPlugins` + and plugins in this list are appended to {option}`vim.startPlugins` by + the wrapper during the build process. + + To avoid overriding packages and dependencies provided by startPlugins, you + are recommended to use this option or {option}`vim.extraPlugins` option. + ''; + }; + + extraPlugins = mkOption { + type = attrsOf extraPluginType; + default = {}; + description = '' + A list of plugins and their configurations that will be + set up after builtin plugins. + + This option takes a special type that allows you to order + your custom plugins using nvf's modified DAG library. + ''; + + example = literalMD '' + ```nix + with pkgs.vimPlugins; { + aerial = { + package = aerial-nvim; + setup = "require('aerial').setup {}"; + }; + + harpoon = { + package = harpoon; + setup = "require('harpoon').setup {}"; + after = ["aerial"]; # place harpoon configuration after aerial + }; + } + ``` + ''; + }; + + extraPackages = mkOption { + type = listOf package; + default = []; + example = ''[pkgs.fzf pkgs.ripgrep]''; + description = '' + List of additional packages to make available to the Neovim + wrapper. + ''; + }; + + # this defaults to `true` in the wrapper + # and since we pass this value to the wrapper + # with an inherit, it should be `true` here as well + withRuby = + mkEnableOption '' + Ruby support in the Neovim wrapper. + '' + // { + default = true; + }; + + withNodeJs = mkEnableOption '' + NodeJs support in the Neovim wrapper + ''; + + luaPackages = mkOption { + type = listOf str; + default = []; + example = ''["magick" "serpent"]''; + description = "List of lua packages to install"; + }; + + withPython3 = mkEnableOption '' + Python3 support in the Neovim wrapper + ''; + + python3Packages = mkOption { + type = listOf str; + default = []; + example = ''["pynvim"]''; + description = "List of python packages to install"; }; }; } diff --git a/modules/wrapper/environment/config.nix b/modules/wrapper/environment/config.nix deleted file mode 100644 index 97788bd4..00000000 --- a/modules/wrapper/environment/config.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ - config, - lib, - ... -}: let - inherit (lib.attrsets) attrValues; - - cfg = config.vim; -in { - config = { - vim.startPlugins = map (x: x.package) (attrValues cfg.extraPlugins); - }; -} diff --git a/modules/wrapper/environment/default.nix b/modules/wrapper/environment/default.nix deleted file mode 100644 index fe9e1b8e..00000000 --- a/modules/wrapper/environment/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{ - imports = [ - ./config.nix - ./options.nix - ]; -} diff --git a/modules/wrapper/environment/options.nix b/modules/wrapper/environment/options.nix deleted file mode 100644 index 6a8d085f..00000000 --- a/modules/wrapper/environment/options.nix +++ /dev/null @@ -1,144 +0,0 @@ -{ - pkgs, - lib, - ... -}: let - inherit (lib.options) mkOption mkEnableOption literalMD; - inherit (lib.types) package bool str listOf attrsOf; - inherit (lib.nvim.types) pluginsOpt extraPluginType; -in { - options.vim = { - package = mkOption { - type = package; - default = pkgs.neovim-unwrapped; - description = '' - The neovim package to use for the wrapper. This - corresponds to the package that will be wrapped - with your plugins and settings. - - ::: {.warning} - You will need to use an unwrapped package for this - option to work as intended. Using an already wrapped - package here may yield undesirable results. - ::: - ''; - }; - - viAlias = mkOption { - type = bool; - default = true; - description = "Enable the `vi` alias for `nvim`"; - }; - - vimAlias = mkOption { - type = bool; - default = true; - description = "Enable the `vim` alias for `nvim`"; - }; - - startPlugins = pluginsOpt { - default = ["plenary-nvim"]; - example = '' - [pkgs.vimPlugins.telescope-nvim] - ''; - - description = '' - List of plugins to load on startup. This is used - internally to add plugins to Neovim's runtime. - - To add additional plugins to your configuration, consider - using the [{option}`vim.extraPlugins`](#opt-vim.extraPlugins) - option. - ''; - }; - - optPlugins = pluginsOpt { - default = []; - example = '' - [pkgs.vimPlugins.vim-ghost] - ''; - description = '' - List of plugins to optionally load on startup. - - This option has the same type definition as {option}`vim.startPlugins` - and plugins in this list are appended to {option}`vim.startPlugins` by - the wrapper during the build process. - - To avoid overriding packages and dependencies provided by startPlugins, you - are recommended to use this option or {option}`vim.extraPlugins` option. - ''; - }; - - extraPlugins = mkOption { - type = attrsOf extraPluginType; - default = {}; - description = '' - A list of plugins and their configurations that will be - set up after builtin plugins. - - This option takes a special type that allows you to order - your custom plugins using nvf's modified DAG library. - ''; - - example = literalMD '' - ```nix - with pkgs.vimPlugins; { - aerial = { - package = aerial-nvim; - setup = "require('aerial').setup {}"; - }; - - harpoon = { - package = harpoon; - setup = "require('harpoon').setup {}"; - after = ["aerial"]; # place harpoon configuration after aerial - }; - } - ``` - ''; - }; - - extraPackages = mkOption { - type = listOf package; - default = []; - example = ''[pkgs.fzf pkgs.ripgrep]''; - description = '' - List of additional packages to make available to the Neovim - wrapper. - ''; - }; - - # this defaults to `true` in the wrapper - # and since we pass this value to the wrapper - # with an inherit, it should be `true` here as well - withRuby = - mkEnableOption '' - Ruby support in the Neovim wrapper. - '' - // { - default = true; - }; - - withNodeJs = mkEnableOption '' - NodeJs support in the Neovim wrapper - ''; - - luaPackages = mkOption { - type = listOf str; - default = []; - example = ''["magick" "serpent"]''; - description = "List of lua packages to install"; - }; - - withPython3 = mkEnableOption '' - Python3 support in the Neovim wrapper - ''; - - python3Packages = mkOption { - type = listOf str; - default = []; - example = ''["pynvim"]''; - description = "List of python packages to install"; - }; - }; -} diff --git a/modules/wrapper/build/patches/flutter-tools.patch b/patches/flutter-tools.patch similarity index 100% rename from modules/wrapper/build/patches/flutter-tools.patch rename to patches/flutter-tools.patch