新エンジンのキーマップ設定

まだアルファ版もリリースできていない新エンジンですが、キーマップ設定の自由度が向上しています。AquaSKK スレに書けるほど短かくまとめられないので、このあたりの仕様変更について簡単に書いておきます。

基本的な考え方

新エンジンでは、全てのキーが特定のイベントと結びついています。今のところ、以下のイベントを定義しています。

これ以外に、疑似イベントとして SKK_CHAR があります。SKK_CHAR は上記イベントのどれにもあてはまらない全てのキーがマップされるため、明示的にキーを割り当てることはできません。そのかわりに、SKK_CHAR を細分化するための「属性」が用意されています。属性の一覧は以下の通り。

  • ToggleKana
  • ToggleJisx0201Kana
  • SwitchToAscii
  • SwitchToJisx0208Latin
  • EnterAbbrev
  • EnterJapanese
  • NextCompletion
  • PrevCompletion
  • NextCandidate
  • PrevCandidate
  • RemoveTrigger
  • UpperCases
  • Direct

一つのキーに複数の属性が付与されることもあります。例えば大文字の X には RemoveTrigger 属性と UpperCases 属性が付与されます。これらの属性は、単語削除や見出し語入力等の入力コンテキストに応じて評価されます。

キー設定文法

識別子          キー割り当て

識別子はイベント名か属性名のどちらかです。キー割り当ては "||" で好きなだけ繋げることができます。キー割り当て表記はやや複雑で、「0 個以上のキーラベル + キー」という形式になっています。キーラベルには以下の種類があります。

  • 単純モディファイア(Shift、Ctrl、Alt、Meta の指定)
  • 16 進表記(表示文字以外の ASCII コードか、生のキーコードのどちらか)
  • 文字集合

これだけではイメージがつかめないと思うので実際の設定ファイルを見てみます。"#" で始まる行はコメントです。

###
### keymap.conf
###

# ======================================================================
# event section
# ======================================================================

SKK_JMODE		ctrl::j
SKK_ENTER		group::hex::0x03,0x0a,0x0d||ctrl::m
SKK_CANCEL		ctrl::g||hex::0x1b
SKK_BACKSPACE		hex::0x08
SKK_BACKSPACE		ctrl::h
SKK_DELETE		hex::0x7f||ctrl::d
SKK_TAB			hex::0x09||ctrl::i
SKK_PASTE		ctrl::y
SKK_LEFT		hex::0x1c||ctrl::b
SKK_RIGHT		hex::0x1d||ctrl::f
SKK_UP			hex::0x1e||ctrl::a
SKK_DOWN		hex::0x1f||ctrl::e
SKK_PING		ctrl::l

# ======================================================================
# attribute section(for SKK_CHAR)
# ======================================================================

ToggleKana		q
ToggleJisx0201Kana	ctrl::q
SwitchToAscii		l||keycode::0x66
SwitchToJisx0208Latin	L

EnterAbbrev		/
EnterJapanese		Q||keycode::0x68
NextCompletion		.
PrevCompletion		,
NextCandidate		hex::0x20||ctrl::n
PrevCandidate		x||ctrl::p||shift::hex::0x20
RemoveTrigger		X

UpperCases		group::A-K,M-P,R-Z

Direct			group::keycode::0x41,0x43,0x45,0x4b,0x4e,0x51-0x59,0x5b,0x5c,0x5f

SKK_CANCEL を見ると ctrl::g||hex::0x1b となっています。これは Ctrl-G と ESC を割り当てる設定です。SKK_BACKSPACE の設定は二行になっていますが、これは hex::0x08||ctrl::h と同じように評価されます。

ASCII 以外のキーを割り当てるなら keycode:: ラベルを指定します。SwitchToAscii 属性では小文字の L と keycode::0x66 で英数キーを割り当てています。

文字集合を定義するには group:: ラベルを指定します。文字集合では "," でキーを連結し、"-" で範囲を表現します。UpperCases 属性の group::A-K,M-P,R-Z は、L と Q を除いた英大文字集合です。

キーラベルの順番は自由ですが、単一のキーに hex:: と keycode:: を同時に指定することはできません。つまり、

SKK_CANCEL    hex::0x1b||keycode::ctrl::group::alt::0x66,0x68

は OK ですが、

SKK_CANCEL    keycode::hex::0x1b

は NG になります。また、shift:: ラベルは表示可能な文字に対しては不要で、大文字の A を表現するのに shift::a などとする必要はありません。使いどころとして想定しているのは、ファンクションキー等との組み合わせです。上記の例では、PrevCandidate 属性に Shift-SPACE が割り当てられています。

互換性を捨て、冗長にした理由

TSM 版 AquaSKK のキー割り当て文法は Emacs 由来の "\C-j" といったものですが、ここに 16 進表記や生のキーコード、文字集合を導入しようとして挫折しました。Emacs 風を維持するのが困難だったのです。それならいっそのこと、新しい文法を作ったほうが良いと判断しました。

リリース時には移行用のスクリプトを用意する予定です。

EBNF による表記

せっかくなので、EBNF を書いてみました。EBNF を書くのは初めてです。様々な方言があるようですが、ここでは以下の文法を使っています。

  • ( ) でグルーピング
  • [ ] はオプション(0 回か 1 回)
  • { } は 0 回以上の繰り返し
syntax  = { line }
        ;

line    = ( empty | comment | rule ) "\n"
        ;

empty   = { blank }
        ;

comment = "#" { any_character }
        ;

rule    = identifier blank keymap [ blank comment ]
        ;

identifier
        = event_id | attribute_id
        ;

blank   = ( " " | "\t" ) { blank }
        ;

keymap  = entry { "||" entry }
        ;

event_id= "SKK_JMODE"
        | "SKK_ENTER"
        | "SKK_CANCEL"
        | "SKK_BACKSPACE"
        | "SKK_DELETE"
        | "SKK_TAB"
        | "SKK_PASTE"
        | "SKK_LEFT"
        | "SKK_RIGHT"
        | "SKK_UP"
        | "SKK_DOWN"
        | "SKK_PING"
        ;

attribute_id
        = "Direct"
        | "UpperCases"
        | "ToggleKana"
        | "ToggleJisx0201Kana"
        | "SwitchToAscii"
        | "SwitchToJisx0208Latin"
        | "EnterJapanese"
        | "EnterAbbrev"
        | "NextCompletion"
        | "PrevCompletion"
        | "NextCandidate"
        | "PrevCandidate"
        | "RemoveTrigger"
        ;

entry   = single_entry | group_entry
        ;

single_entry
        = { modifier } ascii_entry | hex_modifier hex_entry
        ;

group_entry
        = group_modifier ascii_group_entry | hex_group_modifier hex_group_entry
        ;

modifier= "shift::" | "ctrl::" | "alt::" | "meta::"
        ;

ascii_entry
        = base_char | range_char
        ;

hex_modifier
        = { modifier } ( "hex::" | "keycode::" ) { modifier }
        ;

hex_entry
        = [ "0x" | "0X" ] hex_char [ hex_char ]
        ;

group_modifier
        = { modifier } "group::" { modifier }
        ;

ascii_group_entry
        = ascii_range { "," ascii_range }
        ;

hex_group_modifier
        = group_modifier hex_modifier | hex_modifier group_modifier
        ;

hex_group_entry
        = hex_range { "," hex_range }
        ;

base_char
        = digit | letter | symbol
        ;

range_char
        = "," | "-"
        ;

hex_char= digit
        | "a" | "b" | "c" | "d" | "e" | "f"
        | "A" | "B" | "C" | "D" | "E" | "F"
        ;

ascii_range
        = base_char [ "-" base_char ]
        ;

hex_range
        = hex_entry [ "-" hex_entry ]
        ;

digit   = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
        ;

letter  = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j"
        | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t"
        | "u" | "v" | "w" | "x" | "y" | "z"
        | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J"
        | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T"
        | "U" | "V" | "W" | "X" | "Y" | "Z"
        ;

symbol  = "!" | "@" | "$" | "%" | "^" | "&" | "*" | "(" | ")" | "_"
        | "=" | "+" | "\" | "|" | "`" | "~" | "[" | "]" | "{" | "}"
        | ":" | ";" | "'" | '"' | "<" | ">" | "." | "/" | "?"
        ;

any_character の定義をサボってますが、要するに全ての文字です。感覚的には簡単な文法だし、手書きのパーザーもシンプルなのに、EBNF は結構な量になって面倒ですね。