NixOSでmason.nvimで入れたLSP serverを使えるようにする 

## 概要
[mason.nvim](https://github.com/williamboman/mason.nvim)はNeovim向けのLSP serverマネージャーで、OSに応じてビルド済みバイナリ取ってきたり、コンパイルを勝手にやってくれます。
ビルド済みバイナリはライブラリへのリンクがハードリンクでありFHS(Filesystem Hierarchy Standard)に準拠していないNixOSでは基本的にそのまま使うことができません。
この問題は2023/01/28現在、[issue#428](https://github.com/williamboman/mason.nvim/issues/428)で議論されています。
ここではNixOSでmason.nvimで取ってきたLSP serverを使う方法を記します。

なお、mason.nvimを使わなくてもnixpkgsでLSP serverを管理する方法もあります。
長所と短所は以下の通りです。

- mason.nvim
    - 長所
        - linux, mac, windowsでコードを共用できる
        - 常に最新版のパッケージが使える
        - グラフィカルにインストール状況がNeovimから参照できる
    - 短所
        - 最新のパッケージのbreaking changeを踏む可能性が高くなる
        - NixOSではpatchを当てないと使えないパッケージがある
            - mason.nvim上ではインストール完了扱いになっているためLSP起動時まで使えるか分からない
- nixpkgs
    - 長所
        - インストールすれば即使える状態になっている
        - linuxとmacではnixpkgs周りのコードを共用できる
    - 短所
        - channelによっては更新が遅い
        - パッケージが長くメンテナンスされていないことがある
            - ArchLinuxと違ってNixOSでは更新が止まっているpkgsが散見される
        - windowsでは使えない

## パッチの当て方
パッチは[patchelf](https://github.com/NixOS/patchelf)を使って当てます。(事前にインストールが必要)
patchelfの使い方は下に書いた。

`mason-registry`でインストール後にhookが起動するようにし、そこでハードリンクを修正します。

```lua
local mason = require("mason")
local mason_lsp = require("mason-lspconfig")

-- Make mason packages work with nixos
-- We're using patchelf to mathe that work
-- Thanks to: https://github.com/williamboman/mason.nvim/issues/428#issuecomment-1357192515
local function return_exe_value(cmd)
    local handle = io.popen(cmd)
    local result = handle:read("*a")
    handle:close()

    return result
end

local mason_registry = require("mason-registry")
if is_linux and vim.api.nvim_exec("!cat /etc/os-release | grep '^NAME'", true):find("NixOS") ~= nil then
    mason_registry:on("package:install:success", function(pkg)
        pkg:get_receipt():if_present(function(receipt)
            -- Figure out the interpreter inspecting nvim itself
            -- This is the same for all packages, so compute only once
            -- Set the interpreter on the binary
            local nvim = return_exe_value("nix path-info -r /run/current-system | grep neovim-unwrapped"):sub(1, -2)
            local interpreter = return_exe_value(("patchelf --print-interpreter %q" .. "/bin/nvim"):format(nvim)):sub(1, -2)
            for _, rel_path in pairs(receipt.links.bin) do
                local bin_abs_path = pkg:get_install_path() .. "/" .. rel_path
                if pkg.name == "lua-language-server" then
                    bin_abs_path = pkg:get_install_path() .. "/extension/server/bin/lua-language-server"
                    os.execute(("patchelf --set-interpreter %s %s"):format(interpreter, bin_abs_path))
                elseif pkg.name == "marksman" then
                    bin_abs_path = pkg:get_install_path() .. "/marksman"
                    local libstdcpp = return_exe_value("nix path-info -r /run/current-system | grep gcc | grep lib | head -n1"):sub(1, -2) .. "/lib"
                    local zlib = return_exe_value("nix path-info -r /run/current-system | grep zlib | head -n1"):sub(1, -2) .. "/lib"
                    local icu4c = return_exe_value("nix path-info -r /run/current-system | grep icu4c | head -n1"):sub(1, -2) .. "/lib"
                    os.execute(("patchelf --set-interpreter %s --set-rpath %s:%s:%s %s"):format(interpreter, libstdcpp, zlib, icu4c, bin_abs_path))
                elseif pkg.name == "stylua" then
                    bin_abs_path = pkg:get_install_path() .. "/stylua"
                    os.execute(("patchelf --set-interpreter %s %s"):format(interpreter, bin_abs_path))
                elseif pkg.name == "texlab" then
                    bin_abs_path = pkg:get_install_path() .. "/texlab"
                    os.execute(("patchelf --set-interpreter %s %s"):format(interpreter, bin_abs_path))
                end
            end
        end)
    end)
end
```

## patchelfの使い方
pathelfの基本的な使い方は以下の通り(詳しい説明は[NixOS wiki: Packaging/Binaries](https://nixos.wiki/wiki/Packaging/Binaries)

1. `patchelf --print-interpreter <nixpkgsでインストールした適当なバイナリ>`
2. `patchelf --set-interpreter <1.で出力されたinterpreterのパス> <バイナリ>`
2.で動作しない場合
3. `patchelf --print-needed <バイナリ>`で依存ライブラリを列挙
4. `fd <ライブラリ> /run/current-system`などで依存ライブラリを探す
5. `patchelf --set-interpreter <1.で出力されたinterpreterのパス> --set-rpath <lib path 1>:<lib path 2>:... <バイナリ>`

## まとめ
patchelfを使ってmason.nvimでインストールしたLSP serverをNixOSで動作させる方法を記しました。
LSPを動作させるまでNixOS上で実行可能かどうか分からず、都度パッチを当てる必要があることからnixpkgsで管理した方が幸せになれる気もします。
(そうすると今度はwindowsためにコードを書かないといけないという...)