emacs でのキー入力の表現方法

概要

Emacsキーバインドを変更するときに, Control キーや Meta キーを等の修飾キー含むキー入力 "C-a", "M-a" は修飾キーをエスケープして, "\C-a" や "\M-a" のように表記する.
しかし, Shift 等を含んだキー入力を指定する際に, 直感に従って

(global-set-key "\C-\S-a" 'backward-char)

のように "\C-\S-a" を文字列として渡してもうまくいかない.
このため, 複雑なキー入力を指定する際に, global-set-key に何を渡せばよいのかが途端に判らなくなる.
例えば, "C-S-a" (Control + Shift + a) をバインドするときは, [?\C-\S-a] を渡せばよいのだが, このように表記が異なる理由をきちんと理解していなかったので, いつも混乱してネットに頼る羽目になっていた.

この混乱を解消すべく, キー入力の表現方法についてきちんと調べたのでまとめておく.

簡単なキー入力の指定方法

内部的なことは気にせずに, 単に簡単にキー入力を指定したいだけなら, 1つ目の参考サイトを見ればよい.
1つ目のサイトに書かれているように, emacs には kbd というマクロがあり, kbd マクロを介することで, エスケープなしの通常の表記 "C-S-a" によって, キー入力を指定できる.
例えば, シフトを含む場合でも

(global-set-key (kbd "C-S-a") 'backward-char)

のように表記すればうまくいく.
キーボード入力と文字列の対応が判らなければ, C-h k (M-x describe-key) の後にキーを入力してみれば対応がわかる.
なので, C-h k で調べた文字列を単に kbd マクロを介して渡してやれば, それで事足りる.

キー入力の内部での取り扱い

global-set-key は第1引数として, vector 型か string 型の文字列を受けとる.
つまり, "\C-a"という表記と [?\C-a] という表記が許されている.
"\C-a" は string型 で, [?\C-a] という表記は char を1つ格納した vector型 である.
vector は角括弧 [] で表され, char は ? とそれに続く文字で表されることを思い出すとよい.
複雑なキー入力を表すために, 簡単な string 型の表記ではなく, vector 型の表記を使わなくてはならないことになる.
このような事情を理解するためには, emacs の文字や文字列の取り扱いを把握する必要がある.

char 型について

emacs では char 型は整数と全く同じである.
?a も ?\C-a も ?\M-a も ?\C-\S-a も1つの整数を表している.
つまり, 修飾キーつきのキー入力そのものが1つの文字とみなされる.

?a        ; =>  97
?A        ; =>  65
?\C-a     ; =>  1
?\M-a     ; =>  134217825
?\C-\S-a  ; =>  33554433

emacs において, 文字列内, バッファ内, ファイル内の文字は 0 から 524287 までの範囲, すなわち22ビット長に制限されていてる.
(?\M-a や ?\C-\S-a はこの範囲に収まっていないことがわかる.)
22ビットのうち, 0から127までの7ビットで表される文字はASCIIコードと呼ばれる.
アルファベット, 改行・タブ等の特殊文字, コントロール + アルファベットなどターミナルの入力として許される文字がASCIIコードに含まれる.
上記の例では,`?a'と`?A'と`?\C-a'が ASCII コードに含まれる.
`?\C-a'のようにエスケープを含むASCII文字はASCIIコントロール文字と呼ばれる.
(Control キーを含むからコントロール文字というわけではない.)

ASCIIコード以外の文字は非ASCII文字と呼ばれる.
さらに, キー入力を表す文字は22ビットよりも長いビットで表され, 23ビット目以降が Meta, Shift 等の修飾キー入力を表すフラグとして用いられる.

ASCIIコントロール文字

`?\C-a'などのASCIIコントロール文字は7ビット以内で表現されることには, 注意が必要である.
また, ASCIIコントロール文字は大文字と小文字を区別できないことにも注意したい.
さらに, ASCIIコントロール文字は`\C-a'の代わりに`\^a'とも書ける.

?\C-a    ; => 1
?\C-A    ; => 1
?\^a     ; => 1
?\^A     ; => 1

キーボード入力を表すときは`\C-'を用い, バッファ内の文字を表すときは`\^'を用いることが推奨されている.

修飾キー

キー入力を表す文字は 23 bit 目以降のフラグを用いて, 修飾キーを表していると書いたが, それぞれのキーが何 bit 目をフラグとして用いているかをまとめておく.
(emacs reference manual では "Meta は 2**27 bit を使う" という風に書かれているが, これは 2の27乗を表す 28 bit 目が Meta キーに対応するという意味である)

  • Meta (\M-): 28 bit 目
  • Control (\C-): 27 bit 目
  • Shift (\S-): 26 bit 目
  • Hyper (\H-): 25 bit 目
  • Super (\s-): 24 bit 目
  • Alt (\A-): 23 bit 目

上記のリストの通り, Control は 27 bit 目をフラグとして使っているが, このフラグを用いるのは`?\C-1'のような非ASCIIコントロール文字だけである.
`?\C-a'のようなASCIIコントロール文字は, 27 bit 目を用いないので注意したい.

?\C-a ; => 1
?\C-1 ; => 67108913

結局, キーボード入力を表す文字は, "ASCII文字" + "修飾キーフラグ" で表されることになる.
注意すべきは専らControlキーを含むASCIIコントロール文字である.
ASCIIコントロール文字における Control キーは修飾キーとは見なされない.
例えば, `?\C-\M-a' と書いても `?\M-\C-a' と書いても, "ASCII文字(\C-a)" + "修飾キー(\M-)" と解釈される.

参考までに, 非ASCIIコントロール文字を評価した結果を示しておく.
対応する bit フラグが立っていることがわかる:

?\A-a    ; => 4194401
?\s-a    ; => 8388705
?\H-a    ; => 16777313
?\S-a    ; => 33554529
?\M-a    ; => 134217825
?\M-\C-a ; => 134217729
?\C-\M-a ; => 134217729

結局, char型はフラグを用いることで, すべてのキー入力が表現できるので, それを, vector に格納して渡せば一般のキー入力を指定できることになる.

string 型について

char 型の説明内でも少し触れたが, 基本的に string 型に含まれる文字として許されるのは, 修飾キーを含まず 22 bit 以内で表される文字である.
なので, コントロール文字として許されているのは ASCII コントロール文字だけということになる.

このルールに従えば "\C-a" は OK だが "\M-a", "\s-a" 等は文字列として許されないということになる.
しかし, キーバインドを設定する際に "\M-a" という表記はよく使う.
これは, string 型で "\M-a" という表記をした際には 8bit 目を立てることでMeta キーを区別できるようにするという例外があるためである.

(string-to-char "\M-a") ; => 225

例外として立てられた 8bit 目は define-key (global-set-key の内部で呼ばれる)
や lookup-key などで, 正しく Meta キーとして認識され, 適切に変換される.
このような例外があるのは Meta キーのみで, 残りの修飾キー(Super等)を文字列として表すことはできない.

結局, 文字列として指定できるキー入力は

  • ASCII文字
  • Meta キーのみを修飾キーとする ASCII文字

の2種類だけということになる.

例を挙げておくと

  • "\C-a", "\M-a", "\M-\C-a" はOK
  • "\C-1", "\s-a" はダメ

ということになる.

キーシーケンスの指定について

"C-x a"のように複数のキー入力を含むキーシーケンスにコマンドを割り当てる方法について述べる.
キーシーケンスの場合もキー入力として, Control (ASCIIに限る) と Meta 以外の修飾キーをもつような入力は string 型では表現できないことになる.

string を用いた指定方法

次のように, 区切り文字なしでキー入力を続けて表記すればよい.

(global-set-key "\C-xa" 'backward-char)
vector を用いた指定方法

char 型のキー入力を順番に格納した vector を渡せばよい.

(global-set-key [?\C-x ?a] 'backward-char)

kbd マクロについて

結局 kbd マクロはキーシーケンスを表す文字列を, global-set-key に渡すための適切なオブジェクトに変換してくれるマクロである.
適当に, ASCIIコントロール文字や非ASCIIコントロール文字を含むキーシーケンスを与えて評価すると, 以下のようになる:

(kbd "A")       ; => "A"
(kbd "C-a")     ; => "\^A"
(kbd "M-a")     ; => [134217825]
(kbd "M-C-a")   ; => [134217729]
(kbd "C-x a")   ; => \^Xa"
(kbd "C-x M-a") ; => [24 134217825]