1
Fork 0
satellite/modules/common/neovim.nix

623 lines
19 KiB
Nix
Raw Normal View History

2023-12-07 22:35:57 +01:00
# This module provides personalised helpers for managing plugins
# using lazy.nvim and a set of custom runtime primitives.
2023-12-02 06:04:57 +01:00
{ pkgs, lib, config, ... }:
let
inherit (lib) types;
e = config.satellite.lib.lua.encoders;
2023-12-02 06:04:57 +01:00
cfg = config.satellite.neovim;
# {{{ Custom types
myTypes = {
2023-12-07 22:35:57 +01:00
oneOrMany = t: types.either t (types.listOf t);
zeroOrMore = t: types.nullOr (myTypes.oneOrMany t);
2023-12-04 09:06:02 +01:00
2023-12-07 22:35:57 +01:00
# {{{ Lua code
2023-12-02 06:04:57 +01:00
luaCode = types.nullOr (types.oneOf [
types.str
types.path
2023-12-02 12:16:33 +01:00
myTypes.luaLiteral
]);
2023-12-07 22:35:57 +01:00
luaLiteral = types.submodule {
2023-12-02 12:16:33 +01:00
options.__luaEncoderTag = lib.mkOption {
type = types.enum [ "lua" ];
};
options.value = lib.mkOption {
type = types.str;
};
2023-12-07 22:35:57 +01:00
};
2023-12-02 12:16:33 +01:00
luaValue = types.nullOr (types.oneOf [
types.str
types.number
types.bool
(types.attrsOf myTypes.luaValue)
(types.listOf myTypes.luaValue)
2023-12-02 06:04:57 +01:00
]);
2023-12-07 22:35:57 +01:00
# }}}
# {{{ Lazy key
2023-12-02 06:04:57 +01:00
lazyKey = types.oneOf [
types.str
(types.submodule
2023-12-07 22:35:57 +01:00
{
2023-12-02 06:04:57 +01:00
options.mapping = lib.mkOption {
type = types.str;
2023-12-04 09:06:02 +01:00
description = "The lhs of the neovim mapping";
2023-12-02 06:04:57 +01:00
};
options.action = lib.mkOption {
2023-12-10 12:55:54 +01:00
default = null;
2023-12-02 12:16:33 +01:00
type = types.nullOr (types.oneOf [
types.str
myTypes.luaLiteral
]);
2023-12-04 09:06:02 +01:00
description = "The rhs of the neovim mapping";
2023-12-02 06:04:57 +01:00
};
options.ft = lib.mkOption {
default = null;
2023-12-04 09:06:02 +01:00
type = myTypes.zeroOrMore types.str;
2023-12-02 06:04:57 +01:00
description = "Filetypes on which this keybind should take effect";
};
options.mode = lib.mkOption {
default = null;
2023-12-02 12:16:33 +01:00
type = types.nullOr types.str;
2023-12-04 09:06:02 +01:00
description = "The vim modes the mapping should take effect in";
2023-12-02 06:04:57 +01:00
};
options.desc = lib.mkOption {
default = null;
type = types.nullOr types.str;
2023-12-04 09:06:02 +01:00
description = "Description for the current keymapping";
2023-12-02 06:04:57 +01:00
};
2023-12-07 22:35:57 +01:00
})
2023-12-02 06:04:57 +01:00
];
# }}}
2023-12-07 22:35:57 +01:00
# {{{ Tempest key
tempestKey = types.submodule {
options = {
mapping = lib.mkOption {
example = "<leader>a";
type = types.str;
description = "The lhs of the neovim mapping";
};
action = lib.mkOption {
example = "<C-^>";
type = types.either types.str myTypes.luaLiteral;
description = "The rhs of the neovim mapping";
};
bufnr = lib.mkOption {
default = null;
example = true;
type = types.nullOr
(types.oneOf [
types.bool
types.integer
myTypes.luaLiteral
]);
description = ''
The index of the buffer to apply local keymaps to. Can be set to
`true` to refer to the current buffer
'';
};
mode = lib.mkOption {
default = null;
example = "nov";
type = types.nullOr types.str;
description = "The vim modes the mapping should take effect in";
};
silent = lib.mkOption {
default = null;
example = true;
type = types.nullOr types.bool;
description = "Whether the logs emitted by the keymap should be supressed";
};
expr = lib.mkOption {
default = null;
example = true;
type = types.nullOr types.bool;
description = "If set to `true`, the mapping is treated as an action factory";
};
desc = lib.mkOption {
default = null;
type = types.nullOr types.str;
description = "Description for the current keymapping";
};
};
};
# }}}
# {{{ Tempest autocmd
tempestAutocmd = types.submodule {
options = {
event = lib.mkOption {
example = "InsertEnter";
type = myTypes.oneOrMany types.str;
description = "Events to bind autocmd to";
};
pattern = lib.mkOption {
example = "Cargo.toml";
type = myTypes.oneOrMany types.str;
description = "File name patterns to run autocmd on";
};
group = lib.mkOption {
example = "CargoCmpSource";
type = types.str;
description = "Name of the group to create and assign autocmd to";
};
action = lib.mkOption {
2023-12-07 22:35:57 +01:00
example.vim.opt.cmdheight = 1;
type = types.oneOf [
myTypes.tempestConfiguration
myTypes.luaCode
];
description = ''
Code to run when the respctive event occurs. Will pass the event
object as context, which might be used for things like assigning
a buffer number to local keymaps automatically.
'';
};
};
};
# }}}
# {{{ Tempest configuration
tempestConfiguration = types.submodule {
options = {
vim = lib.mkOption {
default = null;
type = myTypes.luaValue;
example.opt.cmdheight = 0;
description = "Values to assign to the `vim` lua global object";
};
keys = lib.mkOption {
default = null;
type = myTypes.zeroOrMore myTypes.tempestKey;
description = ''
Arbitrary key mappings to create. The keymappings might
automatically be buffer specific depending on the context. For
instance, keymappings created inside autocmds will be local unless
otherwise specified.
'';
};
autocmds = lib.mkOption {
default = null;
type = myTypes.zeroOrMore myTypes.tempestAutocmd;
description = "Arbitrary autocmds to create";
};
setup = lib.mkOption {
default = null;
type = types.nullOr (types.attrsOf myTypes.luaValue);
example.lualine.opts.theme = "auto";
description = ''
Key-pair mappings for options to pass to .setup functions imported
from different modules
'';
};
callback = lib.mkOption {
default = null;
type = types.nullOr myTypes.luaCode;
description = "Arbitrary code to run after everything else has been configured";
};
};
};
# }}}
# {{{ Lazy module
lazyModule = lib.fix (lazyModule: types.submodule ({ name ? null, ... }: {
2023-12-02 06:04:57 +01:00
options = {
package = lib.mkOption {
default = null;
type = types.nullOr types.str;
2023-12-02 06:04:57 +01:00
description = "Package to configure the module around";
example = "nvim-telescope/telescope.nvim";
};
dir = lib.mkOption {
default = null;
type = types.nullOr types.path;
description = "Path to install pacakge from";
};
2023-12-02 12:16:33 +01:00
name = lib.mkOption {
2023-12-07 22:35:57 +01:00
default = name;
2023-12-02 12:16:33 +01:00
type = types.nullOr types.str;
description = "Custom name to use for the module";
example = "lualine";
};
main = lib.mkOption {
default = null;
type = types.nullOr types.str;
description = "The name of the lua entrypoint for the plugin (usually auto-detected)";
example = "lualine";
};
2023-12-02 06:04:57 +01:00
version = lib.mkOption {
default = null;
type = types.nullOr types.str;
description = "Pin the package to a certain version (useful for non-nix managed packages)";
};
tag = lib.mkOption {
default = null;
type = types.nullOr types.str;
description = "Pin the package to a certain git tag (useful for non-nix managed packages)";
};
lazy = lib.mkOption {
default = null;
type = types.nullOr types.bool;
description = "Specifies whether this module should be lazy-loaded";
};
dependencies.lua = lib.mkOption {
default = [ ];
2023-12-07 22:35:57 +01:00
type = types.listOf (types.either types.str lazyModule);
2023-12-04 09:06:02 +01:00
description = "Lazy.nvim module dependencies";
2023-12-02 06:04:57 +01:00
};
dependencies.nix = lib.mkOption {
default = [ ];
type = types.listOf types.package;
2023-12-04 09:06:02 +01:00
description = "Nix packages to give nvim access to";
2023-12-02 06:04:57 +01:00
};
cond = lib.mkOption {
default = null;
type = myTypes.luaCode;
description = "Condition based on which to enable/disbale loading the package";
};
2023-12-07 22:35:57 +01:00
env.blacklist = lib.mkOption {
default = [ ];
type = types.listOf (types.enum [ "firenvim" "vscode" "neovide" ]);
description = "Environments to blacklist plugin on";
};
2023-12-02 06:04:57 +01:00
setup = lib.mkOption {
default = null;
2023-12-07 22:35:57 +01:00
type = types.nullOr (types.oneOf [
myTypes.tempestConfiguration
myTypes.luaCode
types.bool
]);
2023-12-02 06:04:57 +01:00
description = ''
Lua function (or module) to use for configuring the package.
2023-12-07 22:35:57 +01:00
Used instead of the canonically named `config` because said name has a special meaning in nix
'';
2023-12-02 06:04:57 +01:00
};
event = lib.mkOption {
default = null;
2023-12-04 09:06:02 +01:00
type = myTypes.zeroOrMore types.str;
2023-12-02 06:04:57 +01:00
description = "Event on which the module should be lazy loaded";
};
ft = lib.mkOption {
default = null;
2023-12-04 09:06:02 +01:00
type = myTypes.zeroOrMore types.str;
2023-12-02 06:04:57 +01:00
description = "Filetypes on which the module should be lazy loaded";
};
2023-12-04 09:06:02 +01:00
cmd = lib.mkOption {
default = null;
type = myTypes.zeroOrMore types.str;
description = "Comands on which to load this plugin";
};
2023-12-02 06:04:57 +01:00
init = lib.mkOption {
default = null;
type = myTypes.luaCode;
description = "Lua function (or module) to run right away (even if the package is not yet loaded)";
};
passthrough = lib.mkOption {
default = null;
type = myTypes.luaCode;
description = "Attach additional things to the lazy module";
};
2023-12-02 12:16:33 +01:00
opts = lib.mkOption {
default = null;
type = myTypes.luaValue;
description = "Custom data to pass to the plugin .setup function";
};
2023-12-02 06:04:57 +01:00
keys = lib.mkOption {
default = null;
2023-12-04 09:06:02 +01:00
type = myTypes.zeroOrMore myTypes.lazyKey;
description = "Keybinds to lazy-load the module on";
2023-12-02 06:04:57 +01:00
};
};
}));
# }}}
};
# }}}
in
{
2023-12-07 22:35:57 +01:00
# {{{ Option declaration
2023-12-02 06:04:57 +01:00
options.satellite.neovim = {
lazy = lib.mkOption {
default = { };
2023-12-07 22:35:57 +01:00
description = "Record of plugins to install using lazy.nvim";
2023-12-02 06:04:57 +01:00
type = types.attrsOf myTypes.lazyModule;
};
2023-12-07 22:35:57 +01:00
# {{{ Generated
2023-12-02 12:16:33 +01:00
generated = {
lazy = lib.mkOption {
2023-12-07 22:35:57 +01:00
type = types.attrsOf (types.submodule {
2023-12-02 12:16:33 +01:00
options = {
raw = lib.mkOption {
type = types.lines;
description = "The lua script generated using the other options";
};
module = lib.mkOption {
type = types.package;
description = "The lua script generated using the other options";
};
2023-12-02 06:04:57 +01:00
};
2023-12-07 22:35:57 +01:00
});
2023-12-02 12:16:33 +01:00
description = "Attrset containing every module generated from the lazy configuration";
};
2023-12-02 06:04:57 +01:00
2023-12-02 12:16:33 +01:00
all = lib.mkOption {
default = { };
type = types.package;
description = "Derivation building all the given nix modules";
};
2023-12-04 06:25:00 +01:00
lazySingleFile = lib.mkOption {
type = types.package;
description = "Derivation building all the given nix modules in a single file";
};
2023-12-04 06:25:00 +01:00
dependencies = lib.mkOption {
default = [ ];
type = types.listOf types.package;
description = "List of packages to give neovim access to";
};
2023-12-02 06:04:57 +01:00
};
2023-12-07 22:35:57 +01:00
# }}}
# {{{ Lua generation lib
2023-12-02 12:16:33 +01:00
lib = {
2023-12-07 22:35:57 +01:00
# {{{ Basic lua generators
2023-12-02 12:16:33 +01:00
lua = lib.mkOption {
default = value: { inherit value; __luaEncoderTag = "lua"; };
type = types.functionTo myTypes.luaLiteral;
description = "include some raw lua code inside module configuration";
};
import = lib.mkOption {
default = path: tag: cfg.lib.lua "dofile(${e.string path}).${tag}";
type = types.functionTo (types.functionTo myTypes.luaLiteral);
description = "import some identifier from some module";
};
2023-12-07 22:35:57 +01:00
# }}}
# {{{ Encoders
encode = lib.mkOption {
default = e.anything;
2023-12-07 22:35:57 +01:00
type = types.functionTo types.str;
description = "Encode a nix value to a lua string";
};
2023-12-02 12:16:33 +01:00
2023-12-07 22:35:57 +01:00
encodeTempestConfiguration = lib.mkOption {
default = given:
e.attrset true [ ]
{
vim = e.anything;
callback = e.nullOr e.luaString;
setup = e.nullOr (e.attrsetOf e.anything);
keys = e.zeroOrMany (e.attrset true [ ] {
mapping = e.string;
action = e.luaCodeOr e.string;
desc = e.nullOr e.string;
expr = e.nullOr e.bool;
mode = e.nullOr e.string;
silent = e.nullOr e.bool;
buffer = e.nullOr (e.luaCodeOr (e.boolOr e.number));
});
autocmds = e.zeroOrMany (e.attrset true [ ] {
event = e.oneOrMany e.string;
pattern = e.oneOrMany e.string;
group = e.string;
action = e.conditional lib.isAttrs
2023-12-07 22:35:57 +01:00
cfg.lib.encodeTempestConfiguration
e.luaString;
});
}
given;
type = types.functionTo types.str;
description = "Generate a lua object for passing to my own lua runtime for configuration";
};
# }}}
# {{{ Thunks
# This version of `nlib.thunk` is required in ceratain cases because
# of issues with `types.oneOf [types.submodule ..., types.submodule]` not
# working as intended atm.
thunkString = lib.mkOption {
default = given: /* lua */ ''
function() ${e.luaString given} end
2023-12-02 12:16:33 +01:00
'';
2023-12-07 22:35:57 +01:00
type = types.functionTo types.str;
description = "Wrap a lua expression into a lua function as a string";
2023-12-02 12:16:33 +01:00
};
2023-12-04 09:06:02 +01:00
thunk = lib.mkOption {
2023-12-07 22:35:57 +01:00
default = given: cfg.lib.lua (cfg.lib.thunkString given);
2023-12-04 09:06:02 +01:00
type = types.functionTo myTypes.luaLiteral;
description = "Wrap a lua expression into a lua function";
};
2023-12-02 12:16:33 +01:00
2023-12-07 22:35:57 +01:00
contextThunk = lib.mkOption {
default = given: cfg.lib.lua /* lua */ ''
function(context) ${e.luaString given} end
'';
type = types.functionTo myTypes.luaLiteral;
description = "Wrap a lua expression into a lua function taking an argument named `context`";
};
# }}}
# {{{ Language server on attach
languageServerOnAttach = lib.mkOption {
default = given: cfg.lib.lua /* lua */ ''
function(client, bufnr)
require(${e.string cfg.runtime.tempest}).configure(${cfg.lib.encodeTempestConfiguration given},
{ client = client; bufnr = bufnr; })
require(${e.string cfg.runtime.languageServerOnAttach}).on_attach(client, bufnr)
end
'';
type = types.functionTo myTypes.luaCode;
description = "Attach a language server and run some additional code";
};
# }}}
};
# }}}
# {{{ Neovim runtime module paths
runtime = {
env = lib.mkOption {
2023-12-02 12:16:33 +01:00
type = types.str;
example = "my.helpers.env";
2023-12-07 22:35:57 +01:00
description = "Module to import env flags from";
};
tempest = lib.mkOption {
type = types.str;
example = "my.runtime.tempest";
description = "Module to import the tempest runtime from";
};
languageServerOnAttach = lib.mkOption {
type = types.str;
example = "my.runtime.lspconfig";
description = "Module to import langauge server .on_attach function from";
2023-12-02 12:16:33 +01:00
};
2023-12-02 06:04:57 +01:00
};
2023-12-07 22:35:57 +01:00
# }}}
2023-12-02 06:04:57 +01:00
};
2023-12-07 22:35:57 +01:00
# }}}
# {{{ Config generation
# {{{ Lazy module generation
2023-12-02 06:04:57 +01:00
config.satellite.neovim.generated.lazy =
let
2023-12-07 22:35:57 +01:00
# {{{ Lazy key encoder
2023-12-02 06:04:57 +01:00
lazyKeyEncoder =
2023-12-07 22:35:57 +01:00
e.stringOr (e.attrset true [ "mapping" "action" ] {
mapping = e.string;
action = e.nullOr (e.luaCodeOr e.string);
mode = e.nullOr
(e.map
lib.strings.stringToCharacters
(e.listAsOneOrMany e.string));
desc = e.nullOr e.string;
ft = e.zeroOrMany e.string;
});
# }}}
# {{{ Lazy spec encoder
xor = a: b: (a || b) && (!a || !b);
implies = a: b: !a || b;
hasProp = obj: p: (obj.${p} or null) != null;
propXor = a: b: obj: xor (hasProp obj a) (hasProp obj b);
propImplies = a: b: obj: implies (hasProp obj a) (hasProp obj b);
2023-12-02 12:16:33 +01:00
lazyObjectEncoder = e.bind
(opts: e.attrset true [ "package" ]
2023-12-02 06:04:57 +01:00
{
package = e.nullOr e.string;
dir = assert propXor "package" "dir" opts; e.nullOr e.string;
tag = assert propImplies "tag" "package" opts; e.nullOr e.string;
version = assert propImplies "tag" "package" opts; e.nullOr e.string;
2023-12-02 12:16:33 +01:00
name = e.nullOr e.string;
main = e.nullOr e.string;
2023-12-07 22:35:57 +01:00
dependencies = e.map (d: d.lua) (e.tryNonemptyList (e.stringOr lazyObjectEncoder));
2023-12-02 06:04:57 +01:00
lazy = e.nullOr e.bool;
cond = e.conjunction
(e.nullOr (e.luaCode "cond"))
(e.filter (_: opts.env.blacklist != [ ])
(e.const /* lua */ ''
2023-12-07 22:35:57 +01:00
require(${e.string cfg.runtime.env}).blacklist(${e.listOf e.string opts.env.blacklist})
''));
2023-12-07 22:35:57 +01:00
config = _:
let
wrap = given: /* lua */''
function(lazy, opts)
require(${e.string cfg.runtime.tempest}).configure(${given},
{ lazy = lazy; opts = opts; })
end
'';
in
e.conditional lib.isAttrs
(e.postmap wrap cfg.lib.encodeTempestConfiguration)
(e.nullOr (e.boolOr (e.luaCode "config")))
opts.setup;
2023-12-02 06:04:57 +01:00
init = e.nullOr (e.luaCode "init");
2023-12-04 09:06:02 +01:00
event = e.zeroOrMany e.string;
cmd = e.zeroOrMany e.string;
ft = e.zeroOrMany e.string;
2023-12-02 06:04:57 +01:00
keys = e.nullOr (e.oneOrManyAsList lazyKeyEncoder);
2023-12-02 12:16:33 +01:00
passthrough = e.anything;
opts = e.anything;
2023-12-02 06:04:57 +01:00
});
2023-12-07 22:35:57 +01:00
# }}}
2023-12-02 06:04:57 +01:00
in
lib.attrsets.mapAttrs
(name: opts: rec {
raw = lazyObjectEncoder opts;
module = config.satellite.lib.lua.writeFile "lua/nix/plugins" name raw;
2023-12-02 06:04:57 +01:00
})
cfg.lazy;
config.satellite.neovim.generated.lazySingleFile =
let
makeFold = name: m: ''
-- {{{ ${name}
${m.raw},
-- }}}'';
mkReturnList = objects: ''
return {
${objects}
}'';
script = lib.pipe cfg.generated.lazy [
(lib.attrsets.mapAttrsToList makeFold)
(lib.concatStringsSep "\n")
mkReturnList
];
in
config.satellite.lib.lua.writeFile
"lua/nix/plugins" "init"
script;
2023-12-02 06:04:57 +01:00
config.satellite.neovim.generated.all =
pkgs.symlinkJoin {
name = "lazy-nvim-modules";
paths = lib.attrsets.mapAttrsToList (_: m: m.module) cfg.generated.lazy;
};
2023-12-07 22:35:57 +01:00
# }}}
2023-12-04 06:25:00 +01:00
config.satellite.neovim.generated.dependencies =
lib.pipe cfg.lazy
[
(lib.attrsets.mapAttrsToList (_: m: m.dependencies.nix))
lib.lists.flatten
2023-12-07 22:35:57 +01:00
];
# }}}
2023-12-02 06:04:57 +01:00
}