lem: Common Lispで書かれたEmacsライクなエディタlemを使ってみた

lemはCommon Lispで書かれたEmacsライクなエディタで、拡張もCommon Lispで書ける。cl-charmsというncursesのCFFIラッパーを使っている。
特に何もしてなくても起動が速いが、lemをロードした状態で処理系のコアイメージをダンプすることでさらに速くなる。

インストール

とのことなのでroswellさえ入っていれば導入は簡単。

ros install cxxxr/lem

こうすると、 ~/.roswell/local-projects/cxxxr/lem 以下にコードがダウンロードされ、Roswellの処理系でコンパイル、ロードされた後に処理系のコアイメージがダンプされる。さらにこのコアイメージを使って処理系を起動し、lemを起動するためのrosスクリプトが ~/.roswell/bin/lem にできる。 ~/.bashrc などでこれを起動できるようにパスを通しておく。

export PATH=$HOME/.roswell/bin:$PATH

あとはシェルからlemで起動できる。起動が速い!

設定ファイル .lemrc

設定は ~/.lemrc に書く。

当然ながらこの設定ファイルもCommon Lispで書ける。とりあえず普段Emacsで使っているキーバインドを設定してみる。

;;; -*- coding:utf-8; mode:Lisp  -*-

(in-package :lem)

(define-command split-3-window-horizontally () ()
  (let ((w-width (window-width)))
    (split-active-window-horizontally)
    (shrink-window-horizontally (- (window-width) (floor (/ w-width 3))))
    (other-window)
    (split-active-window-horizontally)
    (other-window)
    (other-window -2)))

;; Key Bindings
(define-key *global-keymap* "M-o" 'other-window)
(define-key *global-keymap* "M-i" 'delete-other-windows)
(define-key *global-keymap* "M-u" 'split-active-window-vertically)
(define-key *global-keymap* "M-U" 'split-3-window-horizontally)
(define-key *global-keymap* "M-z" 'query-replace)
(define-key *global-keymap* "C-x l" 'start-lisp-repl)

define-commandでコマンドを定義すると 'M-x コマンド名' でも呼べるしキーバインドを割り当てることもできる。自分がよく使う画面を縦に3分割するコマンド split-3-window-horizontally を定義してみた。コマンドの探し方はEmacsと同じように M-x apropos-command でキーワードを入れれば色々出てくる。
デフォルトでCommon Lispシンタックスハイライトされるが、配色やスタイルをいつものやつにしたかったのでこれも設定する。

;;; syntax highlight
;; see (apropos "attribute")
(setf *enable-syntax-highlight* t)
(setf (attribute-fg-color *syntax-keyword-attribute*) "magenta")
(setf (attribute-bold-p *syntax-keyword-attribute*) t)
(setf (attribute-bold-p *syntax-function-name-attribute*) t)
(setf (attribute-fg-color *syntax-comment-attribute*) "cyan")
(setf (attribute-fg-color *syntax-constant-attribute*) "green")
(setf (attribute-fg-color *syntax-string-attribute*) "yellow")
(setf (attribute-fg-color *syntax-variable-attribute*) nil)
(setf (attribute-fg-color *mark-overlay-attribute*) nil)
(setf (attribute-reverse-p *mark-overlay-attribute*) t)

ソースコードをcolorでgrepしてみるとattribute構造体というのが出てきたので、REPLで apropos や describe などを使って設定できそうな変数を探した。その結果、シンタックスハイライトに関わる変数を見つけたので、これらの構造体のスロットを変更することで、前景色、背景色、ボールドにするか、反転するかなどを設定できることが分かった。
下はこの設定をした後の様子。


LispモードとREPL

Emacsと同様 C-x C-f で.lisp拡張子のファイルを開くとLispモードに入る。LispモードはS式単位の編集などは問題なくできた。 C-c C-j でEmacsの *scratch* のように式ごとの評価結果をバッファに書き込む形での評価ができるが、いつも式ごとの評価は C-x C-e でやっているのでその辺も設定しておく。

(in-package :lem.lisp-mode)

(define-key *lisp-mode-keymap* "C-x C-e" 'lisp-eval-last-sexp)
(define-key *lisp-mode-keymap* "C-c C-i" 'lisp-comment-region)
(define-key *lisp-mode-keymap* "C-c C-o" 'lisp-uncomment-region)

;; 現在は修正されている
;; (define-command lisp-repl-move-to-beginning-of-line () ()
;;   (move-to-beginning-of-line)
;;   (shift-position (+ (length (lisp-repl-get-prompt)) 2)))

;; (define-key *lisp-repl-mode-keymap* "C-a" 'lisp-repl-move-to-beginning-of-line)

Common LispのREPLは M-x start-lisp-repl で起動する。これはよく使うので C-x l に割り当てておく。
下はREPLを起動したところ。

defunの途中まで書くとちゃんとラムダリストを表示してくれるのが分かる。ただしこの状態でC-aするとプロンプトを突き抜けて行頭まで行ってしまうのでやっつけで lisp-repl-move-to-beginning-of-line を書いて C-a に割り当てた。(追記: これは現在は修正されている)

感想

lemは去年lispmeetupで@snmstsさんが紹介されていたので知ってはいたのだが、id:cxxxrさんによって地道に開発が続けられていて着実に進化していると思った。コードも読みやすいので拡張機能も書きやすいように思える。日本語入力はuim-fep経由でできるが、SKKクライアントの実装は簡単なので暇があればやってみたい。こうなってくるとiswitchbやelscreenのようなものも欲しくなってくる。
よくよく考えてみればlemに拡張機能を追加していった場合もlem自体をそうしたようにコンパイル、ロード済のコアイメージを読み込めばいいので起動は速いままなはずである。昨今のエディタは拡張機能を高級なスクリプト言語で書ける代わりに拡張が増えてくると起動が遅くなる場合が多い。そしてEmacsのemacsclient、vimの+clientserverのようにエディタを常駐化させるという苦肉の策に出る羽目になる。バイトコンパイルしたとしても結局VMにロードさせる時間はかかるので、ネイティブコンパイル済みのイメージをメモリに読み込むだけのlemが輝いて見える。
lemで一つ気になったのは、Lisp機械学習のような重い処理をやるとエディタも止まってしまって何もできなくなるということ。まあ多分そういう用途よりはさっと起動してちゃちゃっとスクリプトを書くような用途に向いているのかも。あるいはもう一つ別プロセスでLisp処理系を起ち上げてlem側でSLIMEのようなものを動かすという手もあるか。