🗒️

[Emacs] TrampでもLSPしたい

2023/12/24に公開

Emacs Advent Calendar 2023の25日目です。

長年Emacs使っているのにElispをまともに書けない私ですが
TrampでもLSPしたい、という気持ちはしばらく忘れて別の解決策でしのいでいました。
最近Macの仕様が変わったのか、上手く行かなくなってきたので、改めて向き合ってみようと思います。

🖥️ 環境

OS: macOS Sonoma 14.2
Emacs: 29.1

💡先に解決方法

'tramp-remote-path にPATH通したいリモートマシン上のディレクトリを入れていく

(add-to-list 'tramp-remote-path "/home/xxxx/.cargo/bin")

✏️ まえがき

私は普段Macを使っているんですが
リモートワークなのもあって、Mac上ではたくさんのアプリケーションを立ち上げています。

  • ブラウザ
  • Gather
  • Slack
  • Emacs
  • Skitch
  • etc...

弊社ではGatherでカメラとマイクも常時繋ぎっぱなしで仕事しているので
CPUもGPUもメモリもわりといろいろ持っていかれます。

そうなるとMac上で開発しているとリソースが不足してきて、いろいろつらくなります。

なので、 1台別のLinuxマシンを用意し、普段はそのマシンにsshで繋いで開発 しています。
※これはDockerDesktop for Macが遅すぎてLinux上でdocker使ったほうが速くて快適だったのもあります。

ですが、 EmacsはGUI版を使いたいのでMac上で起動したい のです。

そこで、今までは sshfs を使って、リモートマシン上のディレクトリをMac上にマウントし
Emacsで、 マウントしたディレクトリのファイルを編集する ことで対応していました。

❯ sshfs -o noappledouble,volname=xxx server:/home/xxx/workspace/xxx ~/workspace/server/xxx

この方法の利点としては LSPがMac上で起動できる こと。
これに尽きます。

というか、 trampでsshxで開いたファイルだとLSPがエラーになって起動しない ことを解決する為にこうしていました。
正直逃げました。

🔥 問題発生

最近困ったことが起きました。

マウントしたディレクトリのファイルを開くとLSPがエラーを吐いて、
Emacs上からマウントしたディレクトリが全て見れなくなってしまいました。
そのファイルがあるディレクトリだけでなく、マウントしたディレクトリ全体が見れなくなる。

これはおそらく権限的な問題でNetworkVolumeが見れなくなっているのだろうと思い
いろいろ調査した結果、LSPで起動するプログラムに対して、
個別に Macのセキュリティ設定で Network Volumes に対するアクセス権限を与えていくことで解決することがわかりました。
※問題ないのであれば Full Disk Access を許可してもOK

Emacsを許可する例

ただし、この方法だと、言語毎にLSPのプログラムを個別に権限を与えていくことになり
めちゃくそしんどいです。
特にTypeScriptはいくつかのプロセスが上がるので、 nodeだけを許可してもダメで
nodeから起動される別プロセスのtsファイル毎 に権限を与えていく必要がありました。。。

しかもsymlinkを許可してもダメなので、 複数バージョンのnode.jsを使っている場合はバージョン毎に許可 していく必要があります。

これは耐えられない。

そこでやはり Trampに移行すること を決意しました。

🤔 Trampでの問題点

※つい最近まで lsp-mode を使っていたんですが、lsp-mode の恩恵をあまり受けていなかったので eglot に移行しました。

Trampでファイルを開くと、eglotがLSPのプログラムを見つけられずに起動に失敗していました。

以下、TrampでRustのファイルを開いた時のeglotのログです。

...
[internal] Wed Dec 20 22:41:05 2023:
(:message "Running language server: /bin/sh -c stty raw > /dev/null; rust-analyzer")
[stderr] /bin/sh: 1: rust-analyzer: not found
[internal] Wed Dec 20 22:41:05 2023:
(:message "Connection state changed" :change "exited abnormally with code 127\n")

----------b---y---e---b---y---e----------

eshellを開いて本当に見つからないのか確認してみます。

/sshx:server:~/workspace/opensources/diesel-derive-enum/src $ which rust-analyzer
which: no rust-analyzer in (/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin)

見つからないし、PATHが少ないですね。

sshで直接入って確認してみます。

which rust-analyzer
/home/xxxx/.cargo/bin/rust-analyzer

sshで入った時はPATHが通ってました。

💡解決方法 'tramp-remote-path にPATH通したいディレクトリを入れていく

多分 zshenv あたりでPATH通すことも出来る気がしたんですが、上手く行かなかったので
今回は 'tramp-remote-path を使うことにしました。
Remote programs (TRAMP 2.6.0.29.1 User Manual)

私はleaf.elを使わせて頂いているので、こんな感じにしてみました。

(leaf tramp
  :ensure t
  :custom
  (tramp-default-method . "sshx")
  :init
  (with-eval-after-load "tramp"
    (add-to-list 'tramp-remote-path 'tramp-own-remote-path)
    (add-to-list 'tramp-remote-path "/home/xxxx/.cargo/bin")
    (add-to-list 'tramp-remote-path "/home/xxxx/.nvm/versions/node/v16.18.0/bin")
    (add-to-list 'tramp-remote-path "/home/xxxx/.rbenv/shims")
    )
  )

'tramp-remote-path に対してリモートマシン上のPATH通したい場所を1つずつ追加しています。

Rustで試してみる

先程と同じファイルを開いてみます。

eglotのログを見ると

[internal] Wed Dec 20 23:02:18 2023:
(:message "Running language server: /bin/sh -c stty raw > /dev/null; rust-analyzer")
[client-request] (id:1) Wed Dec 20 23:02:18 2023:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
...

なんか起動してそうな予感ですね。

まとめ

  • TrampでLSPしたいときは 'tramp-remote-path にPATH通したい対象を入れていくとなんとかなる
    • ローカルで起動するよりは、やはりもっさりする(若干ラグがある)
    • たまにエラーになる(おそらくリモートのLSPとの通信エラー)
  • 相変わらずLispはよく分からない
  • でもEmacsをやめられない

Discussion