使用時間の長さで言えば、一番使っているのは、やはり 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日別に深緑でなくてもよいのだが、要するに、ソースコードに色をつけて 見やすくしよう、ということである。実例をあげる。たとえば、
// ---- 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 は、
c++-mode
の定義では、c++-font-lock-keywords、
c++-font-lock-keywords-1、c++-font-lock-keywords-2、
c++-font-lock-keywords-3 の4つのシンボルのリストになっている。
nil の場合、KEYWORDS で定義したもの以外は、
フォントロックの対象外になる。
キーワード以外というのは、たとえば、文字列やコメントなど、
構文的に識別される要素だ。
上の定義では
nil になっているので、デフォルトでは、文字列やコメントも
フォントロックの対象になる。
nil になっているので、大文字・小文字の違いは識別される。
たとえば、class はキーワードだが、Class は
キーワードではない (自分で定義しないかぎり)。
c++-mode の例では、'_' と '~' を追加している。これは、
C++ では、'_' が識別子の構成要素であり、また、デストラクタの名前は
'~' で始まるからである。
beginning-of-defun になっている。
ということで、自分なりにキーワードを定義するには、KEYWORDS で
使われている、c++-font-lock-keywords、
c++-font-lock-keywords-1、c++-font-lock-keywords-2、
c++-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-decoration を
nil に設定する。
さて、せっかく自分でキーワードを定義するんだから、やはり、 通常の 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)
なるほど。
font-lock-keyword-face になる。"\\<foo\\>"
font-lock-keyword-face になる。("fu\\(bar\\)" . 1)
("fubar" . fubar-face)fubar-faceにする
(MATCH FACENAME OVERRIDE LAXMATCH)
の形をしたリストである。
ここで、MATCH および FACENAME は、前2項で説明したものと同じ意味である。
つまり、MATCHER にマッチした文字列の一部について、フォントフェイスを指定する
ことができる。OVERRIDE に非nil を与えると、すでにフォントフェイスが
指定されていても、それを上書きする。LAXMATCH の説明は割愛するが、通常は、
t を指定しておけばよかろう。
("goto \\(\\w+\\)" . (1 font-lock-reference-face nil t))font-lock-reference-face にする
("\\(goto\\) \\(\\w+\\)" (1 font-lock-keyword-face nil t) (2 font-lock-reference-face nil t))font-lock-reference-face にする
ようやく 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)) )) )
現在の日付・時刻は、
(current-time)
という関数で取得することができる。だが、これが返す値は、
人間にとって扱いやすいものではない。それを、人間に読める形に変換するのが、
format-time-string という関数である。次のようにして使う。
(format-time-string FORMAT (current-time))
FORMAT で表示形式を設定する。まあ、Cライブラリの printf
みたいなもんだと思えばよろしい。以下のような指定が可能である。
%Y: 4桁で年を表す%m: 01〜12 で月を表す%d: 01〜31 で日を表す私は、インデックスページの「更新: 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)
とやっておくと、ファイルのセーブ時に、その日時を自動的に挿入することができる。