Eglot でのPython開発環境

2023年12月10日 2023年12月11日

この記事は Emacs Advent Calendar 2023 の11日目の記事です。

こんにちは。 一昨年の Emacs Advent Calendar 2021 の私の記事では、Emacsで lsp-mode を用いたPython開発環境を紹介しました。

しかし、この2年で開発環境に変化がありましたので、2023年度版ということで紹介していきます。 大きな違いは lsp-mode から Eglot に変わったことと Ruff を使い始めたことです。 一昨年の記事と被る部分も多くありますが、この記事だけで Emacs の設定を完結できるように重複部分も記載しておきます。

少しでも読んだ方の参考になると嬉しいです。

Python自体の開発環境

まず、Python自体の開発環境は、rtxpipxpoetry を用いて構築します。これらによって、プロジェクトごとに開発環境を分離しています。 特に rtx は 作業プロジェクトのみに自動的に環境変数を設定(direnvと同様の設定)ができるので、大変重宝しています。

Emacs での開発環境

では、本題の Emacs での開発環境です。 Python を Emacs で快適に書くために、以下のパッケージを導入してます。

Emacsパッケージ概要
smartparens括弧等のペアの処理
rainbow-delimiters対応する括弧の強調表示
highlight-indent-guidesインデントの強調表示
whitespace-mode空白の可視化
treesit-autoTree-sitter を便利に使うためのパッケージ
mwim行頭・行末移動の拡張
Tempelテンプレートの挿入
Corfu入力補完用パッケージ
nerd-iconsNerd Icons を扱うためのパッケージ
Eglotlsp を利用するためのクライアントパッケージ
Flymake構文チェッカー
flymake-ruffRuff の解析結果を flymake へ受け渡し
emacs-reformatterカレントバッファの reformatter
ruff-fix自分用に作ったRuffのカレントバッファの linter

では、具体的に各パッケージの説明と設定を紹介します。

smartparens

smartparens は、括弧等のペアを処理するためのマイナーモードです。 以下のようにデフォルト設定を有効化すると、「モードに合った括弧の自動補完」や「選択範囲を括弧等のペアでラップ」等ができるようになります。 色々機能があるので、詳細は公式サイトをご覧ください。

(use-package smartparens
  :ensure t
  :delight
  :hook (after-init-hook . smartparens-global-strict-mode)
  :custom
  (electric-pair-mode nil)
  :config
  (require 'smartparens-config))

私は、上記でstrictモードを有効化して、括弧等のペアが崩れないように強制しています。 例えば、strictモードを有効にすると、以下のように hoge の末尾で C-k を押下した場合、hoge の末尾以降がすべて削除されずに ) が残り、括弧のペアを維持してくれます。便利!

(hoge huga)(hoge)

rainbow-delimiters

rainbow-delimiters は、対応する括弧を強調表示するマイナーモードです。 以下の設定をすることで、対応する括弧を色分けして表示できますので、対応関係を視認しやすくなります。

(use-package rainbow-delimiters
  :ensure t
  :hook ((prog-mode-hook . rainbow-delimiters-mode)))

highlight-indent-guides

highlight-indent-guides は、インデントの位置を強調表示するマイナーモードです。 どのように強調表示されるかは公式サイトのスクリーンショットをご覧ください。 特に Python は indent位置が重要になりますので、重宝しています。

(use-package highlight-indent-guides
  :ensure t
  :delight
  :hook ((prog-mode-hook yaml-mode-hook) . highlight-indent-guides-mode)
  :custom
  (highlight-indent-guides-method  'character)
  (highlight-indent-guides-auto-enabled t)
  (highlight-indent-guides-responsive t)
  (highlight-indent-guides-character ?\|))

whitespace-mode

whitespace-mode は、スペースやタブなどの空白を表示するマイナーモードです。また、不要なスペースやタブの削除も可能です。 設定は@itiut@github様のqiita記事を参考に、以下のように設定しています。 以下では、C-c W の押下で不要なスペースやタブを明示的に除去するとともに、特定のモード(dired-mode, tar-mode, magit-mode)以外は保存時に自動的に不要なスペースやタブを除去しています。

(use-package whitespace
  :ensure t
  :demand t
  :delight
  :bind ("C-c W" . whitespace-cleanup)
  :custom
  (whitespace-style '(face trailing tabs spaces empty space-mark tab-mark))
  (whitespace-display-mappings '((space-mark ?\u3000 [?\u25a1])
                                 (tab-mark ?\t [?\u00BB ?\t]
                                           [?\\ ?\t])))
  (whitespace-space-regexp  "\\(\u3000+\\)")
  (whitespace-global-modes  '(not dired-mode tar-mode magit-mode))
  (global-whitespace-mode t)
  (whitespace-action '(auto-cleanup))
  :config
  (set-face-attribute 'whitespace-trailing nil
                      :background "Black"
                      :foreground "DeepPink"
                      :underline t)
  (set-face-attribute 'whitespace-tab nil
                      :background "Black"
                      :foreground "LightSkyBlue"
                      :underline t)
  (set-face-attribute 'whitespace-space nil
                      :background "Black"
                      :foreground "GreenYellow"
                      :weight 'bold)
  (set-face-attribute 'whitespace-empty nil
                      :background "Black"))

treesit-auto

Emacs 29から標準で Tree-sitter を利用してシンタックスハイライトができるようになりました。 しかし、Tree-sitter を有効するためには、言語に対応するパーサのインストール及び従来の python-mode ではなく ptyhon-ts-mode への変更が必要になります。

treesit-auto は Tree-sitter のメジャーモードを統合してくれます。具体的には、Tree-sitterが利用可能なモードの場合にパーサのインストール及びモードへの切り替えを行ってくれます。

(use-package treesit-auto
  :ensure t
  :custom
  (treesit-font-lock-level 4)
  :config
  (setq treesit-auto-install 'prompt)
  (global-treesit-auto-mode))

mwim

mwim は、コードの先頭・末尾への移動と行頭・行末への移動を行うコマンドを提供します。 実際の動作は公式サイトのスクリーンショットをご参照ください。 私は C-aC-e に割り当てて、行頭・行末への移動を拡張しています。

(use-package mwim
  :ensure t
  :bind (("C-a" . mwim-beginning-of-code-or-line)
         ("C-e" . mwim-end-of-code-or-line)))

Tempel

Tempel は、テンプレートの挿入を提供しているパッケージです。 テンプレートは各自で設定しますが、tempel-collection というテンプレートのコレクションもあります。 例えば、私は ifm から if __name__ == '__main__': が展開されるようにしています。

テンプレートの候補は後述する Corfu で補完対象として表示しています。

(use-package tempel
  :ensure t
  :init
  (defun tempel-setup-capf ()
    (setq-local completion-at-point-functions
                (cons #'tempel-complete
                      completion-at-point-functions)))
  (add-hook 'prog-mode-hook 'tempel-setup-capf)
  (add-hook 'text-mode-hook 'tempel-setup-capf)
  (add-hook 'org-mode-hook 'tempel-setup-capf))

Corfu

Corfu は、Emacs の入力補完用のパッケージです。

Corfu 単体でも入力補完が可能ですが、私は CapeTempel で補完候補とテンプレートを追加しています。また、Corfu は GUI版Emacsのみで使用できるので、私はターミナル上でも利用できるように corfu-terminal も使用しています。

(use-package corfu-terminal
  :ensure t
  :if (not (display-graphic-p))
  :config
  (corfu-terminal-mode +1))

(use-package corfu
  :ensure t
  :custom ((corfu-auto t)
           (corfu-auto-prefix 1)
           (corfu-auto-delay 0)
           (corfu-cycle t))
  :init
  (global-corfu-mode)
  (corfu-popupinfo-mode))

(use-package cape
  :ensure t
  :init
  ;; Add `completion-at-point-functions', used by `completion-at-point'.
  (add-to-list 'completion-at-point-functions #'cape-file)
  (add-to-list 'completion-at-point-functions #'cape-dabbrev)
  ;; (add-to-list 'completion-at-point-functions #'cape-history)
  (add-to-list 'completion-at-point-functions #'cape-keyword)
  ;; (add-to-list 'completion-at-point-functions #'cape-tex)
  ;; (add-to-list 'completion-at-point-functions #'cape-sgml)
  ;; (add-to-list 'completion-at-point-functions #'cape-rfc1345)
  ;; (add-to-list 'completion-at-point-functions #'cape-abbrev)
  ;; (add-to-list 'completion-at-point-functions #'cape-ispell)
  ;; (add-to-list 'completion-at-point-functions #'cape-dict)
  ;; (add-to-list 'completion-at-point-functions #'cape-symbol)
  ;; (add-to-list 'completion-at-point-functions #'cape-line)
)

nerd-icons

Emacs でアイコンを使う場合は、all-the-icons パッケージが有名です。しかし、GUI版Emacsでしか利用できません。

そこで、CUI版Emacsでもアイコンを使うために、Nerd Fonts のアイコンフォント(以下、nerd icons と記載)を使います。 まず、Nerd Fonts が含まれているフォント(PlemolJPCica)を インストール後に、Emacs でフォントを設定します。その後 nerd icons を呼び出してあげるだけです。

nerd icons を利用するためのパッケージとして、nerd-icons.el があります。GUI版でも使えますので、CUI版及びGUI版Emacsで統一的にアイコンを利用することができます。

以下の設定では、 Corfu の補完候補でも nerd icons を表示できるようにしています。

;; PlemolJP をデフォルトフォントに設定
(when (display-graphic-p)
  (if (eq system-type 'darwin)
      (add-to-list 'default-frame-alist '(font . "PlemolJP Console NF-18"))
    (add-to-list 'default-frame-alist '(font . "PlemolJP Console NF-21"))))

;; nerd icon を扱えるように
(use-package nerd-icons :ensure t)

;; dired で nerd icon を表示
(use-package nerd-icons-dired
  :ensure t
  :hook (dired-mode-hook . nerd-icons-dired-mode))

;; completion で nerd icon を表示
(use-package nerd-icons-completion
  :ensure t
  :after marginalia
  :config
  (nerd-icons-completion-mode)
  :hook (marginalia-mode-hook . #'nerd-icons-completion-marginalia-setup))

;; corfu で nerd icon を表示
(use-package nerd-icons-corfu
  :ensure t
  :after corfu
  :config
  (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))

Eglot

Eglot は、Emacsのlspクライアントの一つです。Eglot は Emacs 29 からビルトインされています。

Eglot とこれまで設定してきた他のパッケージを上手く統合すると、Emacs が IDE のように動作します。 以下設定では、Eglot 起動時に Tempel のテンプレートと Eglot の補完候補を Corfu で同時に表示するようにしています。

(use-package eglot
  :bind (nil
         :map eglot-mode-map
         ("C-c a" . eglot-code-actions))
  :config
  (defun my/eglot-capf ()
    (setq-local completion-at-point-functions
                (list (cape-super-capf
                       #'tempel-complete
                       #'eglot-completion-at-point)
                      #'cape-keyword
                      #'cape-dabbrev
                      #'cape-file)
                ))
  (add-hook 'eglot-managed-mode-hook #'my/eglot-capf))

Flymake

Flymake は Emacs にビルトインされている構文チェッカーです。今回は Eglot での解析結果が渡されています。併せて、Flymake の拡張として、以下を導入しています。

(use-package flymake
  :ensure t
  :bind (nil
         :map flymake-mode-map
         ("C-c C-p" . flymake-goto-prev-error)
         ("C-c C-n" . flymake-goto-next-error))
  :config
  (set-face-background 'flymake-errline "red4")
  (set-face-background 'flymake-warnline "DarkOrange"))

(use-package flymake-diagnostic-at-point
  :ensure t
  :after flymake
  :config
  (add-hook 'flymake-mode-hook #'flymake-diagnostic-at-point-mode)
  (remove-hook 'flymake-diagnostic-functions 'flymake-proc-legacy-flymake))

Ruff

Ruff は Rust で書かれた高速なPython用の linter & code formatter です。 Ruff の設定に関しては、別記事にまとめていますので、Emacs から Ruff を使う を参照いただければと思います。

上記の記事では、Ruff を Emacs で使うために、以下のパッケージを導入しています。

  • flymake-ruff: Ruff の解析結果(Linter の結果)を flymake に渡す
  • emacs-reformatter: code formatter を カレントバッファに適用する
  • ruff-fix: Ruff の Linter の修正をカレントバッファに適用する

ここまでで、 Emacsパッケージの設定は完了です。

pyright

最後に、lsp(Language Server Protocol) を利用するための language server です。 Python向けの language server の選択肢は、有名所として以下のものがあります。

  • pyright(インストールに nodejs が必要)
  • python-lsp-server(Jedi が必要)

私は以前 Jedi を使用していたのですが、今は毛色が異なる pyright を利用します。

pyrightのインストール

まず、nodejs が必要となるので、nodejs をインストールしておきます。 nodejs のインストール方法は色々な記事があると思いますので、そちらを参照いただければと思います。

nodejs がインストールされていれば、以下コマンドで pyright をインストールします。

Terminal
npm install -g pyright

pyright がインストールされると、 elgot で pythonファイルを編集時に自動的に pyright に接続してくれます。

Emacs での Python の設定は以下の通りにしています。

(use-package python
  :custom (python-indent-guess-indent-offset-verbose . nil)
  :hook (python-ts-mode-hook . eglot-ensure))

このpython開発環境でできること

以上で、必要なパッケージのインストールと各種設定が完了しました。 ここまでインストールした各パッケージには色々な機能がありますが、私がよく使う機能/コマンドを少しだけ紹介します。

  • テンプレート挿入

    Tempel でテンプレートを挿入しています。 if __name__ == '__main__' はよく忘れるので、ifm でテンプレート挿入でき便利です。

  • 入力補完 Corfu が Eglot からの補完候補を表示します。 1文字から補完を開始し、Tempel のテンプレートも補完対象としています。

  • 定義ジャンプ

    • M-. (xref-find-definitions) カーソル上のシンボル(クラス名や関数名等)の定義先に移動します。
    • M-? (xref-find-references) カーソル上のシンボルを呼び出している箇所の候補を表示して、選択後に移動します。
    • M-, (xref-pop-marker-stack) M-.M-? で移動した先から移動前の場所に戻ります。
  • 構文チェック Flymake を通して、pyright と ruff で動的に構文がチェックします。

  • リファクタリング 保存時に以下を自動で行います。

    • アクティブなバッファのimport順を整理します。
    • アクティブなバッファに Ruff の linter と code formatter を適用します。
    • アクティブなバッファの不要なスペースやタブを除去します。

終わりに

ここまで、読んでいただき、ありがとうございます。 これで一通りの Eglot でのPython開発環境を構築できました。 今回の設定を含む私のEmacsの設定がありますので、よろしければ参考にしてください。

それでは、すべてのEmacsユーザに幸あれ!

謝辞

今回紹介させていただいたパッケージ及び紹介できなったパッケージの作者様、設定ファイルを公開してくださっている方々、いつもありがとうございます。 不都合等ありましたら、コメント等でご連絡いただけますと幸いです。

Posted by mako
関連記事
コメント
...