Daydreaming in Brookline, MA

emacsのPython環境をeglotにしてみる

1 はじめに

一昨年2020年にPythonに(何度めかの)入門をして以来、エディタというかIDE相当の開発環境としてemacs + elpyを使ってきましたが、リモートサーバー上のファイル等を扱う tramp モードに elpy が対応していないことを知り、対応しているらしい eglot を試してみることにしました。

2 LSP

Language Server Protocol (LSP) はマイクロソフトが決めて公開しているプロトコルです。PythonやJavaといった言語ごとのLSPサーバーと、それらの機能を利用するemacs(上で動くeglot)のようなクライアントに分かれていて、それぞれ独立して開発が可能です。

プロトコル部分が標準化されているため、eglotのようなLSPクライアントは、既存の多くのLSPサーバーを使うことができます。elpyのように、何か一つの言語に特化した開発環境を一から実装することは時代遅れになってきたのですね。

3 eglot

emacs用のlspクライアントとしては、 lsp-modeeglot がよく使われているようです。lsp-modeの方が本格的で人気も高いように見えますが、 eglot は設定が簡単で公式ページに「tramp でも動く」と明記してあるので、まずは極力リスクを避けてeglotに入門してみたいと思います。emacsでは動くはずのものが動かないことは日常茶飯事なので(単に私に技術が無いだけとも言う)。

4 設定

4.1 lsp server

Python用のlspサーバーとしては python-language-server と python-lsp-server というよく似た名前のものがありますが、前者はすでにメンテナンスされていないようなので、後者を入れます。

pip install 'python-lsp-server[all]'

簡単に入りました。

4.2 eglot

~/.emacs.d/init.el に以下を書きました。もしまだ use-package を使っていないようなら、ぜひとも使ってみてください。パッケージのインストールを自動化してくれ、とても便利です。私の環境ではなぜか自動インストールが失敗し、 M-x list-packages から手動インストールすることがよくありますが。。

(use-package eglot)
(add-to-list 'eglot-server-programs
               '(python-mode "pylsp"))
(use-package python-mode
  :ensure nil
  :hook
  (python-mode . eglot-ensure))

これで最低限の設定が完了です。試しにローカルの .py ファイルを開いてみてください。

4.2.1 tramp-modeでの注意点

私の場合、ローカルではうまくいくのですが tramp でリモートの .py ファイルを開こうとすると pylsp が見つからないというエラーに悩まされました。試行錯誤の結果、以下の行をinit.elに入れて解決しました。

(add-to-list 'tramp-remote-path 'tramp-own-remote-path)

4.3 その他のパッケージ

他に、以下のパッケージを追加しました。

  • company: オートコンプリート。pythonファイル以外にも適用する
  • highlight-indent-guides: インデントをよりビジュアル的にわかりやすくするパッケージ
  • black: 整形ツール。セーブ時に規定フォーマットを強制する
  • flycheck: 動的に構文チェックしてくれるツール。 flycheck-inline はエラーをインラインで表示させる
(use-package company
  :init
  (global-company-mode)
  (setq company-idle-delay 0.3)
  (setq company-minimum-prefix-length 1)
  (setq company-transformers '(company-sort-by-occurrence))
  :bind
  (:map company-active-map
              ("C-n". company-select-next)
              ("C-p". company-select-previous)
              ("M-<". company-select-first)
              ("M->". company-select-last)))

(use-package highlight-indent-guides
  :config
  (add-hook 'prog-mode-hook 'highlight-indent-guides-mode))

;; black
(use-package blacken
  :config
  (add-hook 'python-mode-hook 'blacken-mode))

;; Flycheck
(use-package flycheck
  :init
  (add-hook 'after-init-hook 'global-flycheck-mode))
(use-package flycheck-inline
  :init
  (with-eval-after-load 'flycheck
    (add-hook 'flycheck-mode-hook #'flycheck-inline-mode)))

5 おまけ

elpyがtrampに 対応していない らしいことは、実際にやってみてわかりました。