commit 2d9d889d94882d21e3408257013f783c685578db Author: LeMarsu Date: Tue Apr 2 02:36:04 2024 +0200 first import diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f3bf9c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.direnv +.envrc diff --git a/README.md b/README.md new file mode 100644 index 0000000..24a17ca --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# TODO + +## MVP + +- [X] Find a way to handle local plugins (like spos.nvim) +- [X] Generate static init.nvim file + +## Backlog + +- [ ] handle dependencies + - [ ] Explore how lazy load plugins + - [ ] load manual plugins + - [ ] play with hm dag + - [ ] respect dependencies in loading +- [ ] Generate spell files on build ? diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..0c404a7 --- /dev/null +++ b/flake.lock @@ -0,0 +1,254 @@ +{ + "nodes": { + "alejandra": { + "inputs": { + "fenix": "fenix", + "flakeCompat": "flakeCompat", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660592437, + "narHash": "sha256-xFumnivtVwu5fFBOrTxrv6fv3geHKF04RGP23EsDVaI=", + "owner": "kamadorueda", + "repo": "alejandra", + "rev": "e7eac49074b70814b542fee987af2987dd0520b5", + "type": "github" + }, + "original": { + "owner": "kamadorueda", + "ref": "3.0.0", + "repo": "alejandra", + "type": "github" + } + }, + "fenix": { + "inputs": { + "nixpkgs": [ + "alejandra", + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1657607339, + "narHash": "sha256-HaqoAwlbVVZH2n4P3jN2FFPMpVuhxDy1poNOR7kzODc=", + "owner": "nix-community", + "repo": "fenix", + "rev": "b814c83d9e6aa5a28d0cf356ecfdafb2505ad37d", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flakeCompat": { + "flake": false, + "locked": { + "lastModified": 1650374568, + "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "b4a34015c698c7793d592d66adbab377907a2be8", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "nil": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + }, + "locked": { + "lastModified": 1704611696, + "narHash": "sha256-4ZCgV5oHdEc3q+XaIzy//gh20uC/aSuAtMU9bsfgLZk=", + "owner": "oxalica", + "repo": "nil", + "rev": "059d33a24bb76d2048740bcce936362bf54b5bc9", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "nil", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1704161960, + "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "63143ac2c9186be6d9da6035fa22620018c85932", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1711668574, + "narHash": "sha256-u1dfs0ASQIEr1icTVrsKwg2xToIpn7ZXxW3RHfHxshg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "219951b495fc2eac67b1456824cc1ec1fd2ee659", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "alejandra": "alejandra", + "nil": "nil", + "nixpkgs": "nixpkgs_2", + "utils": "utils" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1657557289, + "narHash": "sha256-PRW+nUwuqNTRAEa83SfX+7g+g8nQ+2MMbasQ9nt6+UM=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "caf23f29144b371035b864a1017dbc32573ad56d", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": [ + "nil", + "flake-utils" + ], + "nixpkgs": [ + "nil", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1704593904, + "narHash": "sha256-nDoXZDTRdgF3b4n3m011y99nYFewvOl9UpzFvP8Rb3c=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "c36fd70a99decfa6e110c86f296a97613034a680", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "flake-utils": "flake-utils_2" + }, + "locked": { + "lastModified": 1696281284, + "narHash": "sha256-xcmtTmoiiAOSk4abifbtqVZk0iwBcqJfg47iUbkwhcE=", + "owner": "gytis-ivaskevicius", + "repo": "flake-utils-plus", + "rev": "6cf1e312fb259693c4930d07ca3cbe1d07ef4a48", + "type": "github" + }, + "original": { + "owner": "gytis-ivaskevicius", + "ref": "v1.4.0", + "repo": "flake-utils-plus", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ec9751c --- /dev/null +++ b/flake.nix @@ -0,0 +1,36 @@ +{ + description = "My neovim configuration"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-23.11"; + utils.url = "github:gytis-ivaskevicius/flake-utils-plus/v1.4.0"; + nil.url = "github:oxalica/nil"; + alejandra = { + url = "github:kamadorueda/alejandra/3.0.0"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { + self, + nixpkgs, + utils, + alejandra, + ... + } @ inputs: let + in + utils.lib.mkFlake { + inherit self inputs; + outputsBuilder = channel: let + system = channel.nixpkgs.system; + in { + formatter = alejandra.defaultPackage.${channel.nixpkgs.system}; + devShells.default = import ./shell.nix { + pkgs = channel.nixpkgs; + inherit (inputs.nil.packages.${system}) nil; + }; + }; + + lib = import ./lib.nix { inherit nixpkgs; }; + }; +} diff --git a/hm-file-type.nix b/hm-file-type.nix new file mode 100644 index 0000000..be4392e --- /dev/null +++ b/hm-file-type.nix @@ -0,0 +1,152 @@ +# Imported from home-manager. Couldn't find another way to access it +{ + homeDirectory, + lib, + pkgs, +}: let + inherit + (lib) + hasPrefix + hm + literalExpression + mkDefault + mkIf + mkOption + removePrefix + types + ; +in rec { + # Constructs a type suitable for a `home.file."specific/path"` like option. The + # target path may be either absolute or relative, in which case it + # is relative the `basePath` argument (which itself must be an + # absolute path). + # + # Arguments: + # - opt the name of the option, for self-references + # - basePathDesc docbook compatible description of the base path + # - basePath the file base path + fileTypeSubmodule = opt: basePathDesc: basePath: + types.submodule ({ + name, + config, + ... + }: { + options = { + enable = mkOption { + type = types.bool; + default = true; + description = '' + Whether this file should be generated. This option allows specific + files to be disabled. + ''; + }; + target = mkOption { + type = types.str; + apply = p: let + absPath = + if hasPrefix "/" p + then p + else "${basePath}/${p}"; + in + removePrefix (homeDirectory + "/") absPath; + defaultText = literalExpression "name"; + description = '' + Path to target file relative to ${basePathDesc}. + ''; + }; + + text = mkOption { + default = null; + type = types.nullOr types.lines; + description = '' + Text of the file. If this option is null then + [](#opt-${opt}._name_.source) + must be set. + ''; + }; + + source = mkOption { + type = types.path; + description = '' + Path of the source file or directory. If + [](#opt-${opt}._name_.text) + is non-null then this option will automatically point to a file + containing that text. + ''; + }; + + executable = mkOption { + type = types.nullOr types.bool; + default = null; + description = '' + Set the execute bit. If `null`, defaults to the mode + of the {var}`source` file or to `false` + for files created through the {var}`text` option. + ''; + }; + + recursive = mkOption { + type = types.bool; + default = false; + description = '' + If the file source is a directory, then this option + determines whether the directory should be recursively + linked to the target location. This option has no effect + if the source is a file. + + If `false` (the default) then the target + will be a symbolic link to the source directory. If + `true` then the target will be a + directory structure matching the source's but whose leafs + are symbolic links to the files of the source directory. + ''; + }; + + onChange = mkOption { + type = types.lines; + default = ""; + description = '' + Shell commands to run when file has changed between + generations. The script will be run + *after* the new files have been linked + into place. + + Note, this code is always run when `recursive` is + enabled. + ''; + }; + + force = mkOption { + type = types.bool; + default = false; + visible = false; + description = '' + Whether the target path should be unconditionally replaced + by the managed file source. Warning, this will silently + delete the target regardless of whether it is a file or + link. + ''; + }; + }; + + config = { + target = mkDefault name; + source = mkIf (config.text != null) (mkDefault (pkgs.writeTextFile { + inherit (config) text; + executable = config.executable == true; # can be null + name = hm.strings.storeFileName name; + })); + }; + }); + # Constructs a type suitable for a `home.file` like option. The + # target path may be either absolute or relative, in which case it + # is relative the `basePath` argument (which itself must be an + # absolute path). + # + # Arguments: + # - opt the name of the option, for self-references + # - basePathDesc docbook compatible description of the base path + # - basePath the file base path + fileTypeAttrSet = opt: basePathDesc: basePath: + types.attrsOf (fileTypeSubmodule opt basePathDesc basePath); +} diff --git a/lib.nix b/lib.nix new file mode 100644 index 0000000..ebc2852 --- /dev/null +++ b/lib.nix @@ -0,0 +1,324 @@ +{...}: { + mkNeovimModule = { + pluginsDir ? null, + attrName ? "neoflake", + self, + }: { + config, + lib, + pkgs, + ... + }: let + cfg = config.${attrName}; + inherit (builtins) baseNameOf isPath; + inherit (lib) mkEnableOption mkIf mkOption types; + # inherit (lib.debug) traceIf traceSeq traceVal traceValSeq traceValFn; + inherit (lib.attrsets) attrNames optionalAttrs; + inherit (lib.lists) concatMap filter foldl' map optional reverseList; + inherit (lib.strings) concatStringsSep fileContents hasSuffix removePrefix removeSuffix replaceStrings; + + hm-file-type = import ./hm-file-type.nix { + inherit (config.home) homeDirectory; + inherit lib pkgs; + }; + inherit (hm-file-type) fileTypeSubmodule; + + verbatimSubmodule = types.submodule { + options = { + path = mkOption { + description = "path to copy from. Must be included in the flake folder."; + type = types.path; + }; + + dest = mkOption { + description = "dest into `.config/nvim`."; + type = types.str; + }; + }; + }; + + remotePluginConfig = types.addCheck (types.submodule { + options = { + name = mkOption { + type = types.str; + }; + + src = mkOption { + type = types.path; + }; + }; + }) (mod: attrNames mod == ["name" "src"]); + + pluginWithConfigType = types.submodule { + options = { + enabled = + mkEnableOption "enabled" + // { + description = '' + Whether this plugin should be enabled. This option allows specific + plugins to be disabled. + ''; + default = true; + }; + + init = mkOption { + type = types.nullOr (fileTypeSubmodule "${attrName}.plugins._.init" "{var}`xdg.configHome/nvim`" "nvim"); + description = "Script to init this plugin. Run before plugin load."; + default = null; + }; + + config = mkOption { + type = types.nullOr (fileTypeSubmodule "${attrName}.plugins._.config" "{var}`xdg.configHome/nvim`" "nvim"); + description = "Script to configure this plugin. Run after plugin load."; + default = null; + }; + + main = mkOption { + type = with types; nullOr str; + description = "Name of the main module to load."; + default = null; + }; + + ## Lazy options + + lazy = mkOption { + type = types.bool; + description = "Should this plugin be load lazily ?"; + default = false; + }; + + events = mkOption { + type = with types; listOf str; + description = "List of events on which the plugin should be loaded"; + default = []; + }; + + commands = mkOption { + type = with types; listOf str; + description = "List of commands on which the plugin should be loaded"; + default = []; + }; + + filetypes = mkOption { + type = with types; listOf str; + description = "List of filetypes on which the plugin should be loaded"; + default = []; + }; + + keys = mkOption { + type = with types; listOf str; + description = "List of keystrokes on which the plugin should be loaded"; + default = []; + }; + + priority = mkOption { + type = with types; listOf str; + description = '' + Priority of the module. Influence the order of loading plugins. + Highest values get loaded before. + ''; + default = []; + }; + + dependencies = mkOption { + # Should we accept strings? + # type = with types; listOf (either strings package); + type = with types; listOf package; + description = '' + Give the list of packages that should be loaded before the current one. + ''; + }; + + plugin = mkOption { + type = with types; oneOf [path remotePluginConfig package]; + description = "The actual vim plugin package to load"; + }; + }; + }; + + hasNixSuffix = hasSuffix ".nix"; + pluginNixFiles = + if isNull pluginsDir + then [] + else filter hasNixSuffix (lib.fileset.toList pluginsDir); + + pathToNeovimPlugin = src: let + normalizeName = replaceStrings ["."] ["-"]; + in + pkgs.vimUtils.buildVimPlugin rec { + inherit src; + pname = normalizeName (baseNameOf src); + name = pname; + }; + + remotePluginToNeovimPlugin = p: + pkgs.vimUtils.buildVimPlugin rec { + inherit (p) src name; + pname = name; + }; + + mkPlugin = plugin: + if plugin ? plugin + then let + p = plugin.plugin; + in + if isPath p + then pathToNeovimPlugin p + else if attrNames p == ["name" "src"] + then remotePluginToNeovimPlugin p + else p + else plugin; + in { + # imports = map wrapImport pluginNixFiles; + imports = pluginNixFiles; + + options.${attrName} = { + enable = mkEnableOption "${attrName} module"; + plugins = mkOption { + description = "List all plugins to load"; + type = with types; listOf (oneOf [package pluginWithConfigType]); + }; + + includesVerbatim = mkOption { + description = "Includes files as is in final .config/nvim."; + type = with types; listOf (either path verbatimSubmodule); + default = []; + }; + + defaultConfig = { + enable = mkOption { + description = "generate default configuration"; + type = types.bool; + default = true; + }; + }; + }; + + config = let + defaultPlugin = { + enabled = true; + init = null; + config = null; + }; + wrapIfNeeded = p: + if p ? plugin + then p + else {plugin = p;}; + normalizedPlugins = map (p: defaultPlugin // (wrapIfNeeded p)) cfg.plugins; + + getText = submodule: + if ! isNull submodule.text + then submodule.text + else fileContents submodule.source; + + wrapLuaInFunction = section: lua: '' + -- begin ${section} + (function() + ${removeSuffix "\n" lua} + end)(); + -- end ${section} + ''; + + pluginName = p: + if p.plugin ? name + then p.plugin.name + else baseNameOf p.plugin; + + getInitText = p: + optional (!(isNull p.init)) + (wrapLuaInFunction "init for ${pluginName p}" (getText p.init)); + + getConfigText = p: + optional (!(isNull p.config)) + (wrapLuaInFunction "config for ${pluginName p}" (getText p.config)); + + initLua = + concatStringsSep "\n" + (concatMap getInitText normalizedPlugins); + + configLua = + concatStringsSep "\n" + (concatMap getConfigText normalizedPlugins); + + pathToString = filePath: let + keyName = key: { + inherit key; + name = baseNameOf key; + }; + list = map (n: n.name) (builtins.genericClosure { + startSet = [(keyName filePath)]; + operator = item: let + parent = dirOf item.key; + in [(keyName parent)]; + }); + in + concatStringsSep "/" (reverseList list); + normalizeVerbatim = def: + if def ? path && def ? dest + then def + else if ! isPath def + then abort "Not a path nor a verbatim" + else let + fileStr = pathToString def; + root = pathToString self.outPath; + in { + path = def; + dest = removePrefix (root + "/") fileStr; + }; + + normalizedVerbatim = map normalizeVerbatim cfg.includesVerbatim; + + verbatimFiles = + foldl' + (memo: verbatim: + memo + // { + "nvim/${verbatim.dest}" = { + source = verbatim.path; + recursive = true; + }; + }) {} + normalizedVerbatim; + + neoflakeFiles = let + prefix = "nvim/lua/neoflake"; + in { + ${prefix} = { + source = ./lua/neoflake; + recursive = true; + }; + "${prefix}/initialize.lua".text = '' + return function () + ${initLua} + end + ''; + "${prefix}/config.lua".text = '' + return function () + ${configLua} + end + ''; + }; + + defaultConfig = optionalAttrs cfg.defaultConfig.enable { + "nvim/init.lua".source = ./lua/default_init.lua; + }; + in + mkIf cfg.enable { + programs.neovim = { + enable = true; + vimAlias = true; + viAlias = true; + defaultEditor = true; + withPython3 = true; + withNodeJs = true; + + plugins = map mkPlugin cfg.plugins; + }; + + xdg.configFile = + verbatimFiles + // neoflakeFiles + // defaultConfig; + }; + }; +} diff --git a/lua/default_init.lua b/lua/default_init.lua new file mode 100644 index 0000000..589e9f3 --- /dev/null +++ b/lua/default_init.lua @@ -0,0 +1,11 @@ +-- Generated by neoflake +local neoflake = require 'neoflake' + +-- Initialize plugins +neoflake.init() + +-- Should load plugins +neoflake.load() + +-- Load plugins configuration +neoflake.config() diff --git a/lua/neoflake/init.lua b/lua/neoflake/init.lua new file mode 100644 index 0000000..4c10bbd --- /dev/null +++ b/lua/neoflake/init.lua @@ -0,0 +1,10 @@ +local M = { + init = require('neoflake.initialize'), + config = require('neoflake.config'), +} + +function M.load() + -- Not implemented yet +end + +return M diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..2e7ef45 --- /dev/null +++ b/shell.nix @@ -0,0 +1,13 @@ +{ + pkgs, + nil, + ... +}: +with pkgs; + mkShell { + buildInputs = [ + neovim + nil + sumneko-lua-language-server + ]; + }