Emacs上のATOKで快適日本語生活 / 2010 Emacs Advent Calendar

この記事はEmacs Advent Calender jp: 2010の5日目です。

Ubuntu上での漢字変換やEmacs上での漢字変換にはSKKを使われる方が多いと思います。導入も簡単です。ただ、自分は今まで何度も使おうと挑戦してきたのですが、挫折し続けてきました。

一方で、Ubuntuのデフォルトの変換エンジンはAnthyになっています。それなりに悪くないのですが、すぐに学習を忘れてしまうのでスーツ族には辛いです。

ということで、いろいろ試した結果、JustSystemさんの ATOK X3 にたどり着きました。とてもいいです。

普通のGUI上ではそのままで快適なのですが、Emacs上では微妙にキーバインドを取られたりして辛いです。ということで、Emacs上でATOKを快適に使う設定について書いてみようと思います。

Ubuntuユーザーで、ATOKユーザーで、しかもEmacsユーザーいう、ごく狭い対象で申し訳ありません。(例えばMacWindows上の仮想環境でATOKのサーバーを動かして、TCP経由でホスト側からATOKを使うという状況で使えるかも知れません。。。)

あらすじ

  • インストール
  • 設定
  • キーバインド変更
  • 変換候補ポップアップ
  • 確定アンドゥ

インストール

RedHatDebianだと簡単らしいのですが、Ubuntuは公式サポートはされていますがちょっと面倒です。amd64版だとさらに難しいです。インストールは以下のサイトの手順が参考になります。10.04と書いてありますが、10.10でもそのまま有効です。

あと、gtkの入力メソッドのリストからもれていることがあるので、一応以下のサイトを参考に、 /usr/lib/gtk-2.0/2.10.0/gtk.immodules あたりをチェックしてみてください。

お金を出して買ったものがこんなインストール方法というのもどうかと思われるかもしれませんが、裏返せばLinux版開発のリソースがあまり無いのだと思います。この値段で買えると言うだけでもすごいことだと思ってます!頑張って!

Emacsへのインストール

OSのパッケージに入っている場合がありますが、バージョンが古いとATOKと合わなかったりすることがありますので自前で入れた方が良いかもしれません。

ここからダウンロードして、以下のようにしてload-pathに置きます。

$ tar xvzf IIIMECF-0.75.tar.gz
$ cd iiimecf
$ emacs -q --no-site-file -batch -l iiimcf-comp.el
$ cp lisp/* ${どこか load-path の通ったところ}

さらに、Emacs上でGUI経由での漢字変換を無効にするために、emacsの起動方法を変えます。手元では、emacsは /usr/local/emacs-trunk/ に自前コンパイルで入っていますので、 /usr/local/bin/emacs を以下のようなシェルスクリプトにして起動しています。

XMODIFIERS=@im=none exec /usr/local/emacs-trunk/bin/emacs

基本設定

次にEmacsの設定ですが、まず自分の所の基本設定を示してみます。キーバインドが鬼門です。

;; ATOK X3 on IIIMECF

;; 基本設定
(setq iiimcf-server-control-hostlist 
      (list (concat "/tmp/.iiim-" (user-login-name) "/:0.0")))
(require 'iiimcf-sc)
(setq iiimcf-server-control-default-language "ja")
(setq iiimcf-server-control-default-input-method "atokx3")
(setq default-input-method 'iiim-server-control)

;; キーバインド
(setq iiimcf-keycode-spec-alist
      (append 
       '((13 10 0)       ; c-m commit -> [Enter]

         (7 27)          ; c-g -> [esc]
                         ;; MS-IMEのキー体系を仮定
         (1 36 65535)    ; c-a head position -> [Home]
         (5 35 65535)    ; c-e last position -> [End]
         (2 37 65535)    ; c-b backward bunsetu -> [←]
         (16 38 65535)   ; c-p previous candidate -> [↑]
         (6 39 65535)    ; c-f next bunsetu -> [→]
         (14 40 65535)   ; c-n next candidate -> [↓]
         
         (11 37 65535 1) ; c-k narrow bunsetu -> [Shift+←]
         (12 39 65535 1) ; c-l widen bunsetu -> [Shift+→]
         
         (21 121 65535)  ; c-u alphabet -> [F10]
         (15 118 65535)  ; c-o kana -> [F7]
         )
       iiimcf-keycode-spec-alist))
;; ↑変換リストの意味
;; [emacs event] : Emacs上のキーコード(キャラクタコード)
;; [translate keycode] : serverに送りたいキーコード (Javaのajava.awt.evet.KeyEventの値を参照)
;; [translate keychar] : serverに送りたいキーの文字。65535だと何も送らない。
;; [modifier] : 1:shift, 2:ctrl

;; 半角スペース
(defun atok-insert-half-space ()
  (interactive) (insert " "))
(define-key iiimcf-server-control-initial-state-keymap
  (kbd "S-SPC") 'atok-insert-half-space)

最初の設定が必須項目です。
次の数字が並んでいるところがキーバインド設定です。ここは後述します。
最後はShift+Spaceで半角スペースを入れるためのキーバインド追加です。

キーバインド変更について

まずIIIMFのキー入力の考え方を書きます。

IIIMFでは基本的にサーバー側(変換エンジン側)でほとんどの処理を行います。つまり、処理のキーバインドの内容を決めるのはサーバー側で、クライアント側(Emacs側)はサーバーが送ってきた表示指示に従って描画しているだけです。基本的にはATOKのキー設定がそのまま全クライアントで共通に使えるようになります。



IIIMFの動作イメージ

しかしながら、EmacsのキーイベントとATOK(IIIMP)とのキーイベントにミスマッチがあるので、そこを埋めるために変換テーブルで変換しています。それが上の設定にある iiimcf-keycode-spec-alist です。デフォルトの設定はある程度の変換が書かれているのですが、Ctrl系のキーが効かないようです(原因はまだ追えてません)。

そこで、Emacs上で行うCtrl系のキー入力を、目的の機能を持つ別のキーに変換するようにテーブルを書きます。上の例では以下のように変換しています。

;;           Emacs上のキー、機能  ->  ATOKのキー
'((13 10 0)       ; c-m 確定 -> [Enter]
  (7 27)          ; c-g キャンセル -> [esc]

  (1 36 65535)    ; c-a 先頭移動 -> [Home]
  (5 35 65535)    ; c-e 末尾移動 -> [End]
  (2 37 65535)    ; c-b 文節左移動 -> [←]
  (16 38 65535)   ; c-p 前候補 -> [↑]
  (6 39 65535)    ; c-f 文節右移動 -> [→]
  (14 40 65535)   ; c-n 次候補 -> [↓]
  
  (11 37 65535 1) ; c-k 文節縮める -> [Shift+←]
  (12 39 65535 1) ; c-l 文節伸ばす -> [Shift+→]
  
  (21 121 65535)  ; c-u アルファベット化 -> [F10]
  (15 118 65535)  ; c-o カタカナ化 -> [F7]

ATOK側でCtrl系でないキーに必要な機能が割り振ってある必要があります。また、上の例ではMS-IME的な操作になってますので、他のキーが好きな人は適当に変える必要があります。

ATOK側に送るキーコードはJavaのキーコードを使います。定数の値については JDK のドキュメント(例えばここ)を参照してください。

全角・半角スペース

次に、全角と半角のスペースの使い分けもちょっとうまくいかないので、ちょっと手を入れます。普通にスペースを押したときにどちらを入力するかはATOKの設定で行うのですが、Emacs経由だとうまく使い分けられません。そこで、iiimcfのキーマップで制御します。

iiimcfのキーマップには主に2つあります。入力待ち中のキーマップ iiimcf-server-control-initial-state-keymap と、入力中のキーマップ iiimcf-server-control-preedit-state-keymap です。これらに定義してあるキー入力はサーバー側に送られずにクライアント側のみで処理されます。

ということでスペースの使い分けはここで入れることにします。上の最後の部分がその設定です。

以上で、大抵の設定についてはカバーできるかなと思います。

変換候補のポップアップ

IIIMECF 0.75 では、変換候補の一覧がミニバッファに表示されます。

以前も書きましたが、画面が広くなった現在においてはミニバッファは遠すぎます。

そこで id:m2ym さんの popup.el を使って変換文字列の下に表示するようにしてみました。以下のコードで既存の処理を乗っ取ってポップアップさせます。



変換候補のポップアップ

;; IIIMECFで変換候補一覧ポップアップ
(require 'popup)

(defvar iiimcf-UI-draw-lookup-choice-popup-instance nil)

(defun iiimcf-UI-draw-lookup-choice-popup-position ()
  ;; uic via dynamic scope!!!
  (let* ((pos (marker-position (iiimcf-UI-marker uic)))
         (text (iiimcf-UI-preedit-text uic))
         (len (length text))
         (pts 0) pte cprop position)
    (setq text (copy-sequence text))
    (while pts
      (setq cprop (get-text-property pts 'iiim-feedback text)
            pte (next-single-property-change pts 'iiim-feedback text))
      (if (eq 'reverse (car cprop))
          (setq position (+ pos pts) pts nil)
        (setq pts pte)))
    position))

(defun iiimcf-UI-draw-lookup-choice-popup (mk candidates)
   (let ((cands (aref candidates 0))
         (index (aref candidates 1))
         (title (aref candidates 2)) lst)
     (setq lst (loop for pair in cands
                     for num = (car pair)
                     for str = (cdr pair)
                     collect (popup-make-item 
                              (format "%s:%s" num str))))
     (unless (and 
              iiimcf-UI-draw-lookup-choice-popup-instance
              (popup-p iiimcf-UI-draw-lookup-choice-popup-instance))
       (setq iiimcf-UI-draw-lookup-choice-popup-instance
             (popup-create (iiimcf-UI-draw-lookup-choice-popup-position) ; pos
                           (popup-preferred-width lst)  ; width
                           10 ; height
                           :around t
                           :face 'popup-menu-face
                           :selection-face 'popup-menu-selection-face)))
     (popup-set-list iiimcf-UI-draw-lookup-choice-popup-instance lst)
     (popup-draw iiimcf-UI-draw-lookup-choice-popup-instance)
     (popup-select iiimcf-UI-draw-lookup-choice-popup-instance index)))

(defun iiimcf-UI-clear-lookup-choice-popup (marker)
  (when 
      (and 
       iiimcf-UI-draw-lookup-choice-popup-instance
       (popup-p iiimcf-UI-draw-lookup-choice-popup-instance))
    (popup-delete iiimcf-UI-draw-lookup-choice-popup-instance))
  (setq iiimcf-UI-draw-lookup-choice-popup-instance nil))

(defadvice iiimcf-UI-draw-lookup-choice-echo-line
  (around iiimcf-ui-popup (mk candidates))
  (iiimcf-UI-draw-lookup-choice-popup mk candidates)
  ;ad-do-it
  )

(defadvice iiimcf-UI-clear-lookup-choice-echo-line
  (around iiimcf-ui-popup (mk))
  (iiimcf-UI-clear-lookup-choice-popup mk)
  ;ad-do-it
  )

(ad-activate-regexp "^iiimcf-ui-popup")

ポップアップ表示位置を計算するために呼び出し元のローカル変数に入っている uic という変数が使いたかったのですが、引数で取ってくるのが大変そうだったのでダイナミックスコープで取ってきました。

ダイナミックスコープは id:podhmo さんの fletで関数を横取りする でも書かれています。ダイナミックスコープは、こんな風にごにょごにょする際にちょっと便利です。便利ではありますが明らかに正しくない使い方ですので、プロトタイプやちょっとした拡張にとどめておいた方が良いと思います。

手元では大体うまく動いていますが、もしかしたら微妙な挙動があるかも知れません。

確定アンドゥ

最近の漢字変換では確定アンドゥの機能が付いています。 IIIMECF 0.75 ではそのままでは確定アンドゥが使えませんが、確定アンドゥのキーイベントをATOKサーバー側に送れば実現できました。

以下コードと設定例です。

(defun atok-undo-commit ()
  (interactive)
  (when iiimcf-server-control-ic-id
    (iiimcf-server-control-keyforward last-input-event)
    (iiimcf-set-icfocus iiimcf-server-control-ic-id)))

(define-key iiimcf-server-control-initial-state-keymap
  (kbd "<C-backspace>") ; ATOKでは C-BS に確定アンドゥが割り当ててある
  'atok-undo-commit)

その他の機能(辞書登録とか)も頑張れば使えるのかもしれませんが、ちょっと試したぐらいでは実現できませんでした。

まとめ

ということで、UbuntuEmacs上でのATOKについて書いてみました。これまでのコードをgistにまとめました。

変換候補のポップアップと確定アンドゥは既に手に馴染んでいます。全く違和感がありません。つまり、使えて当たり前でいままで出来無かったことの方が異常だったのだと思います。これで普通になれました。

ちなみに、上で書いたコードは少ないのですが、このためには IIIMECF のコード(elisp)と IIIMF の white paper、プロトコル仕様書(i18n.orgはリンク切れですが、ATOKのCDに入っています)、後は popup.el のコード(elisp)、Emacsのイベントに関する文書(info)などを読みました。全部で1週間以上かかっています。大体10年ほど前の設計や実装ですが、綺麗なコードと設計で大変勉強になりました。ただ、IIIMFの仕様はやっぱり「Sunが作った」という感じかなと思いました。


仕様を調べているうちに、設計者の樋浦さんが亡くなられていたということを知りました。IIIMFや国際化に尽力された樋浦さんのご冥福をお祈りいたします。