(Emacs) Font Lockのルールを追加する

前のエントリーからの続きである。

Font Lock modeの追加ルールを設定するには、font-lock-add-keywordsという関数を使う。ルールは、正規表現のパターンとそれにマッチする部分に適用するface、の組み合わせとして定義する。モード毎の設定が基本だが、現在のバッファのみの設定もできる。

font-lock-add-keywords関数の書式は、

(font-lock-add-keywords モード名 ルールのリスト)
である。モード名をnilにすると、現在のバッファに対する設定になる。
ルールの書式はいくつかの種類がある。
  • a. MATCHER
  • b. (MATCHER . SUBEXP)
  • c. (MATCHER . FACENAME)
  • d. (MATCHER . HIGHLIGHTER)
  • e. (MATCHER HIGHLIGHTER1 HIGHLIGHTER2 ...)
  • f. (eval . FORM)
(参考:font-lock-add-keywordsのhelpと、Emacs LispのinfoのFont Lock ModeのSearch-based Fontificationの項)
a.とb.はfaceを指定しない場合(font-lock-keyword-faceが使われる)、c.はfaceのみを指定する場合(MATCHERにマッチする部分全体に適用される)、f.は正規表現の代わりにfaceの変更対象を検索する関数を別途定義する場合に使用するもので、a.〜c.はd.で代用でき、d.はe.で代用でき、f.は特殊なので、1つ覚えるならe.の形式が良いと思う。

HIGHLIGHTERの書式は、主に(SUBEXP FACENAME [OVERRIDE])である。SUBEXPは正規表現に()を使う場合の何番目の()の部分かの意味で、全体なら0とする。OVERRIDEは別のルールが適用済でも適用するかどうかである。

例えば、次のようにすると、java-modeの時に、TABの部分がunderline、2バイトスペースや行末の空白部分がtrailing-whitespaceというface(これらはfaces.elに定義されている)になる。

(font-lock-add-keywords 'java-mode '(
  ("\t" . 'underline)
  (" " . 'trailing-whitespace)
  ("[ \t]+$" . 'trailing-whitespace)
))
実行前

実行後

次のようにすると、java-modeに限らず、全モードで有効になる。

(defadvice font-lock-mode (before my-font-lock-mode ())
  (font-lock-add-keywords
   nil   ;現在のバッファのみ
    '(("\t" 0 'underline append)
      (" " . 'trailing-whitespace)
      ("[ \t]+$" . 'trailing-whitespace)
     )))
(ad-enable-advice 'font-lock-mode 'before 'my-font-lock-mode)
(ad-activate 'font-lock-mode)
なお、上の3行目の部分をnilでなくmajor-modeと書いている例をよく見かけるが、そのようにすると、バッファが開く度にfont-lock-add-keywordsが実行され、font-lock-keywords-alistが肥大化してしまうので、nilの方がいいと思う。


HIGHLIGHTERの書式には、他に、MATCHERがマッチした後の行末までの部分を対象に、別のパターンの検索を行う、anchoredな形式がある。
ANCHORED-MATCHERを使う場合は、HIGHLIGHTERの書式が(ANCHORED-MATCHER PRE-FORM POST-FORM SUBEXP-HIGHLIGHTERS...)となる。PRE-FORMは、このANCHORED-MATCHERの検索が始まる前に実行され、POST-FORMは、行末までこのANCHORED-MATCHERが探された後に実行される。これを使うと、以下のようなことができる。

■コメント部分の特定キーワードの表示を変える
筆者は試行錯誤中のコードをコメントアウトして残す癖があり、作成中のコードには使用中のコードの3〜4倍のコードがコメントに存在することが普通にあるが、デフォルトのルールでは大体それらがコメント色1色になってしまうので、たまに痛い時がある。

(font-lock-add-keywords nil '(
 (";"
  ("face\\|frame"
   nil  ;PRE-FORM
   (goto-char (match-end 0))  ;POST-FORM: pointを";"の直後に戻す
   (0 font-lock-type-face t))
  ("default"
   nil  ;PRE-FORM
   nil  ;POST-FORM
   (0 font-lock-builtin-face t))
  )))
実行前

実行後(コメント中の"face", "frame", "default"に色が付いている)

■CやJavaの"if"の後の"="を警告表示にする

(add-hook 'c-mode-common-hook
  '(lambda ()
    (font-lock-add-keywords major-mode '(
      ("\\<if\\>"
       ("[^<>=]\\(=\\)[^=]" nil nil (1 font-lock-warning-face))
       )))
))
実行前

実行後

過去にそんなミス滅多に無いだろ、みたいなことを書いたが、筆者はExcel VBAを書いた直後だとやってしまうことに気付いた。

■"\x1b\x24\x42"〜"\x1b\x28\x42"とその間の16進数に着色する

(font-lock-add-keywords nil '(
 ("\\\\x\\(1b\\)\\\\x\\(24\\)\\\\x\\(42\\).*?\\\\x\\(1b\\)\\\\x\\(28\\)\\\\x\\(42\\)"
  (1 font-lock-function-name-face t) ;"1b"の部分
  (2 font-lock-function-name-face t) ;"24"の部分
  (3 font-lock-function-name-face t) ;"42"の部分
  (4 font-lock-constant-face t)  ;"1b"の部分
  (5 font-lock-constant-face t)  ;"28"の部分
  (6 font-lock-constant-face t)  ;"42"の部分
  ("[0-9a-f]"  ;ANCHORED
   (progn  ;PRE-FORM
    (goto-char (match-end 3)) ;3つ目の後に移動
    (match-beginning 4))  ;4つ目の先頭までに限る
   nil  ;POST-FORM
   (0 font-lock-type-face t))
)))
実行前

実行後


ANCHORED-MATCHERは、プログラマー魂を刺激するのである。