二 月
7
水曜日
2018

SpacemacsでClojure(基本編)

Spacemacs Clojure を書くためのメモ。
CIDER を使いたくて『Spacemacs入門』した件の続き。
まずはSpacemacsにClojure layerを導入し[ evil-lisp-state ] (https://github.com/syl20bnr/evil-lisp-state)によるS式操作とCIDERの基本的な使い方を学ぶ。


Clojure layerの追加

Clojure layerを導入する。

Spacemacsを起動。
SPC f e d でドットファイルを編集。
dotspacemacs/layers関数内dotspacemacs-configuration-layersリストにclojureを追加。

SPC f e R でコンフィグレーションを反映しClojure layerを有効化。

ちなみに レイヤーの構成パッケージ を確認すると以下のようになっている。

パッケージ名 説明
cider Clojure(Script)のためインタラクティブプログラミング環境
cidier-eval-sexp-fu 評価したS式が光るちょっとしたギミック
clj-refactor Clojure用リファクタリング関数集
clojure-mode Clojure(Script)編集のためのモード
clojure-snippets Clojure用のスニペット集
company 入力補完フレームワーク
ggtags GNU Globalのフロントエンド
helm-gtags GNU Globalのhelmインタフェース
popwin バッファのポップアップ表示
smartparens 括弧の編集支援

S式の操作

Spacemacsでは標準でevil-lisp-stateによるLispコードのナビゲーション、編集支援機能が提供されておりClojure layerの有無に関係なく利用できる。
以下はSpacemacsドキュメント Editing Lisp codeから一部抜粋。

ナビゲーション

対応する括弧へ移動
% or SPC k %
S式のはじめに移動
SPC k 0
S式の終わりに移動
SPC k $
次の右括弧へ移動
SPC k j
前の左括弧へ移動
SPC k k
次のシンボルへ移動
SPC k l
次のS式へ移動
SPC k L

追加

前にS式を挿入
SPC k (
後にS式を挿入
SPC k )

削除

次のシンボルを削除
SPC k d s
前のシンボルを削除

SPC k D s

次のS式の削除
SPC k d x
前のS式の削除
SPC k D x

その他

S式を括弧で囲う
SPC k w
S式から括弧を剥がす
SPC k W
S式をコピー
SPC k y
evil-lisp-stateへ遷移
SPC k .
evil-lisp-stateへ遷移すると以降のコマンド SPC k を省略し連続的に操作ができる。
ESC で復帰。

S式の整形

SPC m f l でclojure-modeによるフォーム要素の垂直方向位置揃えが行われる。

(def a-map
  {:foo 1
   :foo-bar 2
   :foo-bar-hoge-fuga 3})
(def a-map
  {:foo               1
   :foo-bar           2
   :foo-bar-hoge-fuga 3})

map以外にも letcond 等のフォームも揃えてくれるが対象は決まっておりそれ以外の関数では整列されない。


また *SPC* *m* *f* *b* or *SPC* *m* *=* で 後述のCIDERによるREPLを介したコード整形が行われる。

CIDER

CIDERはClojureのインタラクティブな開発を支援するEmacs拡張。
nREPLサーバと接続しコードのナビゲーション、実行、デバッグ、テストやドキュメントの参照などの機能を提供する。

これら機能は SpacemacsからLeiningen / Boot プロジェクトのソースを開き、nREPLサーバへ接続することで利用可能となる。

CIDER用 nREPL middleware

CIDERが提供する多くの機能はCIDER用 nREPL middlewareに依存している。
CIDERから新規にnREPLサーバを起動する際は自動的にCIDER用 nREPL middlewareが組み込まれるが、シェル等からlein replタスクなどでnREPLサーバを起動する場合は個別の設定が必要となる。

例えばLeiningenであれば、以下のように ~/.lein/profiles.clj に CIDER用 nREPLミドルウエアのプラグイン定義が必要となる。

{:repl {:plugins [[cider/cider-nrepl "0.16.0"]]}}

evil-modeとCIDERのキーバインドについて

CIDERではネームスペースブラウザやマクロ展開など機能に応じたメジャー/マイナーモードでキーバインドが定義されているが、evil-modeなSpacemacsからはそのまま利用することができない。

例えば SPC m h n で ネームスペース一覧表示し、ネームスペース選択後の関数一覧で可能なキー操作はInsertステート側へバインドされるためNormalステートから利用することはできない。
この例では関数一覧で関数名にカーソルを当て d でドキュメントが表示されない。i d と一度Insertステートにしてからキーを押す必要がある。

対応方法は以下の3つ。

  1. C-z で一時的にevil-modeを解除しemacs-modeを利用する
  2. 一度Insertステートにしてからキー入力する(ことを受け入れる)
  3. Normalモードで動くようにキーバインドを追加する

Emacs力に乏しい自分にとってevil-modeの解除は死と等しい為1は無し。
基本は2のまま利用しどうしても使いにくいところは3で対応することにした。

例えばネームスペースブラウザをNormalステートのまま利用する場合は以下ように HOME/.spacemacs.d/init.eldotspacemacs/user-config で明示的にNormalステートでのキーを再定義することで対応する。

(with-eval-after-load 'cider-mode
    (evil-define-key 'normal cider-browse-ns-mode-map "d" #'cider-browse-ns-doc-at-point)
    (evil-define-key 'normal cider-browse-ns-mode-map "s" #'cider-browse-ns-find-at-point)
    (evil-define-key 'normal cider-browse-ns-mode-map (kbd "RET") #'cider-browse-ns-operate-at-point)
    (evil-define-key 'normal cider-browse-ns-mode-map "^" #'cider-browse-ns-all))

evil-modeとS式評価

CIDERでは SPC m e e 等でカーソル位置手前のS式を評価する事ができるが evil-mode にとってこの カーソル位置の手前 というのが曲者。

S式が行末にある場合、emacs-modeであれば行末の右括弧の右隣である改行部分にカーソルを置けるので問題無いが evil-modeのNormalステートの場合は行末の右括弧までしかカーソル移動できない。

対応方法は以下の3つ。

  1. evil-move-cursor-backまたはevil-move-beyond-eolを有効にし改行部分までカーソルを移動できるようにする
  2. 行末でInsertステートにして C-x C-e
  3. cider-eval-last-sexp関数等のアドバイスを追加してカーソル位置が右括弧の上でもS式評価できるようにする

1は以下のように HOME/.spacemacs.d/init.eldotspacemacs/user-config で設定することで evil-modeのNormalステートでも改行までカーソル移動できるようにする方法。

(setq evil-move-cursor-back nil) ;; または(setq evil-move-beyond-eol t)

確かに改行までカーソルを移動できるようになるが、行末で文字を削除した時の挙動が若干従来と異なるため見送り。

2は毎回Insertステートにするのが面倒な上、Leaderキーを起点としたキーバインドが使えなくなってしまうためこれも見送り。

今回は以下のように dotspacemacs/user-configcider-eval-last-sexp関数等のアドバイスを追加する方法で対応した。

(with-eval-after-load 'cider-mode
    (let ((f (lambda (command &rest args)
               (if (or (evil-normal-state-p) (evil-motion-state-p))
                   (save-excursion
                     (unless (or (eobp) (eolp)) (forward-char))
                     (apply command args))
                 (apply command args)))))
      (advice-add 'cider-eval-last-sexp :around f)
      (advice-add 'cider-eval-last-sexp-and-replace :around f)
      (with-eval-after-load 'cider-eval-sexp-fu
        (advice-add 'cider-esf--bounds-of-last-sexp :around f))))

上記アドバイスによりS式評価時、一時的に(そして内部的に)カーソルを一文字分進めてから評価が実行される。
これによS式の末尾の右括弧にカーソルを置いて評価できるようになった。

REPL

以下はClojure layer Leader REPLから一部抜粋。

nREPLサーバを起動し接続する
SPC m s i
(ClojureScriptの場合は SPC m s I)
起動済みのnREPLサーバへ接続
SPC m s c
nREPLサーバと切断 / 終了
SPC m s q
REPLバッファとの切り替え
SPC m s s
コードとREPLのバッファを切り替える。
クラスパス上の全てのファイルを全てリロード
SPC m s x
clojure.tools.namespaceを使用したコードのリロードが行われる。
リロード前後のタイミングで任意の関数を実行することもできる。
詳細はCIDER Code reloadingを参照。
バッファを評価
SPC m s b
バッファ全体を評価する。
これはバッファ上のコードがREPLにロードされるという意味合いになる。
最後を大文字にすると( SPC m s B ) 評価後にREPLバッファへ移動する。
関数定義を評価
SPC m s f
カーソル位置の関数定義を評価する。
これは関数がREPLのカレントネームスペースにロードされるという意味合いになる。
最後を大文字にすると( SPC m s F ) 評価後にREPLバッファへ移動する。
カーソル手前のS式を評価
SPC m s e 最後を大文字にすると( SPC m s E ) 評価後にREPLバッファへ移動する。
範囲選択したS式を評価
SPC m s r 最後を大文字にすると( SPC m s R ) 評価後にREPLバッファへ移動する。
バッファのnsフォームを評価
SPC m s n
これはバッファのネームスペースをREPLのデフォルトネームスペースに設定する意味合いになる。
最後を大文字にすると( SPC m s N ) 設定後にREPLバッファへ移動する。

ブラウズ

以下はClojure layer Leader Gotoから一部抜粋。

関数 / 変数定義へ移動
SPC m g g
カーソル下の関数 / 変数の定義へ移動。
前に戻る
SPC m g b
リソースへ移動
SPC m g r
カーソル下のリソースファイルパスを開きバッファへ移動。
現状は動かない。原因はClojure layerがCIDER 0.9.0で消された cider-jump-to-resource を使っているため。
取りあえずプルリクエストは出した。fix: Rename deprecated cider function #10303
エラーが発生している行へ移動
SPC m g e
ネームスペースを検索して詳細表示
SPC m g n
ネームスペースを検索し当該ネームスペースの変数、関数の一覧を表示する。
ネームスペース一覧から詳細表示
SPC m g N
クラスパス一覧を表示
SPC m g C

評価

以下はClojure layer Leader Evaluationから一部抜粋。
バッファ、関数、選択範囲単位の評価など REPL(SPC m s 〜)側と同様の機能があるが差異は無くエイリアスとして存在している。

バッファを評価
SPC m e b
関数定義を評価
SPC m e f
カーソル手前のS式を評価
SPC m e e
カーソル手前のS式を評価して置換
SPC m e w
範囲選択したS式を評価
SPC m e r
マクロ展開
SPC m e m
マクロ全展開
SPC m e M

ドキュメンテーション

以下はClojure layer Leader Documentationから一部抜粋。

ロード済みネームスペース一覧
SPC m h n
ネームスペースブラウザを表示しロード済みネームスペース一覧を表示する。
Spacemacsの場合はキー操作が効かない問題がある。
詳細はEvil modeのキーバインド問題を参照。
apropos
SPC m h a
文字列を入力しロード済みの全てのネームスペースの中から該当するパブリックな変数、関数を検索。
(clojure.repl/apropos相当)
grimoire
SPC m h g
カーソル位置の関数または関数を指定してgrimoireのドキュメントを表示。
doc
SPC m h h
カーソル位置の関数または関数を指定して公式のドキュメントを表示。
(clojure.repl/doc相当)
javadoc
SPC m h j
カーソル位置のクラスまたはクラスを指定してブラウザでjavadocを表示。
(clojure.java.javadoc/javadoc相当)

コード整形

バッファのコードを整形する
SPC m f b or SPC m =

CIDERのポップアップバッファ

ドキュメントの表示時などのCIDERポップアップバッファは q でクローズできる。

最後に

以上でSpacemacsでClojureのコードを書く準備が整った。
今回はテスト、デバッグ、リファクタリングについては触れていないが、これらについては実際に使い込んでみて有用だったらメモにまとめようと思う。