Skip to content

Commit

Permalink
Add support for nix command
Browse files Browse the repository at this point in the history
  • Loading branch information
one-d-wide authored and dundalek committed Feb 26, 2025
1 parent 91146e3 commit 6c3e9bb
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 7 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ If this is not what you want, you can give a try to the [curated configuration](

## How it works

`lazy-lsp` registers all available configurations from lspconfig to start LSP servers by wrapping the commands in a [nix-shell](https://nixos.org/manual/nix/unstable/command-ref/nix-shell.html) environment. The nix-shell prepares the environment by pulling all specified dependencies regardless of what is installed on the host system and avoids packages clashes. The first time a server is run there is a delay until dependencies are downloaded, but on subsequent runs the time to prepare the shell environment is negligible.
`lazy-lsp` registers all available configurations from lspconfig to start LSP servers by wrapping the commands in a [`nix shell`](https://nix.dev/manual/nix/latest/command-ref/new-cli/nix3-env-shell) or an older [nix-shell](https://nixos.org/manual/nix/unstable/command-ref/nix-shell.html) environment. The nix shell prepares the environment by pulling all specified dependencies regardless of what is installed on the host system and avoids packages clashes. The first time a server is run there is a delay until dependencies are downloaded, but on subsequent runs the time to prepare the shell environment is negligible.

## Versions

Expand Down
56 changes: 50 additions & 6 deletions lua/lazy-lsp/helpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,55 @@ local function escape_shell_args(args)
return table.concat(escaped, " ")
end

local cache_nix_command_available = nil
-- Check whether both `nix` command and `nixpkgs` flake are available
---@return boolean?
local function nix_command_available()
if cache_nix_command_available ~= nil then
return cache_nix_command_available
end
cache_nix_command_available = false

local registry = vim.fn.system({ "nix", "--flake-registry", "", "registry", "list" })
if vim.v.shell_error == 0 then
for flake in vim.gsplit(registry, "\n") do
local flake_url = string.match(flake, "^%S+ flake:nixpkgs (.*)")
if flake_url then
-- And we won't accidentally fetch `nixpkgs-unstable` as it used to be.
-- Only since NixOS 24.05, system has its nixpkgs flake in the registry by default
if string.match(flake_url, "^path:") then
cache_nix_command_available = true
end
break
end
end
end
return cache_nix_command_available
end

---@param nix_pkgs string[]
---@param cmd string[]
---@return string[]
local function in_shell(nix_pkgs, cmd)
local nix_cmd = { "nix-shell", "-p" }
vim.list_extend(nix_cmd, nix_pkgs)
table.insert(nix_cmd, "--run")
table.insert(nix_cmd, escape_shell_args(cmd))
if #nix_pkgs == 0 then
error("No nix pkg provided")
return {}
end

local nix_cmd = {}
if nix_command_available() then
nix_cmd = { "nix", "--flake-registry", "", "shell" }
for _, nix_pkg in ipairs(nix_pkgs) do
table.insert(nix_cmd, "nixpkgs#" .. nix_pkg)
end
table.insert(nix_cmd, "--command")
vim.list_extend(nix_cmd, cmd)
else
nix_cmd = { "nix-shell", "-p" }
vim.list_extend(nix_cmd, nix_pkgs)
table.insert(nix_cmd, "--run")
table.insert(nix_cmd, escape_shell_args(cmd))
end
return nix_cmd
end

Expand Down Expand Up @@ -48,8 +92,8 @@ local function process_config(

config.on_new_config = function(new_config, root_path)
pcall(original_on_new_config, new_config, root_path)
-- Don't wrap with nix-shell if user callback already wrapped it
if new_config.cmd[1] ~= "nix-shell" then
-- Don't wrap with nix shell if user callback already wrapped it
if not vim.list_contains({ "nix", "nix-shell" }, new_config.cmd[1]) then
if prefer_local == false or vim.fn.executable(new_config.cmd[1]) == 0 then
local nix_pkgs = type(nix_pkg) == "string" and { nix_pkg } or nix_pkg
new_config.cmd = in_shell(nix_pkgs, new_config.cmd)
Expand Down
45 changes: 45 additions & 0 deletions tests/helpers_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,52 @@ local function make_config(config)
return config
end

local nix_conf_var = "NIX_CONFIG"
local nix_conf_dir_var = "NIX_CONF_DIR"
vim.uv.os_unsetenv(nix_conf_var)
vim.uv.os_setenv(nix_conf_dir_var, "/dev/null")

describe("lazy-lsp", function()
it("in_shell with `nix shell` command", function()
local nix_dir = vim.fs.joinpath(vim.uv.cwd(), ".tests", "nix")
vim.uv.os_setenv(nix_conf_dir_var, nix_dir)
vim.uv.os_setenv(nix_conf_var, "extra-experimental-features = nix-command flakes")

assert(vim.fn.system({
"nix",
"registry",
"add",
"--registry",
vim.fs.joinpath(nix_dir, "registry.json"),
"nixpkgs",
"path:/nix/store/smth",
}))

package.loaded["lazy-lsp.helpers"] = nil
helpers = require("lazy-lsp.helpers")

assert.same(
{ "nix", "--flake-registry", "", "shell", "nixpkgs#nix_pkg_a", "--command", "cmd_a" },
helpers.in_shell({ "nix_pkg_a" }, { "cmd_a" })
)

assert.same(
{ "nix", "--flake-registry", "", "shell", "nixpkgs#nix_pkg_a", "--command", "cmd_a", "arg_a" },
helpers.in_shell({ "nix_pkg_a" }, { "cmd_a", "arg_a" })
)

assert.same(
{ "nix", "--flake-registry", "", "shell", "nixpkgs#nix_pkg_a", "nixpkgs#nix_pkg_b", "--command", "cmd_a" },
helpers.in_shell({ "nix_pkg_a", "nix_pkg_b" }, { "cmd_a" })
)

vim.uv.os_setenv(nix_conf_dir_var, "/dev/null")
vim.uv.os_unsetenv(nix_conf_var)

package.loaded["lazy-lsp.helpers"] = nil
helpers = require("lazy-lsp.helpers")
end)

it("escape_shell_arg", function()
assert.same("'hello world'", helpers.escape_shell_arg("hello world"))
assert.same("'h\"i'", helpers.escape_shell_arg('h"i'))
Expand Down

0 comments on commit 6c3e9bb

Please # to comment.