diff --git a/lib/booleans.nix b/lib/booleans.nix new file mode 100644 index 0000000..277dd18 --- /dev/null +++ b/lib/booleans.nix @@ -0,0 +1,9 @@ +# From home-manager: https://github.com/nix-community/home-manager/blob/master/modules/lib/booleans.nix +{lib}: { + # Converts a boolean to a yes/no string. This is used in lots of + # configuration formats. + yesNo = value: + if value + then "yes" + else "no"; +} diff --git a/lib/dag.nix b/lib/dag.nix new file mode 100644 index 0000000..0b392f9 --- /dev/null +++ b/lib/dag.nix @@ -0,0 +1,107 @@ +# From home-manager: https://github.com/nix-community/home-manager/blob/master/modules/lib/dag.nix +# A generalization of Nixpkgs's `strings-with-deps.nix`. +# +# The main differences from the Nixpkgs version are +# +# - not specific to strings, i.e., any payload is OK, +# +# - the addition of the function `entryBefore` indicating a "wanted +# by" relationship. +{lib}: let + inherit (lib) all filterAttrs nvim mapAttrs toposort; +in { + empty = {}; + + isEntry = e: e ? data && e ? after && e ? before; + isDag = dag: + builtins.isAttrs dag && all nvim.dag.isEntry (builtins.attrValues dag); + + /* + Takes an attribute set containing entries built by entryAnywhere, + entryAfter, and entryBefore to a topologically sorted list of + entries. + + Internally this function uses the `toposort` function in + `` and its value is accordingly. + + Specifically, the result on success is + + { result = [ { name = ?; data = ?; } … ] } + + For example + + nix-repl> topoSort { + a = entryAnywhere "1"; + b = entryAfter [ "a" "c" ] "2"; + c = entryBefore [ "d" ] "3"; + d = entryBefore [ "e" ] "4"; + e = entryAnywhere "5"; + } == { + result = [ + { data = "1"; name = "a"; } + { data = "3"; name = "c"; } + { data = "2"; name = "b"; } + { data = "4"; name = "d"; } + { data = "5"; name = "e"; } + ]; + } + true + + And the result on error is + + { + cycle = [ { after = ?; name = ?; data = ? } … ]; + loops = [ { after = ?; name = ?; data = ? } … ]; + } + + For example + + nix-repl> topoSort { + a = entryAnywhere "1"; + b = entryAfter [ "a" "c" ] "2"; + c = entryAfter [ "d" ] "3"; + d = entryAfter [ "b" ] "4"; + e = entryAnywhere "5"; + } == { + cycle = [ + { after = [ "a" "c" ]; data = "2"; name = "b"; } + { after = [ "d" ]; data = "3"; name = "c"; } + { after = [ "b" ]; data = "4"; name = "d"; } + ]; + loops = [ + { after = [ "a" "c" ]; data = "2"; name = "b"; } + ]; + } + true + */ + topoSort = dag: let + dagBefore = dag: name: + builtins.attrNames + (filterAttrs (n: v: builtins.elem name v.before) dag); + normalizedDag = + mapAttrs (n: v: { + name = n; + data = v.data; + after = v.after ++ dagBefore dag n; + }) + dag; + before = a: b: builtins.elem a.name b.after; + sorted = toposort before (builtins.attrValues normalizedDag); + in + if sorted ? result + then { + result = map (v: {inherit (v) name data;}) sorted.result; + } + else sorted; + + # Applies a function to each element of the given DAG. + map = f: mapAttrs (n: v: v // {data = f n v.data;}); + + entryBetween = before: after: data: {inherit data before after;}; + + # Create a DAG entry with no particular dependency information. + entryAnywhere = nvim.dag.entryBetween [] []; + + entryAfter = nvim.dag.entryBetween []; + entryBefore = before: nvim.dag.entryBetween before []; +} diff --git a/lib/default.nix b/lib/default.nix new file mode 100644 index 0000000..bb4a271 --- /dev/null +++ b/lib/default.nix @@ -0,0 +1,5 @@ +{lib}: { + dag = import ./dag.nix {inherit lib;}; + booleans = import ./booleans.nix {inherit lib;}; + types = import ./types.nix {inherit lib;}; +} diff --git a/lib/hm-module.nix b/lib/hm-module.nix new file mode 100644 index 0000000..838f8c4 --- /dev/null +++ b/lib/hm-module.nix @@ -0,0 +1,44 @@ +# Home Manager module +{ + config, + pkgs, + lib ? pkgs.lib, + ... +}: let + cfg = config.programs.neovim-flake; + set = pkgs.neovim-maximal {mainConfig = cfg.settings;}; +in + with lib; { + meta.maintainers = [maintainers.notashelf]; + + options.programs.neovim-flake = { + enable = mkEnableOption "A NeoVim IDE with a focus on configurability and extensibility."; + + settings = mkOption { + type = types.attrsOf types.anything; + default = {}; + example = literalExpression '' + { + vim.viAlias = false; + vim.vimAlias = true; + vim.lsp = { + enable = true; + formatOnSave = true; + lightbulb.enable = true; + lspsaga.enable = false; + nvimCodeActionMenu.enable = true; + trouble.enable = true; + lspSignature.enable = true; + rust.enable = false; + nix = true; + }; + } + ''; + description = "Attribute set of neoflake preferences."; + }; + }; + + config = mkIf cfg.enable { + home.packages = [set.neovim]; + }; + } diff --git a/lib/stdlib-extended.nix b/lib/stdlib-extended.nix new file mode 100644 index 0000000..f43bc15 --- /dev/null +++ b/lib/stdlib-extended.nix @@ -0,0 +1,13 @@ +# From home-manager: https://github.com/nix-community/home-manager/blob/master/modules/lib/stdlib-extended.nix +# Just a convenience function that returns the given Nixpkgs standard +# library extended with the HM library. +nixpkgsLib: let + mkNvimLib = import ./.; +in + nixpkgsLib.extend (self: super: { + nvim = mkNvimLib {lib = self;}; + + # For forward compatibility. + literalExpression = super.literalExpression or super.literalExample; + literalDocBook = super.literalDocBook or super.literalExample; + }) diff --git a/lib/types-dag.nix b/lib/types-dag.nix new file mode 100644 index 0000000..439c09d --- /dev/null +++ b/lib/types-dag.nix @@ -0,0 +1,68 @@ +# From home-manager: https://github.com/nix-community/home-manager/blob/master/modules/lib/types-dag.nix +# Used for ordering config text. +{lib}: let + inherit + (lib) + defaultFunctor + nvim + mkIf + mkOrder + mkOption + mkOptionType + types + ; + + dagEntryOf = elemType: let + submoduleType = types.submodule ({name, ...}: { + options = { + data = mkOption {type = elemType;}; + after = mkOption {type = with types; listOf str;}; + before = mkOption {type = with types; listOf str;}; + }; + config = mkIf (elemType.name == "submodule") { + data._module.args.dagName = name; + }; + }); + maybeConvert = def: + if nvim.dag.isEntry def.value + then def.value + else + nvim.dag.entryAnywhere ( + if def ? priority + then mkOrder def.priority def.value + else def.value + ); + in + mkOptionType { + name = "dagEntryOf"; + description = "DAG entry of ${elemType.description}"; + # leave the checking to the submodule type + merge = loc: defs: + submoduleType.merge loc (map (def: { + inherit (def) file; + value = maybeConvert def; + }) + defs); + }; +in rec { + # A directed acyclic graph of some inner type. + # + # Note, if the element type is a submodule then the `name` argument + # will always be set to the string "data" since it picks up the + # internal structure of the DAG values. To give access to the + # "actual" attribute name a new submodule argument is provided with + # the name `dagName`. + dagOf = elemType: let + attrEquivalent = types.attrsOf (dagEntryOf elemType); + in + mkOptionType rec { + name = "dagOf"; + description = "DAG of ${elemType.description}"; + inherit (attrEquivalent) check merge emptyValue; + getSubOptions = prefix: elemType.getSubOptions (prefix ++ [""]); + getSubModules = elemType.getSubModules; + substSubModules = m: dagOf (elemType.substSubModules m); + functor = (defaultFunctor name) // {wrapped = elemType;}; + nestedTypes.elemType = elemType; + }; +} diff --git a/lib/types-plugin.nix b/lib/types-plugin.nix new file mode 100644 index 0000000..9e31823 --- /dev/null +++ b/lib/types-plugin.nix @@ -0,0 +1,81 @@ +{lib}: +with lib; let + # Plugin must be same as input name from flake.nix + availablePlugins = [ + # TODO: sort by category + "nvim-treesitter-context" + "gitsigns-nvim" + "plenary-nvim" + "nvim-lspconfig" + "nvim-treesitter" + "lspsaga" + "lspkind" + "nvim-lightbulb" + "lsp-signature" + "nvim-tree-lua" + "nvim-bufferline-lua" + "lualine" + "nvim-compe" + "nvim-autopairs" + "nvim-ts-autotag" + "nvim-web-devicons" + "tokyonight" + "bufdelete-nvim" + "nvim-cmp" + "cmp-nvim-lsp" + "cmp-buffer" + "cmp-vsnip" + "cmp-path" + "cmp-treesitter" + "crates-nvim" + "vim-vsnip" + "nvim-code-action-menu" + "trouble" + "null-ls" + "which-key" + "indent-blankline" + "nvim-cursorline" + "sqls-nvim" + "glow-nvim" + "telescope" + "rust-tools" + "onedark" + "catppuccin" + "minimap-vim" + "dashboard-nvim" + "alpha-nvim" + "scrollbar-nvim" + "codewindow-nvim" + "nvim-notify" + "cinnamon-nvim" + "cheatsheet-nvim" + "colorizer" + "venn-nvim" + "cellular-automaton" + "presence-nvim" + "icon-picker-nvim" + "dressing-nvim" + "orgmode-nvim" + "obsidian-nvim" + "vim-markdown" + "tabular" + "toggleterm-nvim" + "noice-nvim" + "nui-nvim" + "copilot-lua" + "tabnine-nvim" + "nvim-session-manager" + "gesture-nvim" + ]; + # You can either use the name of the plugin or a package. + pluginsType = with types; listOf (nullOr (either (enum availablePlugins) package)); +in { + pluginsOpt = { + description, + default ? [], + }: + mkOption { + inherit description default; + type = pluginsType; + }; +} diff --git a/lib/types.nix b/lib/types.nix new file mode 100644 index 0000000..8625f18 --- /dev/null +++ b/lib/types.nix @@ -0,0 +1,7 @@ +{lib}: let + typesDag = import ./types-dag.nix {inherit lib;}; + typesPlugin = import ./types-plugin.nix {inherit lib;}; +in { + inherit (typesDag) dagOf; + inherit (typesPlugin) pluginsOpt; +}