トップ  |  更新  |  惑々  |  C++  |  Emacs  |  CVS  |  doxygen  |  VC/MFC  |  Libs  |  リンク

Emacs How To

使用時間の長さで言えば、一番使っているのは、やはり Emacs である。 Emacs といっても、最近私が使用しているのは、XEmacs の 21.1 だ。 最近の GNU Emacs のほうは全然使っていないので、よく分からない。XEmacs 固有の話題も多々出てくると思うが、ご容赦願いたい。


領域の別背景色表示

XEmacs を使い始めて、まず便利だと思ったのは、領域、つまり、マークと ポイントの間を、別の背景色で表示してくれることであった。最初のうちこそ 違和感があったが、慣れるにつれて、「なんで今までこの機能が無かったの?」と 思うようになったほどだ。

もちろん、「今までこの機能が無かった」のには理由がある。従来のEmacs では、 領域が常に有効の状態にあったので、単純にこの機能を導入してしまうと、 常にマークとポイントの間が別背景色で表示されることになってしまうからだ。

XEmacs では、領域に「有効状態」というのを導入することによって、上の問題の 解決を図っている。つまり、領域が有効状態にある場合に限り、領域を別背景色で 表示するわけである。しかし、私の場合、ここで別の問題が生じた。これまで 使用していた、自作の vi もどきモードの機能のいくつかが、使えなくなって しまったのである。

たとえば、"dw" というコマンドを実行したとしよう。これは、現在のポイントを マークしてからポイントを次の単語の先頭に移動させ、そこで kill-region を 呼び出す。従来の Emacs なら、これでよかった。なぜなら、領域は、マークと ポイントの間で、常に有効になっていたから。ところが、XEmacs では、 zmacs-activate-region という関数を呼んで、自分で領域を有効にしてやらないと ダメなのである。

"hjkl" によるポイント移動も同様である。マークをしてから、\C-n や \C-p で ポイントを移動するぶんには、ちゃんと領域のところを別背景色で表示してくれるのに、 "j" や "k" を使ったとたん、 別背景色表示が消えてしまうのだ。どうやら、 自作 vi もどきモードは、全面書き替えが必要のようだ。まあ、これも人生である。 リファクタリングの契機を与えてくれたと思えば、感謝の念すら湧いてこよう…… (涙)。

前置きが長くなったが、以下、領域の有効状態の管理方法を解説する。といっても、 私が使っているのは、ごく単純な方法である。

まず、現在、領域が有効かどうかを知るには、関数、

region-exist-p
を使う。non nil が返ってくれば「有効」、nil が返ってくれば「無効」というわけだ *1。 また、領域を有効化する関数は、
zmacs-activate-region
である。この2つの関数を使うと、たとえば、私家版 next-line は、 次のようになる。

(defun svi-next-line (arg)
  "next line if not on the last line of buffer.
If arg is set, move cursor just 1 line physically."
  (interactive "P")
  (let ((reg-act (region-exists-p)))                    ; (A)
    (if arg
	(let ((hc (get-horiz-col)))
	  (vertical-motion 1)
	  (move-to-column (+ (current-column) hc)))
      (or (save-excursion (end-of-line) (eobp))
	  (next-line 1)))
    (if reg-act (zmacs-activate-region))                ; (B)
    (setq this-command 'next-line)))

(defun get-horiz-col ()
  (- (current-column)
     (save-excursion (vertical-motion 0) (current-column))))
  

まず、(region-exists-p) の戻値を reg-arg というフラグに格納している (A)。ポイントの移動を 行った後、そのフラグを見て、必要なら zmacs-activate-region で 領域を有効にしている (B)

この関数は、物理的な一行単位での移動も実装している。興味のある方は、 そのあたりも解読してみると面白いだろう。

初出: 2001年3月5日
  1. より正確に知るためには、zmacs-regionsregion-active-p の組合せを使う必要があるらしい

深緑なコメント

別に深緑でなくてもよいのだが、要するに、ソースコードに色をつけて 見やすくしよう、ということである。実例をあげる。たとえば、

// ---- dynamic_cast (動的型変換) ----
    // 派生関係があるクラスのポインタ間では…
    pDerived = new Derived;
    // アップキャストすると…
    pBase = dynamic_cast<Base *>(pDerived);
    // アドレスを調整する
    assert( (ADDR)pBase == (ADDR)pDerived + sizeof(Foo) );

    // 基底クラスが仮想関数を持たない場合は…
    pFoo = new Derived;
#if 0
    // このダウンキャストはコンパイルできない
    pDerived = dynamic_cast<Derived *>(pFoo);
#endif  

よりは、


// ---- dynamic_cast (動的型変換) ----
    // 派生関係があるクラスのポインタ間では…
    pDerived = new Derived;
    // アップキャストすると…
    pBase = dynamic_cast<Base *>(pDerived);
    // アドレスを調整する
    assert( (ADDR)pBase == (ADDR)pDerived + sizeof(Foo) );

    // 基底クラスが仮想関数を持たない場合は…
    pFoo = new Derived;
#if 0
    // このダウンキャストはコンパイルできない
    pDerived = dynamic_cast<Derived *>(pFoo);
#endif  

のほうが、読みやすかろう、ということである。…はずなのだが、うーむ、 ソースの一部分だけとりだすと、色の付いてないほうが読みやすく思えるのは、 どうしたわけか。

上の例はともかくとして、ソースコードに色をつけるやり方については、 いろいろなところで解説されている。

これらのページをご覧いただければおわかりのように、ソースコードに色を つけるには、各メジャーモードに合わせて、font-lock-mode というマイナーモードを 使うことになる。

font-lock-mode の起動のしかたや色の設定とかの説明は、上記ページに譲る。 ここでは、c++-mode において、色づけしたいキーワードなどを自分で定義する やり方について解説する。 なお、以下の説明は、私の環境 (VineLinux 2.0 上の XEmacs 21) でのみ動作を 確認している。XEmacs のバージョンや、使用している font-lock.el のバージョンに よって、動作が異なりうることは、あらかじめご承知おき願いたい。

まず、font-lock.el をのぞいてみよう。 つらつら眺めていると、次のような部分が目につく。

(put 'c++-mode 'font-lock-defaults
     '((c++-font-lock-keywords
	c++-font-lock-keywords-1 c++-font-lock-keywords-2
	c++-font-lock-keywords-3)
       nil nil ((?_ . "w") (?~ . "w")) beginning-of-defun))
 

雰囲気としては、メジャーモードを表すシンボル (この場合は、 c++-mode) の属性リストに、フォントロックの対象となる キーワードを登録しているようである。font-lock-defaults の コメントを読んでみると、

It should be a list

(KEYWORDS KEYWORDS-ONLY CASE-FOLD SYNTAX-ALIST SYNTAX-BEGIN)

とある。つまり、font-lock-defaults は、

の5つ組からなるリスト、というわけだ。それぞれの意味は、次のとおり。 後で使うのは、KEYWORDS だけなので、面倒なら他は読み飛ばして構わない。

KEYWORDS
シンボル、またはシンボルのリスト。各シンボルは変数か関数であり、 それを評価した値がキーワード(のリスト)を与える。上の c++-mode の定義では、c++-font-lock-keywordsc++-font-lock-keywords-1c++-font-lock-keywords-2c++-font-lock-keywords-3 の4つのシンボルのリストになっている。

KEYWORDS-ONLY
nil の場合、KEYWORDS で定義したもの以外は、 フォントロックの対象外になる。 キーワード以外というのは、たとえば、文字列やコメントなど、 構文的に識別される要素だ。 上の定義では nil になっているので、デフォルトでは、文字列やコメントも フォントロックの対象になる。

CASE-FOLD
"case-fold" とは、文字の大小(case)をたたみ込む(fold)こと *1。 つまり、「大文字・小文字の違いを無視する」ということだ。上の定義では nil になっているので、大文字・小文字の違いは識別される。 たとえば、class はキーワードだが、Class は キーワードではない (自分で定義しないかぎり)。

SYNTAX-ALIST
(CHAR . STRING) の形で、キーワードの構成要素となる文字を定義する。 'CHAR' は、通常の単語の構成要素に加えて、キーワードの一部として扱うべき 文字を指定する。 'STRING' は、syntax-table で使われるものと同じなので、通常は、 単語を意味する "w" を指定することになる。
上のc++-mode の例では、'_' と '~' を追加している。これは、 C++ では、'_' が識別子の構成要素であり、また、デストラクタの名前は '~' で始まるからである。

SYNTAX-BEGIN
構文ブロックの外に出るための関数を定義する。フォントの再表示の際に ソースの解析を始める位置を知るために使われる(と思われる)。C++ モードでは、 beginning-of-defun になっている。

ということで、自分なりにキーワードを定義するには、KEYWORDS で 使われている、c++-font-lock-keywordsc++-font-lock-keywords-1c++-font-lock-keywords-2c++-font-lock-keywords-3 という4つのシンボルに 値を設定してやればよいということが分かったわけだ。

じゃあ、デフォトルでこいつらにはどんなキーワードが定義されているんだろう、 と思い、再び font-lock.el を眺めてみると、かなり面妖なことになっている。

 (setq c++-font-lock-keywords-1
  (append
   c-font-lock-keywords-1
[略]
    )))

 (setq c++-font-lock-keywords-2
  (append c++-font-lock-keywords-1
[略]
    )))

 (setq c++-font-lock-keywords-3
  (append c++-font-lock-keywords-2
[略]
    )))

(defvar c++-font-lock-keywords c++-font-lock-keywords-1
  "Default expressions to highlight in C++ mode.")

略した部分には、それぞれ様々な定義が記述されているわけなんだが、 よく見ると、後から定義されるシンボル (…-keywords-2 とか …-keywords-3 とか) には、前に定義したシンボル (…-keywords-1 とか …-keywords-2 とか) の内容がアペンドされているのである。つまり、 …-keywords-2…-keywords-1 の内容を含み、 …-keywords-3…-keywords-2 の内容を含んでいるということだ。

気を取り直して、各シンボルの説明を読んでみる。どうやら、接尾辞の -N というのは、定義の「レべル」を表しているようだ。

(defconst c++-font-lock-keywords-1 nil
  "Subdued level highlighting for C++ modes.")

(defconst c++-font-lock-keywords-2 nil
  "Medium level highlighting for C++ modes.")

(defconst c++-font-lock-keywords-3 nil
  "Gaudy level highlighting for C++ modes.")

…-keywords-1 では控えめだけど、…-keywords-3 では、 ド派手に色をつけるよ、ということらしい。 つまり、派手好みの人は、…-keywords-3 を使えばよいし、 (私のように) 控え目な人は、…-keywords-1 を使うが吉、 ということである。

では、レベルの選択はどうするのか、というと、これには font-lock-maximum-decoration という変数が用意されている。 この変数の値によって、

nil …… レベル 0。つまり、KEYWORDS リストの先頭のものが使用される
t …… 最大レベル。つまり、KEYWORDS リストの最後のものが使用される
数字 …… 指定されたレベル。つまり、KEYWORDS リストから<数字>番目の ものが使用される
ドット対のリスト …… 各要素は、(モード . レベル) という形をしており、メジャーモード ごとにレベルを指定できる。モードでtを設定すると、残りすべての メジャーモードを指定したことになる

というわけである。デフォルトは、t、すなわち最大レベルである。 したがって、控え目でいくなら、あらかじめ、.emacs の中などで、

(setq font-lock-maximum-decoration
      '((c++-mode . nil) (t . t)))

としておけばよろしい。そうすると c++-mode で使われるのは、 c++-font-lock-keywords (これは、デフォルトで c++-font-lock-keywords-1 の値がコピーされている) だけになる。

……しかし、これを試された方はおわかりかと思うが、 ちと、寂しすぎる気がしないでもない。色がつくのは、コメント、文字列、 プリプロセッサディレクティブ、関数および変数の定義部だけである。


ということで、あいかわらず長い前振りだったが、色づけしたいキーワードを 自分で定義する方法の説明に移ろう。要するに、KEYWORDS の最初に現れるシンボル、 c++-font-lock-keywords に、キーワードのリストを定義してやればよいのである。必要なら、 c++-font-lock-keywords-[123] の内容をアペンドしてやってもよいだろう。その上で、 font-lock-maximum-decorationnil に設定する。

さて、せっかく自分でキーワードを定義するんだから、やはり、 通常の C++ の予約語は全部、色をつけたい。 あと、標準ライブラリで定義されてる型とかオブジェクトも。 それから、ラベルも別の色で表示させたいね。…などと考えはじめると、 何のことはない、結局、"Gaudy level highlighting for C++ modes." という ことになるわけである。

それだったら、素直に最大レベルでやらんかいっ、 とツッコミを入れたくなる向きもあろうが、デフォルトの設定には、いまひとつ、 気に入らないところがあるのだ。たとえば、クラスのコンストラクタ宣言や 変数の定義をうまく捉えきれていなかったり、行頭 # で始まるものは、すべて プリプロセッサディレクティブとして扱ってしまったり。やはり、ここは、 自分で定義を書くしかなかろう。

肝心のキーワードの定義のしかたであるが、 これは、font-lock-keywords 変数の説明のところに書いてある。

(defvar font-lock-keywords nil
  "A list of the keywords to highlight.
Each element should be of the form:

 MATCHER
 (MATCHER . MATCH)
 (MATCHER . FACENAME)
 (MATCHER . HIGHLIGHT)
 (MATCHER HIGHLIGHT ...)
 (eval . FORM)

なるほど。

のいずれかの形をした要素からなるリスト、ということだ。それぞれの意味は、 次のようなところだ。

MATCHER
MATCHER は、キーワードを捕捉するための正規表現。 要素が MATCHER だけの場合は、 正規表現にマッチした文字列全体がキーワードとみなされる。フォントフェイスは、 font-lock-keyword-face になる。
例:
"\\<foo\\>"
…… 単語 foo をキーワードとする

(MATCHER  .  MATCH)
MATCH は、MATCHER の中で、キーワードとして指定したい部分のカッコ番号。 MATCHER 全体ではなく、その部分文字列をキーワードに指定したい場合に使う。 使用される フォントフェイスは、font-lock-keyword-face になる。
例:
("fu\\(bar\\)" . 1)
…… 文字列 fubar の中の、bar がキーワードになる

(MATCHER  .  FACENAME)
FACENAME は、MATCHER にマッチした文字列に対する フォントフェイスを指定する。
例:
("fubar" . fubar-face)
…… 文字列 fubar の フォントフェイスを fubar-faceにする

(MATCHER  .  HIGHLIGHT)
HIGHLIGHT は、
(MATCH FACENAME OVERRIDE LAXMATCH)
の形をしたリストである。 ここで、MATCH および FACENAME は、前2項で説明したものと同じ意味である。 つまり、MATCHER にマッチした文字列の一部について、フォントフェイスを指定する ことができる。OVERRIDE に非nil を与えると、すでにフォントフェイスが 指定されていても、それを上書きする。LAXMATCH の説明は割愛するが、通常は、 t を指定しておけばよかろう。
例:
("goto \\(\\w+\\)" . (1 font-lock-reference-face nil t))
…… goto の後の単語のフォントフェイスを font-lock-reference-face にする

(MATCHER HIGHLIGHT ...)
上記 HIGHLIGHT を複数指定することもできる。
例:
("\\(goto\\) \\(\\w+\\)" (1 font-lock-keyword-face nil t) (2 font-lock-reference-face nil t))
…… goto のフォントフェイスを font-lock-keyword-face にし、 その後の単語のフォントフェイスを font-lock-reference-face にする

(eval  .  FORM)
よくわかんないので、説明は省略。

ようやく c++-font-lock-keywords を定義する準備が整った。 以下が、現在私の使用している定義である。

(load "font-lock")

(set-face-foreground 'font-lock-comment-face       "darkGreen")
(set-face-foreground 'font-lock-doc-string-face    "darkGreen")
(set-face-foreground 'font-lock-function-name-face "darkBlue")
(set-face-foreground 'font-lock-keyword-face       "mediumBlue")
(set-face-foreground 'font-lock-preprocessor-face  "mediumBlue")
(set-face-foreground 'font-lock-reference-face     "darkRed")
(set-face-foreground 'font-lock-string-face        "darkRed")
(set-face-foreground 'font-lock-type-face          "royalBlue")
(set-face-foreground 'font-lock-variable-name-face "darkRed")
(set-face-foreground 'font-lock-warning-face       "darkRed")

(setq font-lock-maximum-decoration
      '((c++-mode . 0)
	(t . t)))

(setq c++-font-lock-keywords
 '(
   ("^#[ \t]*include[ \t]+[<\"]\\([^>\"\n]+\\)[>\"]"
    . (1 nil t t))
   ("^\\(#[ \t]*\\(ifn?\\|un\\)def\\)[ \t]+\\(\\sw+\\)"
    (1 font-lock-preprocessor-face)
    (3 font-lock-reference-face))
   ("^\\(#[ \t]*\\(if\\|else\\|endif\\|elif\\|undef\\|error\\|include\\|line\\|pragma\\)\\)\\>"
    . (1 font-lock-preprocessor-face))
   ("^#.*\\<\\(defined\\)[ \t]*([ \t]*\\(\\sw+\\)"
    (1 font-lock-preprocessor-face)
    (2 font-lock-variable-name-face))
   ("^\\(#[ \t]*define\\)[ \t]+\\(\\sw+\\)\\([ \t]*(\\)?"
    (1 font-lock-preprocessor-face)
    (2 (if (match-beginning 3)
	   font-lock-reference-face
	 font-lock-variable-name-face)))

   ("^[ \t]*\\(template<[^>]+>[ \t]+\\)?\\(class\\|struct\\|union\\|namespace\\)[ \t]+\\(\\sw+\\)"
    (2 font-lock-keyword-face)
    (3 font-lock-function-name-face))

   ("^[ \t]*\\(pr\\(otected\\|ivate\\)\\):[ \t]*\\sw"
    . (1 font-lock-type-face))

   ("\\<\\(default\\):"
    . (1 font-lock-reference-face))
   ("\\<\\(case\\|goto\\)\\>[ \t]*\\([^ \t\n:\;]+\\)?"
    (1 font-lock-keyword-face) (2 font-lock-reference-face nil t))
   ("^[ \t]*\\(\\sw+\\)[ \t]*:[^:]"
    . (1 font-lock-reference-face))

   ("^[ \t]*\\(using\\([ \t]+namespace\\)?\\)[ \t]+\\(\\(\\sw+[ \t]*::[ \t]*\\)*\\sw+\\)"
    (1 font-lock-keyword-face)
    (3 font-lock-variable-name-face))

   ("\\<\\(true\\|false\\)\\>"
    . font-lock-keyword-face)

   ("\\<\\(return\\|delete\\)[ \t]+[*& \t]*\\(\\sw+\\)"
    (1 font-lock-keyword-face)
    (2 nil))

   ("\\<\\(if\\|for\\|while\\|do\\|switch\\)\\>"
    . font-lock-keyword-face)
   ("\\<\\(break\\|continue\\|delete\\|friend\\|new\\|return\\|th\\(is\\|row\\)\\)\\>"
    . font-lock-keyword-face)
   ("\\<\\(class\\|struct\\|union\\|namespace\\|typedef\\)\\>"
    . font-lock-keyword-face)
   ("\\<\\(assert\\|cerr\\|cin\\|cout\\|endl\\|flush\\)\\>"
    . font-lock-keyword-face)
   ("\\<\\(dynamic\\|static\\|reinterpret\\|const\\)_cast\\>"
    . font-lock-keyword-face)

   ("\\<\\(auto\\|bool\\|c\\(har\\|o\\(mplex\\|nst\\)\\)\\|double\\)\\>"
    . font-lock-type-face)
   ("\\<\\(e\\(num\\|xtern\\)\\|f\\(loat\\|riend\\)\\|in\\(line\\|t\\)\\|long\\)\\>"
    . font-lock-type-face)
   ("\\<\\(register\\|s\\(hort\\|igned\\|t\\(atic\\|ruct\\)\\)\\|t\\(emplate\\|ypedef\\)\\)\\>"
    . font-lock-type-face)
   ("\\<\\(un\\(ion\\|signed\\)\\|v\\(irtual\\|o\\(id\\|latile\\)\\)\\)\\>"
    . font-lock-type-face)

   ("\\<\\(string\\|vector\\|map\\|list\\|size_t\\|auto_ptr\\)\\>"
    . font-lock-type-face)
   ("\\<\\(template\\|public\\|private\\|protected\\|mutable\\)\\>"
    . font-lock-type-face)
   ("\\<\\(sizeof\\|using\\|std\\|\\(const_\\)?iterator\\)\\>"
    . font-lock-type-face)
   ))

(let ((matcher-function
       (concat "^[ \t]*"
	       "\\(\\sw+\\(<[A-Za-z0-9_<>:*& \t]+>\\)?[:*& \t]+\\)*"
	       "\\(~?\\(\\sw+\\)\\)[ \t]*(" ))
      (highlight-function
       '(3 (if (or (match-beginning 1)
		   ;; constructor or destructor?
		   (re-search-backward
		    (concat "^[ \t]*\\(template<[^>]+>[ \t]+\\)?"
			    "\\(class\\|struct\\|union\\)[ \t]+"
			    (buffer-substring (match-beginning 4) (match-end 4)) "\\>")
		    nil t nil))
	       font-lock-function-name-face) t t))

      (matcher-variable
       (concat "\\(^\\|(\\|,\\)[ \t]*"
	       "\\(\\sw+\\(<[A-Za-z0-9_<>:*& \t]+>\\)?[:*& \t]+\\)*"
	       "\\sw+\\(<[A-Za-z0-9_<>:*& \t]+>\\)?[*& \t]+"
	       "\\(\\sw+\\)[ \t]*\\(=\\|\\[\\|;\\|,\\|)\\)"))
      (highlight-variable
       '(5 font-lock-variable-name-face t t))
      )
  (setq c++-font-lock-keywords
	(append c++-font-lock-keywords
		(list (list matcher-function highlight-function)
		      (list matcher-variable highlight-variable))
		))
  )

初出: 2001年3月10日
  1. 余談だが、私は、"case-fold" を "case-hold" とカンチガイしていた 時期があった。これでは、case を保持することになり、全く逆の意味に なってしまう

日付・時刻の挿入

現在の日付・時刻は、

(current-time)

という関数で取得することができる。だが、これが返す値は、 人間にとって扱いやすいものではない。それを、人間に読める形に変換するのが、 format-time-string という関数である。次のようにして使う。

(format-time-string FORMAT (current-time))

FORMAT で表示形式を設定する。まあ、Cライブラリの printf みたいなもんだと思えばよろしい。以下のような指定が可能である。

私は、インデックスページの「更新: xxxx年xx月xx日」という部分を自動的に更新するために、 この関数を使っている。次のようなコードだ。

  (insert (format-time-string "%Y-%m-%d" (current-time)))
  (if (re-search-backward "\\<\\([0-9]+\\)-0?\\([0-9]+\\)-0?\\([0-9]+\\)\\>" nil t)
      (replace-match "\\1年\\2月\\3日"))
何でダイレクトに、(insert (format-time-string "%年-%月-%日" (current-time))) とやらないかと言うと、こうすると、なぜか、 「年」「月」「日」が文字化けしてしまうからなのです。 理由を知っている方がいらっしゃったら、ご教示ください。

そして、上のコードを実行する関数を、たとえば、html-update-modified-time という名前で定義して、

(add-hook 'local-write-file-hooks 'html-update-modified-time)

とやっておくと、ファイルのセーブ時に、その日時を自動的に挿入することができる。


トップページへ / Last modified: 2001-10-07 17:03:24 JST
Created by OKA Toshiyuki < oka-t@fides.dti.ne.jp >