skk.vimの構造について
まだ肝心のコードを書いていないので、
こういった構造は変わっていくと思いますが、
フィルタとモードのあたりは採用していきたいと思ってます。
skk.vimも多分こういう処理をしてると思うのですが、あまり追えてない...
このメモは開発中のものであるので、了承なく変更する可能性があるのでご了承ください。
このエントリが追記されなくなったら、 skk7.vimの仕様が固まったということです。
嘘みたいだろ・・・全部iPod touchで書いたんだぜ、これ・・・
この記事は僕のネタ帳みたいになってきてるので、現在の実装とは違っています。
現在の構造についてはいつかskk7.vimの構造が落ち着いてきたらエントリにします...
あとそういえば全部iPod touchはさすがに嘘でした。
いくつかPCでも書いてますが、もはやどの部分かは忘れました。
(2月1日)
ファイル
autoload/skk7.vim
いろいろ。
ここから始まる。
autoload/skk7/util.vim
色んなヘルパー関数。
autoload/skk7/event.vim
autocmd的なもの。
autoload/skk7/mode/*
モードについては後述。
イベント (autoload/skk7/event.vim)
- 入力して字が表示される前
- 後
- InsertChanged的な
- それInsertChangedでできないの?
- 変換する前
- 後
- イベントで通知するより、モード固有でやれることはモードのautoload関数を呼ぶ形でやった方がいいかもしれない
- ここは悩むところ
フィルタ
- 何らかのキーが押される度にフィルタを通して挿入される文字列を決めていく
- フィルタ用関数の返り値は変換した文字列
- lnoremap用関数でディスパッチされるためにlnoremap用関数が文字列を返せばいいのだけど、シンプルさのため
- 変換キーが押されたらskkservなりgoogle suggestなり*2なんらかの関数に渡す
- フィルタ関数の呼び出し元がめんどくさい変換部分の書き換えなどをやってくれる
- 右側のみ更新されたら右側のみ(差分のみ)アップデートとか
- 文字列検索でこういう後ろから検索していくアルゴリズムなかったっけ
- 仮に大文字の英字やsticky keyなどを押すなどして変換中のことを「フィルタ待ち」と呼ぶ
- フィルタ待ちかどうか確認する場合はskk7#...()で確認できる
- それぞれの引数はorig_str, filtered_str, char, henkan_count, is_async
- orig_str: フィルタ待ちの場合でない場合は"", フィルタ待ちの場合ここにバッファ文字列が入れられていく。この文字列はフィルタされた文字列ではなくてすべて英字である
- filtered_str: 上と同じでバッファ文字列だが、フィルタがかけられた文字列が入れられていく
- char: キー入力された文字列 ("o")
- is_async: vimprocを使って非同期でフィルタ用関数が実行されているかどうか
モード
- 各種フィルタ用関数を置くネームスペースと考える
- あとフィルタ用関数は毎回呼び出される*3ので、毎回のフィルタごとに消えてなくならない変数*4、あるいはフィルタごとにやり取りするための変数なんかを決めておく
- あとなんかフィルタ間でやり取りするための変数
- そこら辺のことは次項の「ディスパッチ用関数/フィルタ用関数/での間のやり取りについて」で説明する
- skk7#mode#...#filter_rom()
- skk7#mode#...#filter_hira()
- フィルタをかけて必要があれば他のステートに移る
- autoload/skk.vim内で、ステートはs:skk7_state、モードs:skk7_modeとして表される
- フィルタなどからモードやステートを見たり変更する場合は、操作用関数を通して操作する。
- モードは変更できなくてもいい気がするけど、一応用意しておく
- 例えばs:skk7_modeが"hira"、s:skk7_stateが"rom"であればskk7#mode#hira#filter_rom()が呼び出される
- とは言え一から書くのはめんどくさい
- _defaultというモードは使えなくなるけど、他のモードのためのヘルパー関数は次の通り
- 引数などは変わるかもしれない
- skk7#mode#_default#from_rom_to_hira(char, is_async)
- ローマ字をうっていくとひらがなになる
- charが大文字の英字だった場合やsticky keyだった場合は"▽"を返してskk7#mode#...#()などを呼び出してくれたり
- skk7#mode#_default#from_rom_to_kanji()
- skk7#mode#_default#from_rom_to_kana()
- qのカタカナモード用
- skk7#mode#_default#from_kanji_to_hira()
- 再変換用
- skk7#mode#_default#from_rom_to_hira(char, is_async)
-
- skk7#mode#_default#from_hira_to_kanji()
- モード固定で変換
- skk7#mode#_default#from_hira_to_kanji()
- 特定の範囲をフィルタにかけるoperatorなんてのもあればかなりいいよね
- xxxからyyyに変換 みたいな
- モードが存在しなかった場合は、存在すればskk7#mode#...#unknown_mode()、存在しないならskk7#mode#_default#unknown_mode()が呼び出される
- ステートについても同じでunknown_state()が呼び出される
- フック可能な訳は主にデバッグのため
- あと「未実装です!!」とか言える
- 入力したら別スレッドを起動するようにフィルタをかけて、入力をブロッキングしないようにしたい
- 例えばid:mattnさんのgoogle-suggest.vimをskk7.vimで実現するなら、Googleへのリクエストを投げて、次のキー入力が来たら現在のリクエストをキャンセルしてまた新たにリクエストを投げる、など。普通だったらこれはGoogleからサジェスト結果が来るまで待っていなければならない。
ディスパッチ用関数/フィルタ用関数での間のやり取りについて
- フィルタ用の変数*5については、skk7#mode#...#の中に置いておけばいい?
- まだニーズが分からないけど、モード間でやり取りしたい場合は変数をautoload化して公開すればできる
プラグイン
- 全部autoload化してあるのでプラグインを作る時は楽?
- 新しいモード
- 新しいフィルタ用関数
- 他のフィルタ用関数のヘルパー関数となるようなものを作りたい時はskk7#mode#_hoge#...()のように下線(_)をつけておく
(2月3日追記)
変換フェーズにおけるモード切り替えキーの扱い
モードのキー割り当てについて
- <と>と?にもまだなんか割り当てられそう
- zはモードに割り当てた文字を入力したい時のエスケープみたいなものとして働く
- もちろんキーはカスタマイズ可能
- じゃあ@とかもz@で入力するようにすればモードに割り当てることはできるわけだ
- でもそうやってどんどんモードを割り当てていくと、「z + 文字」が「文字」になることは個々のモードのテーブルに依存してるわけで、そうなると誰かが新しいモードを作ってくれた時とかは、そのキーを入力するために全てのモードのテーブルを書き換えなきゃならない
- zをエスケープ文字列として、ディスパッチ関数側でz@は@にする
- 全角の@を入力したい時は?
- 現在のモードのテーブルにたずねて、なかったら半角の@にフォールバックする?
- やっぱりテーブルの構造についても仕様化しないとダメだ
- autoload/skk7/table/...
テーブルを操作するコマンドマクロ
各テーブルの定義ファイル(autoload/skk7/table/*.vim)は下のような感じにすることを目標にする。
これは autoload/skk7/table/rom_to_hira.vim だとする。
またSkkTableMapにbangを付けると、例えばユーザ側で.vimrcで定義されていたとしても上書きする。
SkkTableMap -table=rom_to_hira a あ SkkTableMap -table=rom_to_hira sa さ SkkTableMap -table=rom_to_hira -rest=x xx っ
rom_to_hiraとテーブルを指定しているのはコマンドには呼び出されたファイルは分からないため。
どうせ同じテーブルの定義が一度に指定されるのだし、事前に知らせておくのはいいかもしれない。
SkkTable rom_to_hira SkkTableMap a あ SkkTableMap sa さ SkkTableMap -rest=x xx っ
迷っていること
- 構文
- これらのコマンドをローカルにするかグローバルにするか
ローカルにする場合は、テーブルの定義ファイルの先頭に次のようにする。
call skk7#table#define_macro()
ディスパッチ関数で(特別に?)処理すべきキー入力
まだ多分いろいろ追加・削除されてくと思う。
- モードの切り替え (q Q l L /)
- Sticky key
- 大文字の英字
- モード切り替えのためのキーのエスケープ用キー (z)
- IMを有効にするキー (C-j)
ディスパッチ関数内の流れ
- (上で挙げた)特殊なキーの処理
- フィルタの処理
- 補完の処理
- 変換キーを押した時の処理は特に特別扱いしない
- 変換キーがスペースキーだったとしたら、変換キーを押した時にスペースが挿入されるか文字列が更新されるかはフィルタ次第
- 変換を確定する関数作る
モードのコールバック
- 特定のモードのみに送りたい場合はイベント発生よりもモードのautoload関数呼び出しのが効率や分かりやすさの点で優れている
- 分かりやすさ、というのは主にモード側のイベント登録などの処理の話。コールバック呼び出しなら関数を定義しておくだけ
- モードが変わった時
- 変わる前のモードのコールバック呼び出し
- 変わった後のモードを引数に渡す
- 変わった後のモードのコールバック呼び出し
- 変わる前のモードを引数に渡す
- 変わる前のモードのコールバック呼び出し
- モード用のキーを押した時
イベント
- あれ?要らない子じゃね?
- 一番よく書けたと思ったコードだったのに...ギリギリ
- モード以外にイベント的な処理するとこないしなぁ
- まぁautoloadなので置いておいても損はないけど、使わなくなるかも
大文字の英字とSticky keyの扱い
- Aは
(skk7-sticky-key)aと同じ
その他いろいろ
変換フェーズとそうでない場合にフィルタ用関数が返す文字列が、全部の文字列を返すのか一部の文字列を返すのか違う
- フィルタ用関数は「確定した」文字列を返す
- ディスパッチ関数が文字列の差分のみアップデートするなど工夫する
現在フィルタ側がバックスペースのキーコードを生成してるけど、これをディスパッチ関数にやらせる
- 具体的には、フィルタ用関数は返り値(skk7#返り値を抽象化する関数を用意)として「削除する文字数」と「挿入する文字列」をディスパッチ関数に返すことが求められる
- こうすることで、統一した文字列の更新が可能になる
- Anthyでは、変換フェーズにたくさんの文字列を入力すると、いちいち全ての入力が書き換えられてるように見えて、それが結構遅かったのでイライラしてた
予測変換
一文節だけならできる
(2月4日追記)
マッピングのコマンドマクロ
Skk7Map ; sticky Skk7Map z escape Skk7Map q zenei
これじゃ特殊なキーとモードの区別がつかない。
Skk7Map -type=sticky ; Skk7Map -type=escape z Skk7Map -type=mode q zenei
指定する引数はwordのみにすれば、次のように特殊なキーを保存することができる。
;: sticky z: escape q: mode-zenei
- type指定は必須。次のようなエイリアスもある。
Skk7MapSticky ; Skk7MapEscape z Skk7MapMode q zenei
テーブル用にはSkk7TableMap。詳しくは前回の追記を参照。
テーブル用コマンド
- ステートを持ってたり(前回ので言えばrom_to_hira)ややこしいので、テーブルの定義ファイル内の最後で削除するコマンドを実行する
- いやステートを上書きすれば問題ないよ
- ようするにテーブルの定義の前にちゃんと
Skk7Table table_name
とすれば問題ないという話
- それともやっぱり明示的にテーブル名を指定させる?
フィルタ関数が返す値
まだ完全に実装のイメージがつかめてない。
多分補完関数についてよく知らないからだと思うので調べる。
- 補完関数を見習う
- つまり返り値は文字列か詳しく指定する辞書型か
- 補完関数の返り値も含める?
- 遅い場合も考えて、遅延できるようにする
- 分けて処理することを伝えるキーと値を含める
メッセージキュー (autoload/skk7/msgqueue.vim)
abbrevモードでスニペット機能を実現
一時的なマッピング
一緒にしてみた
一時的なマッピングについてもう少し
決め事
- ブランチのフォーマットは
/<タイプ>/<好きな名前> - タイプは機能修正なら頭にfix、機能追加ならhack、それ以外なら何もつけない
ステート
- 活用の仕方が分からない
- せめて特定の文字はこっちのフィルタ関数で処理、とかそういうことができればうれしいんじゃないか
- skk7#register_filter_fn(mode, fn, chars)
- これじゃフィルタ関数は登録しないと呼ばれないみたい
- まぁデフォルトでfilter_main()以外は呼ばれないんだし合ってると言えば合ってるか
- 引数を辞書型にして、例えばabcdefgをfilter_abcdefg()で処理したいんだったら
mode: hira fn: abcdefg handle: abcdefg
みたいなのを渡すとか?
キーのおかげで分かりやすそう
- でも結局それmainからやればいいじゃん
- 無駄にディスパッチの処理を増やすのはしたくない
- 結論:ステート廃止?
- いや待てモードの中で変数にステートを保存しておくよりは関数で分けられるのは地味に便利なんじゃないの?
- いやだからそれmainからやればいいじゃん
- うーーーーーん
- 「入力中の処理の流れ/状態」の項の流れをディスパッチ関数で分けてくれるのはうれしい?
- そうでもない
- 例えば変換キーを押してひらがなから漢字に変換する場合はfilter_convert()でやりたいとする
- その場合 filter_main() でそれが変換キーかどうか見て、それからステートをいじって、一回目は自分で
filter_convert() を呼び出す必要がある
-
- だったら filter_main() でそのキーを見てからそれぞれのステート用の関数にディスパッチした方が分かりやすい
- とりあえず先延ばしにしておくか
フィルタ用ディスパッチテーブル
- 上で言ったようなステートの遷移をどのように表せばいいのか
state_a: b: state_b state_b: a: state_a
- モードなどは気が向いた誰かに追加していってほしい部分
- なのでこういうステートの遷移などが便利なようならヘルパー関数書いとく
変換フェーズ
フェーズとかステートとかモードとか、一応使い分けるよう注意してるんですよ。
決してかっこつけたいとかそういう訳じゃないです。決して(ry
- SKKには3つのフェーズがある
- ノーマルフェーズ
- 変換フェーズ
- 送り仮名の変換フェーズ
- とにかく変換フェーズか、そうでないか、では判断材料が足りない
- それぞれのステートを定数で表す
入力中の処理の流れ/状態
これはフローチャートにした方が分かりやすいな。
いろいろ見落としてる気がする。
- モードの中で行われるフェーズ
- 英字からひらがな等に変換中
- 変換後
- sticky keyや英字を押して変換する文字列の入力フェーズ
- もしもう一回sticky keyや大文字の英字キーを押した場合
- 送り仮名入力待ちフェーズ
- マッチしたので変換候補選択フェーズ
- あれ?それともいきなり確定されるっけ?
- 変換キーが押されて変換候補選択フェーズ
補完にもフェーズというかモードというかステートがある気がしてきた
- というかなんか:helpにそう書いてある
- autoload/skk7/complete/ に色んな補完関数を置く
- その中のスクリプトは、モードと同じでコールバックやいくつかの関数を用意する必要がある
- なかったら_default.vimにフォールバック?
- モードと補完は一対一であるべきか
- いや、googleでsuggestしたいじゃん
- それは変換キーが押された時のモードのコールバックとして用意すればいいんじゃない?
- 補完でもgoogle suggestありかも、DOS攻撃っぽくなるけどね
変換用API
- autoload/skk7.vimに実装
- フィルタは変換用のキーを自分自身でハンドルするべきか
- 次の文字列をそれぞれ相互に変換
- 英字
- ひらがな
- カタカナ
- 漢字
- n * (n - 1) 通りの変換関数を用意する必要がある?
- いや文字の種類はそうそう変わらんだろ、アホか
- 12個全部は実装無理っぽいかもしれない
ディスパッチ関数に渡す引数
- どこから呼び出したか
- キーは大文字か
その他
最近全然眠れてない。
(2月5日追記)
だんだんとイメージがつかめてきた。
どうやらちょっと難しく考えすぎてたようで、
プラガブルにしようと意識するあまり
テーブルとモードの対応をどうするか、
補完とモードの対応をどうするか、
などいろんなことを考えていた。
それは全部モードがグローバル変数でやればいいことで、
それでもしほしいAPIなどが定まってきたら
後々しかるべき場所に実装してけばいいことだった。
文字列の更新や非同期な呼び出しなどはディスパッチ関数でやりたいけど、
それ以外は全部モードに丸投げしたっていい。
モードは他の人に書いてもらいたい部分だったから
あまりモードを複雑にしない方がいいんじゃないかと考えていたが、
それはまだ今考えるべきことではなかった。反省。
ディスパッチ関数の引数
- 大文字かどうか
- 現在の状態をキーとともに保存するか
- Aの場合はAで保存しといて、aの時は保存しない
フィルタ関数
- 変換キーが押された時は別のフィルタ関数を呼び出す
- 現在のステートを廃止し、固定の名前でフィルタ関数を呼び出す
モードがcb_now_working()でtrueを返す場合の特殊なキーの挙動
- フィルタ関数にそのまま渡す
- 挙動がちょっと違うけど、各モードで使えるキーは最大限使わせる
- qでカタカナに変換するのはhira.vimがやればいいよね
フィルタ関数に渡す引数
- 現在のcolとline( getpos() ?)
- 引数が多ければ...で省略すればいいじゃん
- autoload関数で取得させるのは実質その変数はグローバルなのであんまりよくない
フィルタ関数の戻り値
- 現在のバックスペースによる返り値も許容する
- 長い文字列を置き換える場合
- 返り値に挿入でなく置き換えであることを明示する値
- replaceってキーに始点のgetpos()の結果を保存しとく
コールバック
- skk7#sticky_key()から呼び出す
- skk7#init_keys()から呼び出す
マッピングテーブル
非同期
- CursorMovedI
- CursorHoldI
- vimproc
- +clientserver
- 制限について調べる
- Vimサーバ
keymap
補完はモード側でやるかディスパッチ関数側でやるか
- これは実際Shougoさんにコードを見てもらって、どの時点で補完関数を呼び出せばいいのか訊く
変換候補
- 候補は数字で選択できる
- 数字を入力するときは?
- prompt.vimがインストールされていれば、プロンプトでも選べる
変換用関数
- 変換用関数もモード側にあればそっち使うべき?
ギャル文字変換テーブル
- ・・・
「多く」「大く」
独自マッピングテーブルはやめる
- 引数でどこから起動したか判断すれば、独自のマッピングテーブル持つことないような
- 補完関数のfindstartみたいな感じ
- そもそもarpeggio使えないじゃん!
(2月7日追記)
変換フェーズ・変換バッファテーブル
- 変換フェーズごとにバッファを持っている
- (正確ではないけど)Sticky keyなどを押すたびに変換フェーズが変わる
- バッファはテーブルで分けられてるので大文字で保存する必要はない
- 変換する時は変換関数にテーブルごと渡せばいい
- 変換関数はデフォルトでg:skk#henkan_buf_tableを使う
- モードのがあればそっち使う
- どんなケース?
- 現在の変換フェーズの状態をstatuslineに表示できたらいいかも
- 送り仮名は母音が入力されたら変換関数に渡す
- 他にも変換キーが押されたら変換する
- これらの処理はfilter_henkan()で行われる
#cb_ってプレフィックスいらなくね?
#フィルタ関数だってコールバックだし
- ディスパッチ関数でバッファテーブルに入れていって、マッチしたらクリアの方がシンプルな気がする
- それに再描画も簡単
- 文字列の削除なども簡単
- バッファテーブルを弄って、マッチしたらクリア
- これっきゃない
- フィルタ関数の返り値はどうする?
- 関数で抽象化
- throwしたっていい
- 具体的には次のことが返り値か例外で知らせることができればいい
- バッファテーブルの再描画する部分
- 挿入する文字列
- フィルタ関数実行中に変更されたバッファテーブルを監視していれば挿入する文字列だけでもいい
- 返り値か例外って言ったけど、返り値は一切無視して関数で伝えてもいいんだよね
- 関数で抽象化
- 辞書登録モードもバッファテーブルを用意して同じようにできたらいいな
- 一応マーカーも設定できるように
- g:skk7_marker_henkan
- それぞれのバッファテーブルは配列の辞書型にする
- 文字列の他に位置も覚えておく必要がある
- 前の追記でgetpos()とか言ってたやつ
- フィルタされた文字列も含める
モードの再確認
- 各モードは英字から何かへの変換、何かからまた違う何かへの変換をするもの
- hiraモードだったら英字からひらがな、ひらがなから漢字
- 最初の変換はシームレスに行われ、次の変換はSticky keyか大文字の英字を押すことで文字列を受け付ける
- 2回目の変換の時に入力される文字列は最初の変換で入力される文字列と同じ
- そこで変換キーが押されるか、新たにSticky keyか大文字の英字を押して送り仮名の母音を入力するとfilter_henkan() が呼ばれる
- 現在はどの変換フェーズでもfilter_main()を呼び出してるけど、今後どうするか
- もういっそfilter_main()はfilter()にして、変換キーが押されたらhenkan()を呼ぶのはどうか
- 引数は同じ?
- バッファテーブルを渡した方がいいんじゃないか
- skk7.vimのバッファテーブルがグローバルなのはどうなんだろう
- ローカルにして、それぞれゲッターとセッターを用意する
- テーブルはモード固有のがあれば使う?
- どういうケース?
モード固有のマッピング
- 例えばhiraモードでqがカタカナへの変換になるとか
- ユーザがいじれるとうれしい
- 変わる前と変わった後にユーザが特定のコマンドを実行できればいいのか
- モードに入ったらarpeggioでマッピングとか
- モードを去る時にちゃんと後始末するコードも書く必要がある
- それくらい単純な方がユーザにとっても分かりやすいような気がする
ユーザが登録できるイベント
- フィルタが呼び出されるコールバックとは別
- event.vim使い回す?
- autocmd likeなコマンドマクロ
- 投げるイベントはmode-モード名-enterとかにすればいいんじゃないか
モード側がやるべきこと
モードから呼び出す補完について
- モードの補完関数はグローバル変数で決め、ローカルな値
- CursorHoldIよりCursorMovedI的な挙動の方がうれしいらしい
- 引数はVimのomnifuncで設定する関数と同じでいいんだろうか
- neocomplcacheとの連携
変換フェーズ
- ちゃんと名前があったみたい
- ノーマルモードが■モード
- 変換モードが▽モード
- 送り仮名モードが▼モード
- Qをひらがなの最初の位地で押せば▽モードに入れるらしい
- ▽モードを抜けるてマーカーを消すには
- ▽モードを抜けてかつマーカーもマーカー以後の文字も消すには
変換文字列などのシンタックスハイライト
q
「かなモード」、「カナモード」間をトグルする。
l
「かなモード」または「カナモード」から「アスキーモード」へ。
L
「かなモード」または「カナモード」から「全英モード」へ。
C-j
「アスキーモード」または「全英モード」から「かなモード」へ。
特殊なキーとして実装するんじゃなくて、モードのローカルなマッピングとして実装した方がいい気がする。
もちろんarpeggio対応!
辞書を編集
- もちろんVimはエディタなので辞書編集にも最適な機能を発揮する
独自のマッピングテーブル
ドキュメント
- skk7.vim
- モードごと
SKKって中国語とかハングルとかも変換できないかな?
- ノーマルフェーズで部首を表す漢字に変換
- 変換フェーズではその部首を組み合わせた漢字にする
- SKK最強じゃね?
- ノーマルフェーズで細かい文字(ひらがな等)を入力、変換フェーズやその他のフェーズで複雑な文字に変換
- 英字からの変換はテーブルを使って、それじゃ表現しきれない文字についてはどっかにリクエスト送ったりほげほげして複雑な文字にすると
- 複雑な文字からより複雑な文字へ変換することも考えられるけど、そんな複雑な文字地球上に存在するの・・・?
- 宇宙言語対応
- 中国語のこととか考えてみると
(2月12日追記)
Emacsの非同期プロセス?
複数のモードを併用するとかしたい
- 例えば
- ディスパッチ関数を再帰的に呼び出す
- いややっぱり一つのモードごとに文字列の再描画とかしてたら遅いので、順番にモードのフィルタを適用していくだけ
- モードに引数も与えるには:を使う
- 引数の受け取り方
- フィルタ関数に渡す
- モードのautoloadの変数に代入する
- 基本的に何か渡したかったらこの2つが速度的に速い
- autoload/skk7.vim側の変数や関数はあまり増やしたくはない
- statuslineの表示が長くなるな
- 現在のモード切り替えのマッピングでskk7#set_mode()するとtable:dvorakが解除されてしまう
- 1つのメジャーモードといくつかのマイナーモード
- 置き換えられるのはメジャーモードだけ
- skk7#set_major_mode()
- 1つのメジャーモードといくつかのマイナーモード
- 補完とかもモードの後ろか前にくっつければいいんじゃないか
- 補完のメニューを出すだけで、文字は変更しない
- 各モードは「環境」と思っていたけど、こうするとモードは独立した機能をそれぞれ提供してパイプのように挿入する文字列などを決めていくことになる
- 各モード間でやりとりしたいときは?
- それはどんなケースか
- 小さい機能をそれぞれやっていくのはUNIXのコマンドライン文化だけど、Emacs、Perlなど例外はある
- Vimの中でシェルを動かす時代ですよ
- こういう「小さいことはいいことだ」のアプローチの利点は選択肢がかけ算的に増えて行くこと
- Perlみたいにいくつかの機能をまとめるならその時一つのモードにまとめればいいじゃない
- 非同期処理(spawn)はコマンドラインの&みたいなもんか
- 非同期処理もモードでやるとか?
- もしモード文字列に複数のモードを設定できるようにするなら、そうした方がいい
- (今考えてる形だと)ディスパッチ関数側でやると一つのモードごとにspawnしてしまうから
- 各モード間でやりとりしたいときは?
メジャーモード・マイナーモード
- Emacsと同じで現在のメジャーモードは一つのみ
- マイナーモードはキー配列の切り替えや補完など補助的な機能
- メジャーモードの切り替えはskk7#set_major_mode()
- 変換キーなどいくつかの処理はメジャーモードにのみ任される?
- メジャーモードはmajormode、マイナーモードはminormodeでそれぞれautoload分ける
モード
キー配列
- AZIK
- KZIK
- @ohacさんが作ったっぽい?
- http://ohac.pun.jp/old_pages/kzik.html
- http://ohac.sytes.net/tdiary/
その他
- マッピングテーブルになかったらfeedkeys()で元のコマンド実行したりできない?
- モードを選択するコマンドの補完
- znを「ん」に割り当てたら結構nが続く場合でも押しやすい気がする
- バッファ文字列もフィルタ関数に読み取り専用で渡す
- :lockvar
- 現在は関数で取得したりしてるけどそれよりも引数で渡した方が速度やスコープの範囲の点でいい
- マイナーモードはEmacsではどのように設定されるのか
- メジャーモードに「設定してもらう」?
- それともメジャーモードになった場合の「イベント」?
(2月15日追記)
スティッキーキーの扱い
予測変換の補完
- それぞれomniで表示
- 変換した部分のみ
- 送り仮名まで
- その後のひらがな全部
- 一文字ずつ全部リストアップする?
- ノーマルフェーズで打ち込んだ文字も保存しておく?
- モードが保存する?
- 補完関数のモードとのやりとりが面倒
- autoload/skk7.vimが持つ
- モードが保存する?
確定
テーブルが持つ要素
- それぞれのフェーズが開始された位置
- 書き換える時マーカーに頼らない
- 変換フェーズの文字列
- 送り仮名
- フィルタされた文字列?
- 挿入する文字列
- マーカーなども含めるべきか
- 補完のモードのことを考えるとそうすべきじゃない
- 含めてないのと両方持つべき?
- マーカーなども含めるべきか
- これに加えて「描画するけど変換などのためではないバッファ」もあるといい
- かな入力の濁点とかは直前の文字を変化させる。だったら直前の文字を調べるより、「作業中」ということでテーブルの上に置いとけばいい
- 直前の文字列なんかも調べる関数があればいい
- もちろん位置も保存する必要がある
テーブルが持つバッファが持つ情報
- 文字列
- 描画する最初の位置
- 描画するか否か
テーブルは配列で、これらの各要素は辞書型。
ードの順番の構造
- 配列
- マイナーモードは文字列
- メジャーモードは数値
- 順番はs:modes_order
- メジャーモードはs:major_modes
- マイナーモードはs:minor_modes
- s:modes_orderの中でメジャーモードはs:major_modesの添字である数値で、マイナーモードはそのままの文字列で保存される
モードの登録
- メジャーモードとマイナーモードで分けるべき
- そうしないとautoloadのパスの解決に失敗する
マッピングテーブル
- arpeggioなどのことも考えてオプションなどを取れる余地を残す
補完用のモード
モード
- Google Suggest
- Googleの「もしかして」
- スペル修正に最適
- Google先生の偏見有り
その他
- インクリメンタル検索の途中なんかだと「▽」などのマーカーはうざい
- モードごとにマーカー文字列は指定できるようにする?
- デフォルト値とモードに特化した文字列(辞書型)
- 携帯風の配列にすれば片手でキーボードが操作できる・・・!
(3月1日追記)
確認用。
いくつか確認
- 関数は挿入する文字列をいじるとその文字列が挿入される
- ノーマルモードじゃなかったらフィルタされた文字列を保存する
- バッファしてる文字列をモードがローカルに持つとマイナーモードが補完などをできない
- でもバックスペース時に英字とフィルタされた文字列の両方を「一文字」削除するのはどこまですればいいのか曖昧などといった問題があるため、次のようにする
- 補完はフィルタされた文字列に対して行う
- 子音などのまだひらがなに変換されてないものはモードが管理する
- 「フィルタされた文字列」はバッファテーブルが持つ
何をバッファすればいいのか
- ひらがなに変換中のローマ字
- 漢字に変換中のひらがな
- スティッキーキーが押されたら保存する
- 補完に使えるはず
ただモードのバッファ用にするんじゃなくて、補完用のマイナーモードで使えるためにバッファの用途を仕様化する必要がある。
ひらがなを見るモードもあればローマ字を見るモードも作れる。
「ひらがな」「ローマ字」の順に表示される。
2つはバッファ文字列が持っていて、それぞれのフェーズごとに用意される。
テーブル上で現在のフェーズより小さい添字の全てのバッファ文字列が、間にマーカーなどを置かれて挿入される。
また2つに分かれているのはモードがいじりやすいため。
hiraモードでは、ひらがなに変換中の文字列はローマ字のバッファに入れられて、変換されたひらがなはひらがな用バッファに入れられる。
asciiモードでは、ローマ字のバッファしか使われない。
ノーマルモードだったらひらがなのバッファは挿入された後finallyでクリアされる。
これはoptにクリアするかのフラグを持たせて、モードにフラグを立たせる。
それかもっと汎用的にfinallyで実行する関数とかexコマンドを入れてくとか。
ただ難点はクロージャが使えないので、元の値を「参照」して変更することができない。
と思ったけど、C++0xのクロージャの実装みたく、変数とそれに束縛されている値(つまり環境)を実行する時に復元すればいいのか。
Vimでクロージャってできるかも。C++の参照はポインタの糖衣構文で、コンパイラの助けがいるのでできないとしても、ポインタは1要素のリストとして代用できる。
文字列を書き換える
例えば「さ」を入力したい時、aがフィルタ関数に送られた時に、直前に挿入された「s」を削除する必要がある。
この場合「s」はバッファ文字列にあるので、バッファ文字列の「s」を削除したあと、その文字数分だけバックスペースのキーコードを送ればいい。
バックスペースキーの仕事
- 現在のバッファ文字列
- ローマ字
- ひらがな
- 下位フェーズのバッファ文字列
- ....
と見ていって、文字列が空でなかったらその文字列の最後の文字を削除して、挿入された最後の文字も削除するためにバックスペースのキーコードを返す。
全部空文字だったらただバックスペースのキーコードを返すだけ。
バックスペースなどの処理はautoload/eskk.vimのデフォルトフィルタの関数に戻す。
バッファ文字列
- マーカー?
- スティッキーキーによってセットされる
- ひらがな
- ローマ字
- 変更されたかのフラグ
バッファテーブル
- フェーズごとのバッファ文字列
- バッファ文字列が変更されたり、まだ作られていなかった場合は、挿入する文字列を生成する
- 挿入する文字列はfinallyでクリアされる
- 変更された点を見つけるためのテーブルの複製
モードが(最低限)すること
- ローマ字→ひらがな
- 変換の処理
あとはモード側で変換フェーズを見て、ノーマルフェーズでなかったらデフォルトフィルタを呼び出す。
マイナーモード?
- 'pre_filter'とか'post_filter'とかってメジャーモードの前後に実行するフィルタ関数のモードを登録できる形にする
その他
(3月2日追記)
だんだんと記録用になってきた気がする...
ディスパッチ側: 文字列の書き換え
- 文字列が変更されていたら先頭に戻り表示されている文字列を書き換える
- 改行を含んでいる場合、`
`で削除できるかは`&backspace`に依存する - `setline()` ?
バッファテーブル: 操作の抽象化
- 現在のバッファ文字列が空文字で、セットする文字列が空文字じゃなかったらgetpos()する
- 補完との協調
- バッファ文字列の先頭の位置は補完関数が使える形にする
- 確定(?)のための関数名
- バッファ文字列
- `clear()`
- バッファテーブル
- `clear_all()`
- バッファ文字列
- モードから簡単には変更できなくする
- (モードに渡している間は)lockvarされるべきもの
- バッファ文字列の先頭の位置
- バッファテーブル(配列か辞書)
- メソッドはunlockvarしてから操作を加える
- (モードに渡している間は)lockvarされるべきもの
(3月4日追記)
モード
マイナーモードとかメジャーモードとか区別しない。
- 現在のモードの前に呼び出すモード
- 現在のモード
- 現在のモードの後に呼び出すモード
ってな風に呼び出していって、補完を出すなり、文字列の変換なりすればいい。
あと例としてだけど、capslock機能とか。
文脈を判断したautofill
実装するかはともかく、実装可能にしたい。
入力中に折り返されても(改行が入っても)変換を可能にしたい。
- 表示する時に挿入する文字とその位置を指定できるようにする
- バッファ文字列のローマ字やひらがなへの削除や追加の情報はバッファ文字列が持って、書き換えの際にそれらの変更が挿入する文字列に加えられる
ローマ字からひらがなへ変換する際の挙動
ローマ字からひらがなへ変換する際の挙動 2
statuslineに表示するモードの文字列
- モード名じゃつまらない
漢字の意味をプレビューウインドウで表示
プレビューウインドウやポップアップは一度のキー入力に対して一つのモードしか使えない。
プレビューウインドウなら出力が複数行に渡った場合、:vsplitしてそれぞれ異なった行を見せることでスペースを稼げる。
でもプレビューウインドウってバッファみたいに分割できるんだろうか。
そもそもどういうものなのかもあまりよく知らないので調べる。
バッファでいい気がする。
バッファなら上みたいに複数ウインドウを作れば何個でも情報を表示できる。
確かpythonのomni補完がそんな感じで情報を表示してたような気がする。
他言語インターフェース
ソケット使って他言語でもモードを書けるようにする。
フォーマットはJSONとYAMLどっちにしよう。
と思ったけどHTTPで通信するならヘッダでフォーマットを指定すればいいのか。
あと値を変更したら、その値を返してくれないと困る。
Vimでパースの簡単さを考えるとJSONだろうか。
(JSONはVimスクリプトとの親和性が高いってid:mattnさんが言ってたけどeval()すれば読み込めるんだろうか(辞書とリストだけなら))
現状モードの仕様はフィルタ関数に引数で渡したものをいろいろ変更して処理を進めていくので、Vimスクリプトの場合は辞書とリストは「参照」なのでいいけども、JSONなどにデコードしてしまったら当然元の変数は「参照」されない。
あとバッファテーブルやバッファ文字列をいじるための関数はそれ自身に含めない方がいいな。
関数群は別に作って、new()の時にバッファテーブルを保存するといいかも。
あとあとやっぱりキーの名前は下線付きでない方がいい。
他の言語でもいじるんなら。
もともと関数から操作してほしいって意図でそうしたけど。
この機能といいvimprocが実用段階になったら色々できるのでvimprocの開発にも参加したいところ。
とりあえずskk.vimの機能をある程度実装できたら参加することにしよう。
ただ今LLに浸かりきってるのでCいじったら脳が爆発する気がする。
Google IME
多くのIMEは、ユーザーの入力から学習して、文節の区切りや単語のランクを変更する。ところが、この学習が愚直で、大昔にただ一度だけ入力した変な変換結果を、そのまますべての変換に適用し、その結果、学習すればするほど、おバカになっていくということが、ままある。
SKKにはあまり関係のないことかもしれないけど、
例えば補完で候補を選択するならその候補を削除とかできるとうれしいのかも。