lsp-mode 與 Python virtualenv 的整合

Emacs 的 lsp-mode 是利用 Language Server Protocol (LSP) 來支援程式語言的編輯,LSP 一開始是為了 VS Code 開發的,可以把對語言的編輯支援與 IDE/editor 脫勾,常常是孤兒的 Emacs 就比較可以跟上時代的發展。但,慣用的 palantir/python-language-server (pyls) 在搭配 Python 幾乎一定會使用的 virtualenv 時,還是會有認不出虛擬環境裡模組的問題,如果再加上 pyenv 之類的,就還需要考慮不同 Python 版本,事情又更麻煩。以下是幾種解決方案:

每個虛擬環境都裝一套

這需要搭配 add-dir-local-variable 使用,在專案目錄底下新增一個 dir local variable pyvenv-activate 指向虛擬環境的絕對路徑,然後只要在這個環境下安裝 pyls 就可以了。這是個非常穩當的方法,所有基於虛擬環境的應用例如 pipenv 之類的也都適用。

缺點是,整套 pyls 也佔不少空間,每個專案底下都做一次很麻煩,要升級還得各自升級。

自己寫個 pyls wrapper

這算是前面方法的變體。先在自己環境裡裝一套 pyls,例如我習慣裝在 homedir 底下,那就是

1
python3 -m pip install --user 'python-language-server[all]'

這樣只要在設定環境時,用 virtualenv --system-site-packages 或者像我是用 pipenv 那就是 pipenv install --site-packages 就可以一併帶入到虛擬環境裡面。但這方法有個問題:帶入的模組是不會在虛擬環境裡新增執行檔的,也就是說 Emacs 執行 pyls 這命令時,會用到虛擬環境「外面」的執行檔,這就會出錯了。解決方法是要繞一圈,在虛擬環境裡以 python -m pyls 的方式執行。

但問題又來了:如果直接把 lsp-pyls-server-command 這變數改掉,lsp-pyls.el 的寫法是把整個值當成命令名稱來用,但系統裡面當然不該有個名稱叫做 python -m pyls 的執行檔。所以可以自己寫個 pyls.sh 裡面就寫 python -m pyls 然後把 lsp-pyls-server-command 改成 pyls.sh 就行了。這方法一樣要仰賴在每個專案正確設定 pyvenv-activate. 而我是慣用 pipenv,所以只要寫成這樣

1
2
3
4
5
6
7
8
9
#!/bin/bash

set -e

if pipenv --venv &> /dev/null; then
exec pipenv run python -m pylsp "$@"
else
exec python3 -m pylsp "$@"
fi

如此不特別設定 pyvenv-activate 也一樣會動。