現時点(2012年1月初頭)では、
- Emacs23 の最新版 emacs-23.3b.tar.gz (http://ftp.gnu.org/gnu/emacs/emacs-23.3b.tar.gz)
- MacEmacs JP の inline patch の最新版 trunk (http://sourceforge.jp/projects/macemacsjp/) - 以下を含む:
- emacs-inline.patch: patch for input method
- font.patch: patch to fix trembling font when user inputs japanese by input method
- xcode4.patch: patch to compile by xcode4 (gcc-4.2 64bit)
- lion.patch: patch to compile on MacOSX 10.7. It contains xcode4.patch.
- KAYAC さんの OS X Lion 用のフルスクリーン用パッチ (http://bm11.kayac.com/2009/project/opensource/kayac-emacs)
- popup 時にクラッシュする問題に対するパッチ (http://moimoitei.blogspot.com/2010/05/fix-cocoa-emacs-23.html)
- MacEmacsJP の ML で報告された SIGSEGV 問題(http://sourceforge.jp/projects/macemacsjp/lists/archive/users/2011-August/001699.html) に対するパッチ (https://github.com/n-miyo/homebrew/commit/f45111c99bf287b197f21a6faf28417e9ffa72ef)
以上の先達の成果物によって Emacs 自体がクラッシュしてしまうことはなくなったのだが、漢字変換中にしばしば "Args out of range: x, y" エラーが頻発し、これが出ると変換された文字がバッファ内に表示されないという問題が出現する。
こういうときには Google に頼るのだが、数件の問題報告は見るものの、パッチが出てきた様子はない。
というわけで、Email も Emacs + Wanderlust で読み書きしている自分には死活問題なので、Lisp は知らないけれど、2011〜2012の年末年始にこの問題を追うことにした。
基本はいわゆる「printf デバッグ」作戦を Emacs Lisp に対してやってみたのだが、src/nsterm.m の "#define NS_KEYLOG 0" を 1 にして入力キーの様子を stdout に出しつつ、lisp/term/ns-win.el に message 出力を仕込んでみたところ、問題っぽいところを発見した。
こんなふうに message 出力を仕込んで
--- lisp/term/ns-win.el.patched 2012-01-01 00:56:06.000000000 +0900
+++ lisp/term/ns-win.el 2012-01-05 18:28:27.000000000 +0900
@@ -649,10 +649,20 @@
`ns-working-overlay' and `ns-marked-overlay'. Any previously existing
working text is cleared first. The overlay is assigned the faces
`ns-working-text-face' and `ns-marked-text-face'."
+ (message "DEBUG: pos=[\%d] len=[\%d] ns-working-text=[\%s] text-len=[\%d]" pos len ns-working-text (length ns-working-text))
(ns-delete-working-text)
+ (message "DEBUG: ns-delete-working-text done")
(let ((start (point)))
- (put-text-property pos len 'face 'ns-working-text-face ns-working-text)
+ ; check pos and ns-working-text length
+ (if (<= pos (length ns-working-text))
+ (progn
+ (put-text-property pos len 'face 'ns-working-text-face ns-working-text)
+ (message "DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done")
+ )
+ (message "DEBUG: SKIPPED put-text-property pos len 'face 'ns-working-text-face ns-working-text")
+ )
(insert ns-working-text)
+ (message "DEBUG: insert-ns-working-text done")
(if (= len 0)
(overlay-put (setq ns-working-overlay
(make-overlay start (point) (current-buffer) nil t))
この状態でことえり経由で文字をピコピコ入力する経過を *Message* バッファで見張ってみた。そうしたところ、得られた結果がこれ:
DEBUG: pos=[1] len=[0] ns-working-text=[k] text-len=[1] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[1] len=[0] ns-working-text=[か] text-len=[1] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[2] len=[0] ns-working-text=[かn] text-len=[2] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[3] len=[0] ns-working-text=[かんg] text-len=[3] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[3] len=[0] ns-working-text=[かんが] text-len=[3] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[4] len=[0] ns-working-text=[かんがえ] text-len=[4] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[5] len=[0] ns-working-text=[かんがえt] text-len=[5] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[5] len=[0] ns-working-text=[かんがえた] text-len=[5] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[6] len=[0] ns-working-text=[かんがえたほ] text-len=[6] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[6] len=[0] ns-working-text=[かんがえたほ] text-len=[6] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[7] len=[0] ns-working-text=[かんがえたほう] text-len=[7] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[8] len=[0] ns-working-text=[かんがえたほうh] text-len=[8] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[8] len=[0] ns-working-text=[かんがえたほうほ] text-len=[8] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[9] len=[0] ns-working-text=[かんがえたほうほう] text-len=[9] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[10] len=[0] ns-working-text=[かんがえたほうほうh] text-len=[10] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done DEBUG: pos=[10] len=[0] ns-working-text=[考えた方法は] text-len=[6] <----- !!! DEBUG: ns-delete-working-text done <----- !!! DEBUG: SKIPPED put-text-property pos len 'face 'ns-working-text-face ns-working-text <----- !!! DEBUG: insert-ns-working-text done DEBUG: pos=[0] len=[3] ns-working-text=[考えた方法は] text-len=[6] DEBUG: ns-delete-working-text done DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done DEBUG: insert-ns-working-text done
あれ、ns-working-text がおかしい。「かんがえたほうほうh」のあと "a" をタイプしたので「かんがえたほうほうは」になるはずなのに、それがなくて、いきなり感じ変換されちゃった後の「考えた方法は」になってるね。なのに、そのとき pos の数値はまだ「かんがえたほうほうは」をベースにした 10 になっている。結果、ns-working-text の方の文字列はすでに漢字変換後のデータになっていて 6 文字しかないのに、pos の数値は 10 になってる。ふむむ。
ソースの中で関連しそうなのは src/keyboard.c の
4204 struct input_event *event;
4205
4206 event = ((kbd_fetch_ptr < kbd_buffer + KBD_BUFFER_SIZE)
4207 ? kbd_fetch_ptr
4208 : kbd_buffer);
...... (snip) ......
4241 #if defined (HAVE_NS)
4242 else if (event->kind == NS_TEXT_EVENT)
4243 {
4244 if (event->code == KEY_NS_PUT_WORKING_TEXT)
4245 obj = Fcons (intern ("ns-put-working-text"), Qnil);
4246 else if (event->code == KEY_NS_UNPUT_WORKING_TEXT)
4247 obj = Fcons (intern ("ns-unput-working-text"), Qnil);
4248 else if (event->code == KEY_NS_PUT_MARKED_TEXT)
4249 obj = Fcons (intern ("ns-put-marked-text"), event->arg);
4250 kbd_fetch_ptr = event + 1;
4251 if (used_mouse_menu)
4252 *used_mouse_menu = 1;
4253 }
4254 #endif
と、さっき手を入れた lisp/term/ns-win.el の
638 (defun ns-put-marked-text (event)
639 (interactive "e")
640
641 (let ((pos (nth 1 event))
642 (len (nth 2 event)))
643 (if (ns-in-echo-area)
644 (ns-echo-marked-text pos len)
645 (ns-insert-marked-text pos len))))
646
647 (defun ns-insert-marked-text (pos len)
648 "Insert contents of `ns-working-text' as UTF-8 string and mark with
649 `ns-working-overlay' and `ns-marked-overlay'. Any previously existing
650 working text is cleared first. The overlay is assigned the faces
651 `ns-working-text-face' and `ns-marked-text-face'."
652 (message "DEBUG: pos=[\%d] len=[\%d] ns-working-text=[\%s] text-len=[\%d]" pos len ns-working-text (length ns-working-text))
653 (ns-delete-working-text)
654 (message "DEBUG: ns-delete-working-text done")
655 (let ((start (point)))
656 ; check pos and ns-working-text length
657 (if (<= pos (length ns-working-text))
658 (progn
659 (put-text-property pos len 'face 'ns-working-text-face ns-working-text)
660 (message "DEBUG: put-text-property pos len 'face 'ns-working-text-face ns-working-text done")
661 )
662 (message "DEBUG: SKIPPED put-text-property pos len 'face 'ns-working-text-face ns-working-text")
663 )
664 (insert ns-working-text)
665 (message "DEBUG: insert-ns-working-text done")
666 (if (= len 0)
667 (overlay-put (setq ns-working-overlay
668 (make-overlay start (point) (current-buffer) nil t))
669 'face 'ns-working-text-face)
670 (overlay-put (setq ns-working-overlay
671 (make-overlay start (point) (current-buffer) nil t))
672 'face 'ns-unmarked-text-face)
673 (overlay-put (setq ns-marked-overlay
674 (make-overlay (+ start pos) (+ start pos len)
675 (current-buffer) nil t))
676 'face 'ns-marked-text-face))
677 (goto-char (+ start pos))))
のあたり。
keyboard.c で (input_event)event にセットしたイベントを引数にして Lisp 関数の ns-put-marked-text を呼んでいて、ns-win.el 側で定義されている Lisp 関数 ns-put-marked-text 側で渡された引数から変数 pos / len に何文字目をいじるのか、を入れているようなのけれど、稀に pos/len の数値は漢字に変換される前のデータなのに対し、ns-working-text の文字列は漢字に変換された後のデータとなっていて、ここに情報に不一致があるために ns-win.el 側で put-text-property するところで pos/len のデータが ns-working-text の文字数をはみ出してしまうので out of range エラーとなるみたい。
タイミングの問題かなにかで、event が発生した後、put-text-property するまでの間に ns-working-text が更新されてしまっているのか?
というわけで、本当なら pos/len のデータ ns-working-text を一致させるべきなのだろうけど、どうやって同期を取ったらいいのかわからないので、とりあえず pos の数値が ns-working-text の文字数を超えていたら put-text-property はしないでやり過ごす、ということにした。
--- lisp/term/ns-win.el.patched 2012-01-05 20:07:58.000000000 +0900 +++ lisp/term/ns-win.el 2012-01-05 20:10:16.000000000 +0900 @@ -651,7 +651,8 @@ `ns-working-text-face' and `ns-marked-text-face'." (ns-delete-working-text) (let ((start (point))) - (put-text-property pos len 'face 'ns-working-text-face ns-working-text) + (if (<= pos (length ns-working-text)) + (put-text-property pos len 'face 'ns-working-text-face ns-working-text)) (insert ns-working-text) (if (= len 0) (overlay-put (setq ns-working-overlay
副作用として、漢字変換で入力している文字がたま〜に位置がずれて挿入されちゃうのが確認されているのですが、これで out of range エラーなく動いているので、とりあえず ok とします。
(2012/01/07 追記): 上の副作用があったので、こうしました。
--- lisp/term/ns-win.el.patched 2012-01-05 20:07:58.000000000 +0900
+++ lisp/term/ns-win.el 2012-01-05 20:10:16.000000000 +0900
@@ -651,6 +651,8 @@
`ns-working-text-face' and `ns-marked-text-face'."
(ns-delete-working-text)
(let ((start (point)))
+ (if (<= pos (length ns-working-text))
+ (progn
(put-text-property pos len 'face 'ns-working-text-face ns-working-text)
(insert ns-working-text)
(if (= len 0)
@@ -664,7 +666,7 @@
(make-overlay (+ start pos) (+ start pos len)
(current-buffer) nil t))
'face 'ns-marked-text-face))
- (goto-char (+ start pos))))
+ (goto-char (+ start pos))))))
(defun ns-echo-marked-text (pos len)
"Echo contents of `ns-working-text' in message display area.
これで、漢字変換している文字がずれて挿入される問題も回避できている模様。
ただ、これは根本的な解決策ではない。本当は、event の中身と ns-working-text の同期をとれるようにしないといけないはず。まあ、でも、こうしてブログに晒しておけば、だれかもっと造詣の深い心優しい人がもっとまともなパッチを作ってくれるかもしれないし。
(2012/01/14 追記): ソースの引っ張り方とそれぞれのパッチの当て方を教えて、というリクエストがありました。長くなるので、コメント (Comment) のほうに一通りの手順を載せておきます。たしかに trunk とか、パッチのパッチみたいなのもあって、わかりにくかったですね。ごめんなさい。