EmacsでSATySFi Language Serverを使う話

Page content

TL;DR

こういう感じに設定すると動きます!やった!

;; satysfi-modeの設定
(use-package satysfi
  :init
  (setq satysfi-command "/root/.opam/4.11.1/bin/satysfi")
  :mode (("\\.saty$" . satysfi-mode)
         ("\\.satyh$" . satysfi-mode)
         ("\\.satyg$" . satysfi-mode))
  :hook (satysfi-mode . lsp)
  :config

  ;; lsp-modeの設定
  (use-package lsp-mode
        :ensure t
        :commands lsp
        :config
        (add-to-list 'lsp-language-id-configuration '(satysfi-mode . "satysfi-ls"))
        (lsp-register-client
         (make-lsp-client :new-connection (lsp-stdio-connection '("~/.cargo/bin/satysfi-language-server"))
                          :major-modes '(satysfi-mode)
                          :priority 0
                          :activation-fn (lsp-activate-on "satysfi-ls")
                          :server-id 'satysfi-ls)))

  ;; 補間にはcompanyを使う
  (use-package company
    :ensure t
    :init
    ;; company-capf関係でyasnippetも必要な模様
    (use-package yasnippet
      :ensure t
      :init
      (yas-global-mode 1))
    :hook
    (satysfi-mode . company-mode)
    :config
    (setq lsp-completion-provider :capf)
    (setq company-minimum-prefix-length 1))

  ;; error checkにはflycheckを使う
  (use-package flycheck
    :ensure t
    ;; キーバインドの設定。これはなくても良い
    :bind (:map flycheck-mode-map
                ("M-n" . flycheck-next-error))
    :bind (:map flycheck-mode-map
                ("M-p" . flycheck-previous-error))))

はじめに

この記事は SATySFi Advent Calendar 2021 13日目の記事です。

12日目の記事はSATySFiの作者であるgfnさんの縦書きやLTR RTL共存のための型の水準での拡張でした。要するに縦組みと横組みの混在を型レベルで弾きながら従来と同様の記述をできる用にするために多相を使うという話でした。組版言語ならではの型に対する考察は面白いですね。

さて、たまーにSATySFiで文書を書くMasWagです。主に弊サークル (yabaitech.tokyo) の記事を書くのにSATySFiを使っています。エディタは長いことGNU Emacsを使っています。

SATySFi向けのEmacsのメジャーモードにはgfnさん作のsatysfi.elがあります。satysfi.elにはシンタックスハイライト等の必要最低限の機能はありますが、補完やシンタックスエラーの検出などの機能はありません。この記事ではmonaqaさんによって作られたSATySFi-langauge-serverを使うことで、EmacsでもSATySFiコードの補完やシンタックスエラーの検出などを行う設定について書きます。

LSPについて

LSPことはじめ

注意: 筆者はLSPの中身のことは全然良くわかっていないですし、以下は必要最小限の内容しか書いていません。興味のある人は少し古いものですが例えばこのQiitaの記事などを参照してください。

テキストエディタやIDEには各種プログラミング言語やマークアップ言語に対するシンタックスハイライトや補完、オンラインのエラー検出などの機能があります。 これらの機能を実装する際には、例えばC/C++のエラー検出ではclangを使うなどそれぞれの言語における資産を再利用することはありますが、基本的には個々のテキストエディタやIDEに対して実装をする必要があります。 利用者数の多い言語に対してはこの様なアプローチで個々のエディタに対して様々な機能を実装するのも良いかもしれませんが、例えばSATySFiの様に比較的利用者数の少ない言語において個々のエディタ向けのサポートを実装するのは開発リソースの面であまり現実的ではありません。

こういった事情があってかどうかは良くわかりませんが、複数のエディタやIDEに対して補完やエラー検出ツールの部分を共通化しよう、という試みの成果がLanguage server protocol (LSP)やそのプロトコルに基づく各種language serverです。 各種エディタやIDEが適宜LSPを用いて各言語に対するlanguage serverと通信を行なうことで、language serverから補完候補や検出されたエラーなどの情報を取得し、それをユーザに提示することができます。

EmacsでのLSPについて

ではEmacsでLSPを使ってlanguage serverと通信するにはどうするかと云う話ですが、2021年現在lsp-modeelgotという二つのモードが有名です。 GitHubのコミット状況を見るにどちらも十分活発に開発が行われている様です。 個人的には使い分けたことがないので比較はできませんが、redditの記事によるとeglotの方がminimalisticらしいです。 ちょっと調べてみたところlsp-modeの方がユーザが多そうなので、今回はlsp-modeを使います。

lsp-modeのインストールは公式ドキュメントにある通りpackage.elを用いてインストールすることができます。 但し今回はuse-packageを用いて設定を記述するので、use-packageの :ensure t を用いて自動でインストールされる様に設定します。

SATySFi language serverについて

LSPを使って各種エディタと通信する、SATySFi向けのlanguage serverとしてはmonaqaさんによって作られたSATySFi Langauge Serverがあります。READMEによるとLSPの全ての機能を実装している訳ではないものの、かなり多くの機能が既に実装されている様です。

SATySFi Language ServerはRustで実装されており、バイナリでの配布されていないので自分でRustプログラムをコンパイルできる環境が必要です。具体的にはcargoが使える必要があります。Rustの開発環境のインストールについては例えばこのWebページを参照してください。

SATySFi Language Serverのコンパイルは以下の様に行います。ここではgitレポジトリをcloneせずに最新のコードをダウンロードしています。そこまで大きなレポジトリでもないので git clone https://github.com/monaqa/satysfi-language-server.git でcloneしても差し支えないでしょう。

curl -L https://github.com/monaqa/satysfi-language-server/archive/refs/heads/master.zip > /tmp/master.zip
unzip /tmp/master.zip
cd satysfi-language-server-master
cargo install --path .

Emacsの具体的な設定

ここではEmacsのlsp-modeとSATySFi Language Serverを使う様なEmacsの設定について説明します。設定の全容は前述した通りなので詳細に興味のない人は単に上の設定を使ってください。また、微妙に動作が不安定な様な感じがします。より正しい設定をご存知の方がいればご教示いただけると幸いです (lsp-modeをほとんど使ったことがないので)

lsp-mode

まずlsp-modeの設定です。ここでは主に「satysfi-modeではsatysfi-langauge-serverを使う」という設定をします。とはいえ記述する内容はほぼ定型通りなのであまり難しい点もないでしょう。 実はここが一番自信がなかったりします。

(use-package lsp-mode
      :ensure t
      :commands lsp
      :config
      (add-to-list 'lsp-language-id-configuration '(satysfi-mode . "satysfi-ls"))
      (lsp-register-client
       (make-lsp-client :new-connection (lsp-stdio-connection '("~/.cargo/bin/satysfi-language-server"))
                        :major-modes '(satysfi-mode)
                        :priority 0
                        :activation-fn (lsp-activate-on "satysfi-ls")
                        :server-id 'satysfi-ls)))

company

コード補完にはcompanyを使います。ここもやるべきことはあまりないですが、最初にyasnippetを使わないで設定していたところ、補完が上手く行かなかったので追加しています。ここではyasnippetを常に使う様な設定になっていますが、必要なければsatysfi-modeのみで使う様な設定にしても良いと思います。多分 :hook でsatysfi-modeのhookに指定すれば良いと思います。

(use-package company
  :ensure t
  :init
  ;; company-capf関係でyasnippetも必要な模様
  (use-package yasnippet
    :ensure t
    :init
    (yas-global-mode 1))
  :hook
  (satysfi-mode . company-mode)
  :config
  (setq lsp-completion-provider :capf)
  (setq company-minimum-prefix-length 1))

flycheck

エラー検出はflycheckを使います。flymakeを使うこともできる様ですが、個人的には普段flycheckを使っているので今回もflycheckを使います。こちらは本当にキーバインドを設定する以外何もしなくても動きました。デフォルトのもので十分ならキーバインドの設定すら不要です。

(use-package flycheck
  :ensure t
  ;; キーバインドの設定。これはなくても良い
  :bind (:map flycheck-mode-map
              ("M-n" . flycheck-next-error))
  :bind (:map flycheck-mode-map
              ("M-p" . flycheck-previous-error)))

こんな感じで動きました

上記の設定を行ったdockerイメージを作るためのDockerfileを作りました。このGitHubのレポジトリから入手できます。

動かしてみた結果は以下の様になります。Docker上で動かしているからか特に序盤の動作がちょっと怪しいですが、一応何とか動いている様です。Dockerではない環境ではもっと安定して動作します。