メインコンテンツへスキップ

EmacsでのlspをベースとしたPython開発環境

··4 分·
Emacs Lsp Python
Makoto Morinaga
著者
Makoto Morinaga
技術メモ、コーディング、環境構築のための個人ノート。
目次

この記事はEmacs Advent Calendar 2021の2日目の記事です。

昨年のEmacs Advent Calendar 2020の私の記事では、EmacsでのElpyを用いたPython開発環境を紹介しました。

しかし、この一年でlanguage server protocol(lsp)に入門し、Elpyからlspでの開発環境に移行しました。 移行した理由は、language server protocolを触ってみたかったという興味本位です。

この記事では私のEmacsでのlspをベースとしたPython開発環境を紹介します。

昨年の記事と被る部分も多くありますが、この記事だけでEmacsの設定を完結できるように重複部分も記載しておきます。

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

Pythonの開発環境
#

Pythonの開発環境は pyenv, pipx, poetry を用いて構築します。これらを利用することで、プロジェクトごとに開発環境を分離しています。 具体的な構築方法はpyenv、pipx、poetryによるPython開発環境(2020)を記載していますので、よろしければご参照ください。

Emacsでの開発環境
#

EmacsでPythonを快適に書くために、以下のEmacsパッケージを導入しています。

Emacsパッケージ 概要
smartparens 括弧等のペア処理
blacken コードフォーマッタ
highlight-indent-guides インデントの強調表示
rainbow-delimiters 対応する括弧の強調表示
whitespace-mode 空白の可視化
mwim 行頭・行末移動の拡張
py-isort Pythonファイルのimport順の整理
company-mode 入力補完
yasnippet テンプレート挿入
Flycheck 構文チェッカー
lsp-mode lspのクライアントパッケージ
lsp-pyright lsp-modeでpyrightを利用するためのパッケージ

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

なお、私はPackage管理のパッケージとして、[[https://github.com/conao3/leaf.el]を使用していますので、今回はleaf.elの記法で設定していきます。 個人的にはleaf.elは可読性が高く、設定もしやすいので、とてもありがたいです。 各パッケージの設定は用いて記述する。

smartparens
#

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

emacs-lisp
(leaf smartparens
  :ensure t
  :hook (after-init-hook . smartparens-global-strict-mode)
  :require smartparens-config
  :custom ((electric-pair-mode . nil))) ; electirc-pair-modeを無効化

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

text
(hoge huga)
(hoge)

blacken
#

blackenは、外部のblackを利用してアクティブなバッファをコードフォマットに従って整形するパッケージでです。 blackはかなり厳格なコードフォーマットを提供しています。

blackenはblackが必要となりますので、別途インストールします。

blackはpipでインストールできるので、globalのpyhtonにインストールするか、pipxでインストールします。

以下の設定でblackenを有効化します。 私は、blackのデフォルト設定から文字制限や一部の変換強制を緩和しています。

emacs-lisp
(leaf blacken
   :ensure t
   :custom ((blacken-line-length . 119)               ; 1行の流さを119文字まで許可
            (blacken-skip-string-normalization . t))) ; 文字リテラルの「'」を「"」に変更しないように抑制

コードフォーマットにより整形を実施したい時は、 M-x blacken-buffer でアクティブなバッファを整形します。

highlight-indent-guides
#

highlight-indent-guidesは、インデントの位置を強調表示するマイナーモードです。

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

emacs-lisp
(leaf highlight-indent-guides
  :ensure t
  :blackout t
  :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 . ?\|)))

rainbow-delimiters
#

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

emacs-lisp
(leaf rainbow-delimiters
      :ensure t
      :hook
      ((prog-mode-hook       . rainbow-delimiters-mode)))

white-space-mode
#

white-space-modeはa、スペースやタブなどの空白を表示するマイナーモードです。また、不要なスペースやタブの削除も可能です。

設定は@itiut@github様のqiita記事を参考に以下のように設定しています。 C-c W で不要なスペースやタブを明示的に除去するようにしています。

emacs-lisp
(leaf whitespace
      :ensure t
      :commands whitespace-mode
      :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 . '(emacs-lisp-mode shell-script-mode sh-mode python-mode org-mode))
               (global-whitespace-mode . t))

      :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")
      )

mwim
#

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

emacs-lisp
(leaf mwim
      :ensure t
      :bind (("C-a" . mwim-beginning-of-code-or-line)
             ("C-e" . mwim-end-of-code-or-line)))

py-isort
#

py-isortは、外部のisortを利用してアクティブなバッファ内のimport順を整理するパッケージです。

isortはimportするライブラリの順序をPEP8で定義される以下のルールに従ってソートします。

import文 は次の順番でグループ化すべきです:

  1. 標準ライブラリ
  2. サードパーティに関連するもの
  3. ローカルな アプリケーション/ライブラリ に特有のもの

上のグループそれぞれの間には、1行空白を置くべきです。

py-isortはisortが必要となりますので、別途インストールします。

isortは pip でインストールできるので、globalのpyhtonにインストールか、 pipx でインストールする。

Emacsでは、以下の設定でpy-isortを有効化しています。

emacs-lisp
(leaf py-isort :ensure t)

before-save-hookpy-isort-before-save フックする設定を追加することで、バッファの保存時に自動でソートできます。 しかし、私は自分でソートタイミングを指定したいので、設定せずに M-x py-isort-buffer で、明示的にソートしています。

company-mode
#

company-modeは、Emacsの入力補完用のパッケージです。 python以外でも色々な言語等で入力補完ができます。

私は以下の設定で、1文字入力以降から補完を開始して、 C-pC-n, C-i 等で補完候補を選択できるようにしています。

emacs-lisp
(leaf company
  :ensure t
  :leaf-defer nil
  :blackout company-mode
  :bind ((company-active-map
          ("M-n" . nil)
          ("M-p" . nil)
          ("C-s" . company-filter-candidates)
          ("C-n" . company-select-next)
          ("C-p" . company-select-previous)
          ("C-i" . company-complete-selection))
         (company-search-map
          ("C-n" . company-select-next)
          ("C-p" . company-select-previous)))
  :custom ((company-tooltip-limit         . 12)
           (company-idle-delay            . 0) ;; 補完の遅延なし
           (company-minimum-prefix-length . 1) ;; 1文字から補完開始
           (company-transformers          . '(company-sort-by-occurrence))
           (global-company-mode           . t)
           (company-selection-wrap-around . t))

yasnippet
#

yasnippetは、テンプレートの挿入を提供しているパッケージです。 こちらもPython以外の言語でも使えます。 Pythonのテンプレートは、yasnippet-snippetsのpython-modeをご参照ください。 例えば、 ifm と入力して C-i を押下すると、 if __name__ == '__main__': が展開されます。

以下の設定では、compay-modeの補完対象として、yasnippetのテンプレートも表示されるようにしています。company-modeと組み合わせることで一つのUIで入力補完とテンプレート挿入ができるようになり、とても便利です。

emacs-lisp
(leaf yasnippet
      :ensure t
      :blackout yas-minor-mode
      :custom ((yas-indent-line . 'fixed)
               (yas-global-mode . t)
               )
      :bind ((yas-keymap
              ("<tab>" . nil))            ; conflict with company
             (yas-minor-mode-map
              ("C-c y i" . yas-insert-snippet)
              ("C-c y n" . yas-new-snippet)
              ("C-c y v" . yas-visit-snippet-file)
              ("C-c y l" . yas-describe-tables)
              ("C-c y g" . yas-reload-all)))
      :config
      (leaf yasnippet-snippets :ensure t)
      (leaf yatemplate
            :ensure t
            :config
            (yatemplate-fill-alist))
      (defvar company-mode/enable-yas t
        "Enable yasnippet for all backends.")
      (defun company-mode/backend-with-yas (backend)
        (if (or (not company-mode/enable-yas) (and (listp backend) (member 'company-yasnippet backend)))
            backend
          (append (if (consp backend) backend (list backend))
                  '(:with company-yasnippet))))
      (defun set-yas-as-company-backend ()
        (setq company-backends (mapcar #'company-mode/backend-with-yas company-backends))
        )
      :hook
      ((company-mode-hook . set-yas-as-company-backend))
      )

Flycheck
#

Flycheckは、構文チェッカーです。

emacs-lisp
(leaf flycheck
      :ensure t
      :hook (prog-mode-hook . flycheck-mode)
      :custom ((flycheck-display-errors-delay . 0.3)
               (flycheck-indication-mode . 'left-margin)) ;terminalで使うので、fringeではなくmarginに警告を表示
      :config (add-hook 'flycheck-mode-hook #'flycheck-set-indication-mode) ; flycheckのみでmarginを使用
      (leaf flycheck-inline
            :ensure t
            :hook (flycheck-mode-hook . flycheck-inline-mode)))

また、Flycheckの拡張として、以下を導入しています。

  • flycheck-inline: エラーをエコーエリアではなく、インライン内に表示

lsp-mode
#

lsp-modeは、Emacsでlspを利用できるようにするためのパッケージです。 lsp-modeを導入することで、これまで設定してきた他のパッケージ(companyやflycheck等)を上手く統合してEmacsがIDEのように動作します。

lspを利用するためのパッケージはいくつかありますが、私はlsp-modeとlsp-uiを利用しています。 lsp-modeではlsp自体の設定、lsp-uiでlsp-modeにおけるuiの設定ができます。

細かく設定できるので、公式サイトを参考に設定してみてください。 参考までに、私の設定を以下に記載しておきます。

emacs-lisp
(leaf lsp-mode
      :ensure t
      :commands (lsp lsp-deferred)
      :config
      :custom ((lsp-keymap-prefix                  . "C-c l")
               (lsp-log-io                         . t)
               (lsp-keep-workspace-alive           . nil)
               (lsp-document-sync-method           . 2)
               (lsp-response-timeout               . 5)
               (lsp-enable-file-watchers           . nil))
      :hook (lsp-mode-hook . lsp-headerline-breadcrumb-mode)
      :init (leaf lsp-ui
                  :ensure t
                  :after lsp-mode
                  :custom ((lsp-ui-doc-enable            . t)
                           (lsp-ui-doc-position          . 'at-point)
                           (lsp-ui-doc-header            . t)
                           (lsp-ui-doc-include-signature . t)
                           (lsp-ui-doc-max-width         . 150)
                           (lsp-ui-doc-max-height        . 30)
                           (lsp-ui-doc-use-childframe    . nil)
                           (lsp-ui-doc-use-webkit        . nil)
                           (lsp-ui-peek-enable           . t)
                           (lsp-ui-peek-peek-height      . 20)
                           (lsp-ui-peek-list-width       . 50))
                  :bind ((lsp-ui-mode-map ([remap xref-find-definitions] .
                                           lsp-ui-peek-find-definitions)
                                          ([remap xref-find-references] .
                                           lsp-ui-peek-find-references))
                         (lsp-mode-map ("C-c s" . lsp-ui-sideline-mode)
                                       ("C-c d" . lsp-ui-doc-mode)))
                  :hook ((lsp-mode-hook . lsp-ui-mode))))

lsp-pyright
#

lspを利用するためには、肝心なlanguage serverが必要となります。 Python向けのlanguage serverの選択肢は、有名所として以下のものがあります。

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

以前使っていた開発環境のElpyはJediベースなので、今回は毛色が異なるpyrightを利用します。 また、lsp-pyright(lsp-modeでpyrightを利用できるようにするパッケージ)もインストールします。

pyrightのインストール
#

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

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

Terminal
npm install -g pyright

lsp-pyrightの設定
#

以下の設定より、python-mode時にlsp-pyrightを有効化します。

emacs-lisp
(leaf lsp-pyright
      :ensure t
      :hook (python-mode-hook . (lambda ()
                                  (require 'lsp-pyright)
                                  (lsp-deferred))))

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

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

  • テンプレート

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

  • 自動補完

    lspがcompany-modeのバックエンドとして稼動しています。 company-modeの設定で1文字から補完を開始し、yasnippetのテンプレートも補完対象としています。

  • 定義ジャンプ

    • M-. (xref-find-definitions)

      カーソル上のシンボル(クラス名や関数名等)の定義先に移動します。

    • M-? (xref-find-references)

      カーソル上のシンボルを呼び出している箇所の候補を表示して、選択後に移動します。

    • M-, (xref-pop-marker-stack)

      =M-.=や=M-?=で移動した先から移動前の場所に戻ります。

  • 構文チェック

    flycheckで動的に構文がチェックされます。

  • リファクタリング

    • M-x py-isort-buffer

      アクティブなバッファ内のimport順を整理します。

    • M-x blacken-buffer

      アクティブなバッファ内にblackのコードフォーマットを適用します。

    • C-c W(whitespace-cleanup)

      アクティブなバッファ内の不要なスペースやタブを明示的に除去します。

終わりに
#

これで一通りのEmacsでのlspをベースとしたPython開発環境を構築できました。

今回の設定は、他の統合開発環境ならば標準機能であったり設定が簡単そうだからそっちを使ったほうが良いかなと思うこともあります。

しかし、それでも私がEmacsを使う理由は、自由にカスタマイズできるEmacsが大好きだからです。

今回であれば、自分の好きなように動作する統合開発環境が作れました。 Emacsではelispが読めれば、自分が改善したいところはかなり改善できますし、今回紹介しましたように色々な方が便利なパッケージを公開してくれています。 カスタマイズが止められない、それがEmacs。

すべてのEmacsユーザに幸あれ!

謝辞
#

leaf.elや今回紹介させていただいたパッケージ及び紹介できなったパッケージの作者様、設定ファイルを公開してくださっている方々、いつもありがとうございます。

今回の設定は、色々な方々の設定ファイルを参考にさせていただいている箇所もあるのですが、出典を記録していないため、参考元のサイトが不明となっており、申し訳ございません。

不都合等ありましたら、コメント等でご連絡いただけますと幸いです。

関連記事

EmacsでのElpyをベースとしたPython開発環境
··3 分
Elpy Python Emacs
Elpy RPC Process and Python Version
··1 分
Elpy Python Emacs
Pyenv、pipx、poetryによるPython開発環境(2020)
··1 分
Macos Linux Python