Skip to content

Instantly share code, notes, and snippets.

@kesor
Last active November 15, 2025 09:12
Show Gist options
  • Select an option

  • Save kesor/e3f6d5e521bb522bc7f0353861471d9a to your computer and use it in GitHub Desktop.

Select an option

Save kesor/e3f6d5e521bb522bc7f0353861471d9a to your computer and use it in GitHub Desktop.
Installing language servers with Home-Manager Nix
{
pkgs,
lib,
config,
...
}:
let
cfg = config.programs.language-servers;
allLanguageServers = with pkgs; [
bash-language-server
ctags-lsp
diagnostic-languageserver
docker-compose-language-service
dockerfile-language-server
emmet-language-server
golangci-lint
golangci-lint-langserver
gopls
harper
helm-ls
lemminx
lua-language-server
markdown-oxide
marksman
nginx-language-server
nil
nixd
openscad-lsp
pyright
ruby-lsp
tofu-ls
typescript-language-server
typos
typos-lsp
vale-ls
vim-language-server
vscode-langservers-extracted
yaml-language-server
];
in
{
options.programs.language-servers = {
enable = lib.mkEnableOption "language servers";
packages = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = allLanguageServers;
description = "List of language server packages to install";
};
};
config = lib.mkIf cfg.enable {
# Install language servers in a separate profile/environment
# They'll be available via absolute paths but not in user PATH
xdg.dataFile."language-servers" = {
source = pkgs.symlinkJoin {
name = "language-servers";
paths = cfg.packages;
};
};
# Create a convenience script to add them to PATH when needed
home.packages = [
(pkgs.writeShellScriptBin "with-language-servers"
# bash
''
export PATH="''${LANGUAGE_SERVERS_PATH}:''${PATH}"
exec "$@"
''
)
];
# Export the package list for other modules to use
home.sessionVariables = {
LANGUAGE_SERVERS_PATH = "${config.xdg.dataHome}/language-servers/bin";
};
};
}
{ pkgs, config, ... }:
{
xdg.configFile."nvim/lua/lsp.lua".source = ./lsp.lua;
xdg.configFile."nvim/lsp".source = ./lsp-config;
# Language servers are now managed by the dedicated language-servers module
# We make them available to Neovim by symlinking them into its runtime environment
programs.neovim = {
extraPackages = [
# Only include packages that aren't language servers
pkgs.lua5_1
pkgs.luajit
pkgs.luarocks
]
++ (
if config.programs.language-servers.enable then
[
# Create a symlink join of all language servers for Neovim to use
(pkgs.symlinkJoin {
name = "neovim-language-servers";
paths = config.programs.language-servers.packages;
})
]
else
[ ]
);
# Add language servers to PATH for Neovim as a fallback
extraLuaConfig =
# lua
''
-- Add language servers to PATH if they exist
local lsp_path = vim.fn.expand("''$LANGUAGE_SERVERS_PATH")
if vim.fn.isdirectory(lsp_path) == 1 then
-- Set PATH for the current Neovim process
vim.env.PATH = lsp_path .. ":" .. vim.env.PATH
-- Also set it for child processes
vim.fn.setenv("PATH", lsp_path .. ":" .. vim.fn.getenv("PATH"))
end
'';
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment