nix-community / nixvim

Configure Neovim with Nix! [maintainers=@GaetanLepage, @traxys, @mattsturgeon, @khaneliman]
https://nix-community.github.io/nixvim
MIT License
1.78k stars 275 forks source link

Combine `files` and `extraFiles` #1794

Open MattSturgeon opened 4 months ago

MattSturgeon commented 4 months ago

Status quo

Currently we have files for building a nixvim config as an extra file and extraFiles for directly writing text as an extra file.

There is also no way to add a path as an extra file (without using readFile to get the text).

Simple option

If we make an option with the type attrsOf (oneOf [ str path fileModuleType ]), then we could combine the two existing options by making one of them into a rename-alias.

Better option

It may be better to have a new option with a slightly different type, more in line with what nixos & home-manager use:

type = attrsOf (submodule (
  { name, config, options, ... }:
  {
    options = {
      target = /* default to `name` */;
      source = /* path to a file */;
      text = /* alternatively, file content */;
      module = /* alternatively, a nixvim module to build */;
    };
    config = mkIf (config.source == null) (mkMerge [
      { source = mkIf (config.text != null) (pkgs.writeText "" config.text); }
      { source = mkIf (config.module != null) config.module.plugin; }
    ]);
  }
))

The config block ensures the option will always have a .source file path, along with .target that is where the file should be symlinked from when used in xdg/etc.

Heavily based on nixos's environment.etc.* option type: https://github.com/NixOS/nixpkgs/blob/7dca15289a1c2990efbe4680f0923ce14139b042/nixos/modules/system/etc/etc.nix#L123-L215

Also similar to home-manager's file-type.

Type coercion

We can transition extraFiles type from str to submodule using types.coercedTo, which takes a coerced type, a coercion function, and a final type.

The coercion function is essentially the same as an option's apply function.

The description is "X or Y convertible to it", but we could override that to just "X" if/when we wanted to.

We could print a deprecation warning in the type's merge function, thanks to \@infinisil for explaining this to me:

Details

```nix # Extend lib.types.coercedTo with a deprecation warning for the coerced type deprecateType = coercedType: coerceFunc: finalType: lib.types.coercedTo coercedType coerceFunc finalType // { # Override description inherit (finalType) description; # Override merge so we can print a warning merge = loc: defs: let coerceVal = val: if coercedType.check val then lib.warn "Passing a ${coercedType.description} for ${showOption loc} is deprecated, use ${finalType.description} instead. Definitions: ${showDefs defs}" (coerceFunc val) else val; in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs); }; ```

User's perspective

extraFiles = {
  "ftplugin/markdown.lua".text = "print('lua here')";

  "after/some_file.lua".source = ./some/lua-file.nix;

  "ftplugin/lua.lua".module = {
    extraConfigLua = "print('module built by nixvim')";
  };

  "ftplugin/nix.lua".module = ({config, options, ...}: {
    extraConfigLua = "print('fancy module built by nixvim')";
  });
}
MattSturgeon commented 4 months ago

I think we can make extraFiles into an authority of a nixvim module's outputs.

Submodules can set put their content into extraFiles and the host module will then propagate that into its extraFiles.

Eventually, at the top-level, init.lua is also added to extraFiles and the wrapper implementation doesn't have to know about init.lua specifically.