From 91eb12343cadd42a041971dccdb1b79c151acfc6 Mon Sep 17 00:00:00 2001 From: Matei Adriel Date: Sat, 2 Dec 2023 06:04:57 +0100 Subject: [PATCH] Sketch out lazy.nvim nix interface --- home/features/neovim/config/lua/my/lazy.lua | 79 +++-- .../config/lua/my/plugins/nvim-tree.lua | 20 -- home/features/neovim/default.nix | 31 +- modules/common/default.nix | 1 + modules/common/neovim.nix | 334 ++++++++++++++++++ 5 files changed, 404 insertions(+), 61 deletions(-) delete mode 100644 home/features/neovim/config/lua/my/plugins/nvim-tree.lua create mode 100644 modules/common/neovim.nix diff --git a/home/features/neovim/config/lua/my/lazy.lua b/home/features/neovim/config/lua/my/lazy.lua index cf801b9..6e317ec 100644 --- a/home/features/neovim/config/lua/my/lazy.lua +++ b/home/features/neovim/config/lua/my/lazy.lua @@ -1,46 +1,53 @@ local M = {} +local function importFrom(p) + return { import = p } +end + function M.setup() - require("lazy").setup("my.plugins", { - defaults = { lazy = true }, - install = { - -- install missing plugins on startup. This doesn't increase startup time. - missing = true, - -- try to load one of these colorschemes when starting an installation during startup - colorscheme = { "rose-pine", "catpuccin" }, - }, - change_detection = { - enabled = false, - notify = false, - }, - dev = { - -- Fallback to git when local plugin doesn't exist - fallback = true, + require("lazy").setup( + { importFrom("my.plugins"), importFrom("nix.plugins") }, + { + defaults = { lazy = true }, + install = { + -- install missing plugins on startup. This doesn't increase startup time. + missing = true, + -- try to load one of these colorschemes when starting an installation during startup + colorscheme = { "rose-pine", "catpuccin" }, + }, + change_detection = { + enabled = false, + notify = false, + }, + dev = { + -- Fallback to git when local plugin doesn't exist + fallback = true, - -- Directory where I store my local plugin projects - path = "~/Projects", + -- Directory where I store my local plugin projects + path = "~/Projects", - patterns = { "Mateiadrielrafael" }, - }, - performance = { - rtp = { - paths = { - -- Extra runtime path specified by nix - os.getenv("NVIM_EXTRA_RUNTIME") or "", - }, - disabled_plugins = { - "gzip", - "matchit", - "matchparen", - "netrwPlugin", - "tarPlugin", - "tohtml", - "tutor", - "zipPlugin", + patterns = { "Mateiadrielrafael" }, + }, + performance = { + rtp = { + paths = { + -- Extra runtime path specified by nix + os.getenv("NVIM_EXTRA_RUNTIME") or "", + }, + disabled_plugins = { + "gzip", + "matchit", + "matchparen", + "netrwPlugin", + "tarPlugin", + "tohtml", + "tutor", + "zipPlugin", + }, }, }, - }, - }) + } + ) end vim.keymap.set("n", "L", "Lazy", { desc = "[L]azy ui" }) diff --git a/home/features/neovim/config/lua/my/plugins/nvim-tree.lua b/home/features/neovim/config/lua/my/plugins/nvim-tree.lua deleted file mode 100644 index 5849055..0000000 --- a/home/features/neovim/config/lua/my/plugins/nvim-tree.lua +++ /dev/null @@ -1,20 +0,0 @@ -local env = require("my.helpers.env") - -local M = { - "kyazdani42/nvim-tree.lua", - cmd = "NvimTreeToggle", - config = true, - cond = env.vscode.not_active() and env.firenvim.not_active(), -} - -function M.init() - -- Toggle nerdtree with Control-n - vim.keymap.set( - "n", - "", - ":NvimTreeToggle", - { desc = "Toggle [n]vim-tree" } - ) -end - -return M diff --git a/home/features/neovim/default.nix b/home/features/neovim/default.nix index 31bd2e2..a314e8f 100644 --- a/home/features/neovim/default.nix +++ b/home/features/neovim/default.nix @@ -76,10 +76,7 @@ let ]; # }}} # {{{ extraRuntime - extraRuntime = env: [ - # Snippets - (config.satellite.dev.path "home/features/neovim/snippets") - + extraRuntimePaths = env: [ # Base16 theme (pkgs.writeTextDir "lua/nix/theme.lua" @@ -92,7 +89,21 @@ let "lua/nix/env.lua" "return '${env}'" ) + + # Experimental nix module generation + config.satellite.neovim.generated.all ]; + + extraRuntime = env: + let + generated = pkgs.symlinkJoin { + name = "nixified-neovim-lua-modules"; + paths = extraRuntimePaths env; + }; + + snippets = config.satellite.dev.path "home/features/neovim/snippets"; + in + lib.concatStringsSep "," [ generated snippets ]; # }}} # {{{ Client wrapper # Wraps a neovim client, providing the dependencies @@ -109,7 +120,7 @@ let postBuild = '' wrapProgram $out/bin/${binName} \ --prefix PATH : ${lib.makeBinPath extraPackages} \ - --set NVIM_EXTRA_RUNTIME ${lib.strings.concatStringsSep "," (extraRuntime name)} \ + --set NVIM_EXTRA_RUNTIME ${extraRuntime name} \ ${extraArgs} ''; }; @@ -195,4 +206,14 @@ in }; }; # }}} + # {{{ Custom module testing + satellite.neovim.styluaConfig = ../../../stylua.toml; + satellite.neovim.lazy.nvim-tree = { + setup = true; + package = "kyazdani42/nvim-tree.lua"; + keys.mapping = ""; + keys.desc = "Toggle [n]vim-tree"; + keys.action = "NvimTreeToggle"; + }; + # }}} } diff --git a/modules/common/default.nix b/modules/common/default.nix index 1f799d1..99affe4 100644 --- a/modules/common/default.nix +++ b/modules/common/default.nix @@ -5,4 +5,5 @@ lua-colorscheme = import ./lua-colorscheme.nix; theming = import ./theming.nix; toggles = import ./toggles.nix; + neovim = import ./neovim.nix; } diff --git a/modules/common/neovim.nix b/modules/common/neovim.nix new file mode 100644 index 0000000..8e60d0e --- /dev/null +++ b/modules/common/neovim.nix @@ -0,0 +1,334 @@ +# Additional theming primitives not provided by stylix +{ pkgs, lib, config, ... }: +let + inherit (lib) types; + + cfg = config.satellite.neovim; + + # {{{ Custom types + myTypes = { + luaCode = types.nullOr (types.oneOf [ + types.str + types.path + ]); + + fileTypes = types.nullOr (types.oneOf [ + types.str + (types.listOf types.str) + ]); + + # {{{ Key type + lazyKey = types.oneOf [ + types.str + (types.submodule + (_: { + options.mapping = lib.mkOption { + type = types.str; + }; + + options.action = lib.mkOption { + type = types.str; + }; + + options.ft = lib.mkOption { + default = null; + type = myTypes.fileTypes; + description = "Filetypes on which this keybind should take effect"; + }; + + options.mode = lib.mkOption { + default = null; + # Only added the types I'm using in my config atm + type = types.nullOr (types.enum [ "n" "v" ]); + }; + + options.desc = lib.mkOption { + default = null; + type = types.nullOr types.str; + }; + })) + ]; + # }}} + # {{{ Lazy module type + lazyModule = lib.fix (lazyModule: types.submodule (_: { + options = { + package = lib.mkOption { + type = types.oneOf [ + types.package + types.str + ]; + description = "Package to configure the module around"; + example = "nvim-telescope/telescope.nvim"; + }; + + 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 = [ ]; + type = types.listOf lazyModule; + }; + + dependencies.nix = lib.mkOption { + default = [ ]; + type = types.listOf types.package; + }; + + cond = lib.mkOption { + default = null; + type = myTypes.luaCode; + description = "Condition based on which to enable/disbale loading the package"; + }; + + setup = lib.mkOption { + default = null; + type = types.oneOf [ myTypes.luaCode types.bool ]; + description = '' + Lua function (or module) to use for configuring the package. + Used instead of the canonically named `config` because said property has a special name in nix''; + }; + + event = lib.mkOption { + default = null; + type = types.nullOr (types.oneOf [ + types.str + (types.listOf types.str) + ]); + description = "Event on which the module should be lazy loaded"; + }; + + ft = lib.mkOption { + default = null; + type = myTypes.fileTypes; + description = "Filetypes on which the module should be lazy loaded"; + }; + + 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"; + }; + + keys = lib.mkOption { + default = null; + type = + types.nullOr (types.oneOf [ + myTypes.lazyKey + (types.listOf myTypes.lazyKey) + ]); + }; + + opts = { }; + }; + })); + # }}} + }; + # }}} + # {{{ Lua encoders + mkRawLuaObject = chunks: + '' + { + ${lib.concatStringsSep "," (lib.filter (s: s != "") chunks)} + } + ''; + + # An encoder is a function from some nix value to a string containing lua code. + # This object provides combinators for writing such encoders. + luaEncoders = { + # {{{ General helpers + identity = given: given; + bind = encoder: given: encoder given given; + conditional = predicate: caseTrue: caseFalse: + luaEncoders.bind (given: if predicate given then caseTrue else caseFalse); + map = f: encoder: given: encoder (f given); + trace = message: luaEncoders.map (f: lib.traceSeq message f); + # }}} + # {{{ Base types + # TODO: figure out escaping and whatnot + string = string: ''"${string}"''; + bool = bool: if bool then "true" else "false"; + nil = _: "nil"; + stringOr = luaEncoders.conditional lib.isString luaEncoders.string; + boolOr = luaEncoders.conditional lib.isBool luaEncoders.bool; + nullOr = luaEncoders.conditional (e: e == null) luaEncoders.nil; + # }}} + # {{{ Advanced types + luaCode = tag: + luaEncoders.conditional lib.isPath + (path: "require(${path}).${tag}") + luaEncoders.identity; + + listOf = encoder: list: + mkRawLuaObject (lib.lists.map encoder list); + tryNonemptyList = encoder: luaEncoders.conditional + (l: l == [ ]) + luaEncoders.nil + (luaEncoders.listOf encoder); + oneOrMany = encoder: + luaEncoders.conditional + lib.isList + (luaEncoders.listOf encoder) + encoder; + oneOrManyAsList = encoder: luaEncoders.map + (given: if lib.isList given then given else [ given ]) + (luaEncoders.listOf encoder); + + attrset = noNils: listOrder: listSpec: spec: attrset: + let + shouldKeep = given: + if noNils then + given != "nil" + else + true; + + listChunks = lib.lists.map + (attr: + let result = listSpec.${attr} (attrset.${attr} or null); + in + lib.optionalString (shouldKeep result) result + ) + listOrder; + objectChunks = lib.mapAttrsToList + (attr: encoder: + let result = encoder (attrset.${attr} or null); + in + lib.optionalString (shouldKeep result) + "${attr} = ${result}" + ) + spec; + in + mkRawLuaObject (listChunks ++ objectChunks); + # }}} + }; + + e = luaEncoders; + # }}} + + # Format and write a lua file to disk + writeLuaFile = path: name: text: + let + directory = "lua/${path}"; + destination = "${directory}/${name}.lua"; + unformatted = pkgs.writeText "raw-lua-${name}" text; + in + pkgs.runCommand "formatted-lua-${name}" { } '' + mkdir -p $out/${directory} + cp --no-preserve=mode ${unformatted} $out/${destination} + ${lib.getExe pkgs.stylua} --config-path ${cfg.styluaConfig} $out/${destination} + ''; +in +{ + options.satellite.neovim = { + lazy = lib.mkOption { + default = { }; + description = "Record of persistent locations (eg: /persist)"; + type = types.attrsOf myTypes.lazyModule; + }; + + generated.lazy = lib.mkOption { + type = types.attrsOf (types.submodule (_: { + 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"; + }; + }; + })); + description = "Attrset containing every module generated from the lazy configuration"; + }; + + generated.all = lib.mkOption { + default = { }; + type = types.package; + description = "Derivation building all the given nix modules"; + }; + + styluaConfig = lib.mkOption { + type = types.path; + description = "Config to use for formatting lua modules"; + }; + }; + + config.satellite.neovim.generated.lazy = + let + lazyKeyEncoder = + e.stringOr (e.attrset true [ "mapping" "action" ] + { + mapping = e.string; + action = e.nullOr e.string; + } + { + mode = e.nullOr e.string; + desc = e.nullOr e.string; + ft = e.nullOr (e.oneOrMany e.string); + }); + + renameKey = from: to: lib.mapAttrs' (name: value: + if name == from then { inherit value; name = to; } + else { inherit name value; }); + + + lazyObjectEncoder = e.map (renameKey "setup" "config") + (e.attrset true [ "package" ] + { + package = e.string; + } + { + tag = e.nullOr e.string; + version = e.nullOr e.string; + dependencies = e.map (d: d.lua) (e.tryNonemptyList lazyObjectEncoder); + lazy = e.nullOr e.bool; + # TODO: add sugar for enabling/disabling under certain envs + cond = e.nullOr (e.luaCode "cond"); + config = e.nullOr (e.boolOr (e.luaCode "config")); + init = e.nullOr (e.luaCode "init"); + event = e.nullOr (e.oneOrMany e.string); + ft = e.nullOr (e.oneOrMany e.string); + keys = e.nullOr (e.oneOrManyAsList lazyKeyEncoder); + # TODO: passthrough + }); + + makeLazyScript = opts: '' + -- This file was generated by nix + return ${lazyObjectEncoder opts} + ''; + in + lib.attrsets.mapAttrs + (name: opts: rec { + raw = makeLazyScript opts; + module = writeLuaFile "nix/plugins" name raw; + }) + cfg.lazy; + + config.satellite.neovim.generated.all = + pkgs.symlinkJoin { + name = "lazy-nvim-modules"; + paths = lib.attrsets.mapAttrsToList (_: m: m.module) cfg.generated.lazy; + }; +}