auto-complete に日本語を補完させるプラグインをつくってみた

はじめに

これはEmacs Advent Calendar 2017の 25 日目の記事です。

記事を書くのは実に 3 年ぶりのようですね……

発端

ある日*1、嫁さんが言いました。

「講演会のメモとりでメモ帳とかワードパッド使ってるけど、emacs を試してみようかな」

それを聞いた私は考えました。

「せっかく使うって言うんだし、便利機能があると嬉しかろう。そういえば、昨今はスマホタブレットも入力時は補完候補を出してくるし、日本語補完とかあったら良さそう。よーし、おじさん一肌ぬいじゃおかな?」

できたもの

というわけで、ac-jawords という拡張機能を書きました。auto-complete が導入されている環境ならすぐに使えますので、よろしければお試しください。

https://raw.githubusercontent.com/lurdan/ac-jawords/master/screenshot.png

次のような特徴があります。

  • 文脈 (現在作業中のバッファ) に即して補完候補を表示
  • 補完候補は単語+αくらいでほどほどの長さ
  • 議事録やブログエントリ程度の長さの文章なら、まぁ軽快に動作 (論文とか書籍原稿なんかは重すぎて厳しいかも……)

補完候補の元データは現在のバッファなので、既に入力している言葉以外は出現しません。逆に、特殊な用語などで「あー、単語登録するほどでもないけど何度も同じ入力するの面倒くせー」とか、自分がよく使う言い回しなどは、それなりに候補として出してくれるのではないかと思います。

tinysegmenter という形態素解析ライブラリを使うことで、候補文字列をわりといい感じに区切って出すということをしていますが、一方で頻度計算や重みづけといったスマートな仕組みは何もないため、候補の妥当性は完全に tinysegmenter 頼みになっています。

オチ

それっぽく動くようになった時点で「こんなん作ってみたけど、どうかなー?」と動作デモをしてみた結果、

「専門用語は自分で略記するからねー (だから補完はなくても別に……)」
「一呼吸あるというか、ちょっと動作が重いよね」

など、ありがたいお言葉をいただき、利用されるには至りませんでした。泣いてなんかないやい。*2

実装まわりのよしなしごと

さて、日本語を補完するような emacs 拡張は実はすでに存在しています*3

auto-complete 用のプラグインとして、情報源が 2 つ利用可能です。ただ、困ったことにどちらも情報源の名前で ac-source-skk を使っているので、併用したい場合は注意が必要です。

ac-source-dabbrev-ja は、同じような入力ではじまる文字列を、バッファ内から探してくれるものです。標準の ac-source-dabbrev でも日本語を拾ってくれなくはないのですが、半角スペースや行末をみつけるまで全部つなげてきたりと色々な問題があって実用できません。それを日本語だけ拾うように調整した、という感じの挙動をします。

ac-source-skk (ac-ja 版) は、入力しかけた頭文字と一致する単語を SKK 辞書から拾ってくれるものです。動作イメージとしては、ローカルで動く server completion、でしょうか。先頭一致の候補がどさっと表示されるので、ちょっと絞るのが大変かも。

ac-source-skk (ac-skk 版) は、skk-dcomp のように動きます。つまり、漢字を変換しようとしている時に、その候補を出してくれます。skk-dcomp との違いは、候補がひらがなではなく漢字で表示されることでしょうか。「探す→選ぶ→変換」という操作が「探す→選ぶ」になって若干短縮されるのかもしれません。

ac-source-skk-hiracomp は、ひらがなを入力していくと、変換モード扱いの候補を出してくれます。区切り文字がうるさかったり、動きにはクセがありますが、SKK でモードレス入力をしたい、とかはできる、のかもしれません……。

私のイメージに一番近いのは ac-source-dabbrev-ja だったのですが、「出てほしい候補が出ないな……」「候補がちょっと長すぎるかも」と、どうにも挙動がしっくり来ません。他の情報源も、出てくる候補が多すぎたり、挙動が鈍重だったりして、どうもうまく使えるようにできませんでした。

仕方ない、自分でなんとかするしかなさそう。というわけで、あれこれ模索してみることにしました。

(失敗) company-dabbrev-ja / ac-dabbrev-ja-2

最初は「まぁ auto-complete 用はもうあるんだし、ac-source-dabbrev-ja を company 用に移植する感じでいってみよう」と思ってました。

実際に作りかけていたんですが、company にはコンソールモードでは fullwidth 文字が崩れるという問題がある (うえで放置されている) *4 ことがわかったので、company じゃダメだわ、となってしまいました。イマドキ ASCII 専用とかマジ勘弁。
余談ですが、自分 の emacs 設定では auto-complete から company へとのんびり切り替えを進めていたんですが、上記を受けてキャンセルし、auto-complete の設定をやり直すハメになったりしています。

ともあれ、先達の実績もあるので、素直に auto-complete を使うことにします。これなら ac-dabbrev-ja をちょっと自分好みに調整するだけでいいだろうし?

……というのは浅はかだったようで、次は dabbrev の挙動を追いきれないという壁にあたってしまいました。
どうやら、現在カーソルがある場所から、前方と後方にそれぞれ複数回の検索をしているような気配なんですが、細かい部分でどういう意図で何をやっているのかどうも見えてきません。
だいたい、一つの関数がやたら長くてわかりにくいし、これはコードを読んで挙動を追うのは無理っぽい……ということで諦めました。プログラマじゃないからね。しかたないね。

(断念) prime.el / pobox.el もどき

prime も pobox も、知る人は知っているイニシエの Emacs 拡張で、日本語入力の補完機能をもっていたものです。
打ち捨てられて久しいこれらの機能を、auto-complete の情報源として使えないかしら?

……と思ったのですが、prime も pobox もコードを読もうとしてみたものの、挙動がさっぱり理解できなくて諦めました。プログラマじゃ(ry

ac-jawords

こりゃ無理かな、と思っていた時に、auto-complete には標準で ac-source-words-in-buffer があることに気付きました。これは開いている複数バッファ内の (英) 単語を補完候補として探してくるもので、その場で検索する dabbrev とは違って、事前に単語のインデックスを作っているようです。コードも比較的追いかけやすい気がするし、これをベースに、日本語でそれっぽく動くようにしてみよう、……としてみた結果が、ac-jawords(-in-buffer) というわけです。

実際のところ、調整した内容は比較的単純です。

  • 日本語文字以外は全て無視する
  • 候補は、形態素解析をした後で、先頭の 2,3 ブロックのみを候補として利用する

この時点で、おおむね想定通りの動きはするようになっていたのですが、嫁さんはともかく自分でも使わんわい、というぐらい動作がもっさりで、「うーん、微妙……」という感じでした。
なので、その後は少しでも動きを軽くするための調整をちまちまとやっています。

  • 各種文字数の分岐条件をあれこれいじってみる (何文字以上は無視、とか何文字以上だけ拾う、とか)
  • 候補になりえない文字列をはじくなど、インデックスをできるだけコンパクトに
  • できるだけ tinysegmenter に文字列を渡す回数を減らし、渡す文字列も短く
  • 対象を現在バッファのみに限定 (同一 major-mode のバッファとかそういうのは削除) *5

ネタとして Advent calendar に間にあうように仕上げるのは無理かなー? と諦めていたのですが、自分では使ってもいいかと思えるくらいにはなった*6ので、えいやっとエントリしてみることにしたのでした。

というわけで、Advent calendar 最終日、お楽しみいただけましたでしょうか。それでは皆様、よいお年を。

*1:この日に至るまでにはそれなりの経緯 (セットアップとか専用設定とか org-mode の説明とか) があったりするけど、それはまた別の話……

*2:なので、その後の調整はほぼ自分専用 (= SKK 前提) になってしまった

*3:ほぼ @myuhe さんの独壇場

*4:https://github.com/company-mode/company-mode/issues/495

*5:実はこれで劇的に軽くなった

*6:コミットログを眺めると、右往左往してあがいているさまがあらわに……(*ノノ