nixd
nixd copied to clipboard
Infer flake.nix as the source of nixpkgs
I realized my editor configuration (this may be used to refine the existing Emacs configuration) is dynamically finding the Flake to use. When most of the time I'm editing a flake.nix, this actually makes little sense.
;; Thanks Mr. Purcell
;; https://github.com/purcell/emacs.d/blob/master/lisp/init-nix.el
;;
;; we can ignore the use of nix-ts-mode, which is just tree sitter grammar
(use-package nix-ts-mode
:ensure (nix-ts-mode
:fetcher github
:repo "remi-gelinas/nix-ts-mode")
:init (add-to-list 'auto-mode-alist '("\\.nix\\'" . nix-ts-mode))
:hook (nix-ts-mode . eglot-ensure)
:config
;; Here is the informative bit. If the flake.nix exists, that is treated as the location
;; for the nixpkgs inputs.
(defun pmx--project-flake-path (_)
(let ((flake-path (expand-file-name "flake.nix" (projectile-project-root))))
(if (file-exists-p flake-path)
`("nixd"
:initializationOptions
(:nixpkgs (:expr ,(format "import (builtins.getFlake
\"%s\").inputs.nixpkgs { }" flake-path))))
'("nixd"))))
;; After eglot is loaded, we add nixd, with this dynamic configuration function, to its settings
(let ((nix-settings
'((nix-ts-mode) . #'pmx--project-flake-path)))
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs nix-settings))))
Why not just decide this when editing a file called flake.nix and infer that it has inputs and that those inputs define what will be used within the file?
Why not just decide this when editing a file called flake.nix and infer that it has inputs and that those inputs define what will be used within the file?
Because a nixd user may not have just one flake.nix; for instance, when editing their own flake (e.g., for NixOS configuration files), they might use version 1 of nixpkgs, while in another project (e.g., niri), which also has a flake.nix, that flake comes with version 2 of nixpkgs. The user might want to always point to and use version 1 of nixpkgs, and may prefer not to fetch version 2 of nixpkgs at all.
We can still override this via the language server settings. The suggestion is only for the case where nixpkgs is not passed as a setting and the file being edited is flake.nix and defines nixpkgs as an input
I usually set a FLAKE variable in my devShells, load those with direnv and than use this expression to load the flake:
(builtins.getFlake (builtins.getEnv "FLAKE")). This works quite well with multiple flake repositories.
use this expression to load the flake:
(builtins.getFlake (builtins.getEnv "FLAKE")).
I took some inspiration and wrote a nix expression that finds the current flake by walking up the current directory path.
This works best if you can save the nix expression to a file in the nix store and reference that in your nixd config:
https://github.com/MattSturgeon/nix-config/commit/b8aa42d6c01465949ef5cd9d4dc086d4eaa36793
Nix expression
let
self = "TODO: Path to main/global flake";
system = builtins.currentSystem;
# Reimplementation of `lib.lists.dropEnd 1` using builtins
dropLast =
list:
let
len = builtins.length list;
dropped = builtins.genList (builtins.elemAt list) (len - 1);
in
if list == [ ] || len == 1 then [ ] else dropped;
# Walk up the directory path, looking for a flake.nix file
# Called with an absolute filepath
findFlake =
dir:
let
isPart = part: builtins.isString part && part != "" && part != ".";
parts = builtins.filter isPart (builtins.split "/+" dir);
in
findFlake' parts;
# Underlying impl of findFlake
# Called with a list path instead of a string path
findFlake' =
parts:
let
dir = "/" + builtins.concatStringsSep "/" parts;
files = builtins.readDir dir;
isFlake = files."flake.nix" or null == "regular";
parent = dropLast parts;
in
if parts == [ ] then
null
else if isFlake then
dir
else
findFlake' parent;
# Path to the local flake, or null
path = findFlake (builtins.getEnv "PWD");
in
{
inherit system self path;
local = if path == null then null else builtins.getFlake path;
global = builtins.getFlake self;
}
Then you can import that file into your nixd expressions:
nixd settings
init.lua = /* lua */ ''
{
nixd = {
diagnostic = {
suppress = { "sema-escaping-with", "var-bind-to-this" }
},
nixpkgs = {
expr = "with import ${./nixd-expr.nix}; import (if local ? lib.version then local else local.inputs.nixpkgs or global.inputs.nixpkgs) { }"
},
options = {
["flake-parts"] = {
expr = "with import ${./nixd-expr.nix}; local.debug.options or global.debug.options"
},
["home-manager"] = {
expr = "with import ${./nixd-expr.nix}; global.nixosConfigurations.desktop.options.home-manager.users.type.getSubOptions [ ]"
},
nixos = {
expr = "with import ${./nixd-expr.nix}; global.nixosConfigurations.desktop.options"
},
nixvim = {
expr = "with import ${./nixd-expr.nix}; global.nixvimConfigurations.''${system}.default.options"
}
}
}
}
''