smart-newline.elという拡張 #emacs

.emacs Advent Calendar 2013 8日目の今日は、最近自分が開発しているsmart-newline.elについてのお話です。

既に何回かこのブログでも解説してる。

また、拡張を作るにあたってテスト用のライブラリも生まれた。

最近ちょっとずつアップデートしたので改めて解説記事を書きたい。

smart-newline.elとはなにか?

一言で言うと、コードを書くための改行の入力をエンターキー1つで快適に行うための拡張。 https://github.com/ainame/smart-newline.el

モチベーション

改行の入力方法を統合してキーバインドを自由にする

エディタでコードを書くときに、改行文字の入力はほぼ必須だけど、その際にインデントや改行の入力後のカーソル位置の違いなど気になることがある。特にEmacsの場合、newline(C-m/RET)や、newline-and-indent(C-j) 、open-line(C-o)や、reindent-then-newline-and-indent などデフォルトでも様々な改行の入力方法があって、デフォルトではそれぞれ違うキーバインドが割り当てられてる。

これが仮に、C-mだけで済ませられれば、C-jやC-oが要らなくなり、押しやすいキーバインドが2つとも自由になる。

コードを楽に書きたい

コードを書く時は、完全に新規ファイルにプログラムを書くときよりも既存のコードを変更する事のほうが多いかと思う。例えば以下の様なfooメソッドとbarメソッドの間に新たらしいメソッドを書きたいと思ったら、自分は、現在1つしか空行が入ってない状態から、2つ空行を挟んでから書き始めたい。この操作をするためには単に改行を入力するためだけでなく、カーソル位置の変更も伴わなければならない。そのために普通だったRET C-o、とかRET RET C-pとか使って無意識で操作はできるものの毎回この操作をするのが馬鹿らしくなったりする。

連続でRETを押すだけでメソッド書き始められる

行の先頭の方にカーソルがある場合は上の行に挿入出来る

カーソルの上下に空行のバランスを保って改行挿入

こんな感じで、コードを差し込むことにも対応できた賢い改行文字挿入がしたかった。

使い方

el-getかpackage.elのmelpa(ここがマージされたら)から。

あとは、init.elなどの設定Fileに以下のように書き足す。

(add-hook 'ruby-mode-hook ;; or any major-mode-hooks
  (lambda ()
  (smart-newline-mode t)))

こうすると、ruby-modeの時だけsmart-newline-modeのminor-modeが有効になる。他にも自分が使いたいメジャーモード用のhookを書けば良い。

もしemacsをコードを書く以外に使わないなら常に有効になるようにリターンキーに直接割り当ててもいいと思うけど、自分はHTMLとかメモ書くときにはちょっと邪魔さを感じたのであんまりおすすめしない。

(define-key global-map (kbd "RET") 'smart-newline) ;; or any key as you like

あとは、このままコードを書き始める時にRET or C-mを押しまくって使い心地を確かめるだけである。

コントリビュート

もし使ってみて不満がある挙動があったり、もっと良いのでは?というパターンがあれば、プルリクエスト下さい。 しかし、1つの機能に無理やりいろんなことをさせようとするために、正規表現とかで頑張ってコンテキストを解釈して、条件判定して改行方法を切り替えているため、コードはあんまり綺麗じゃなくて、今後どうやって拡張していくべきか悩んでいるところでもある。

↓こんな感じ https://github.com/ainame/smart-newline.el/blob/master/smart-newline.el#L102

ただ、無策で開発しているわけではなく、対応するべきパターンが増えるたびに動作確認する必要性が多くなってくるため、 少なくとも実装したパターンは自動テストでカバーしようと思い、cursor-test.elというテストライブラリを作った。

これを使うと、emacsのカーソル位置が目に見える形でテスト記述が出来て、すごく便利。 こんな感じで、「|」でカーソル位置を表現できて、expectとactualを比較する。

(ert-deftest test-cperl-mode-05 ()
  "open-line-between"
  (cursor-test/equal
   :type 'pos
   :actual (cursor-test/setup
            :init "
    sub foo {
        my $self = shift;
        |return $self->foo;
    }
"
            :exercise 'insert-newline-once-in-cperl-mode)
   :expect (cursor-test/setup
            :init "
    sub foo {
        my $self = shift;
        |
        return $self->foo;
    }
")))

こういうテストケースの積み重ねで複雑な動作を積み上げている感じ。

余談:最初はpointの比較でテストしてたんだけど、pointだとソースコードの末尾に空白が残ってしまったりとかで完全にはカーソル位置が一致しない場合があったので、きちんと行番号と列番号の比較が出来るように仕様を変更しました。

以上、smart-newline.elの紹介でした。 明日の担当は、catatsuyさんです。お楽しみに。