Emacs Lisp 勉強会(バッファとウィンドウ編)
10/11に行ったEmacs Lisp勉強会の内容です。
コラムのとこに載せようと思ったのですが、やり方がわからないのでここに載せました。
バッファとウィンドウって?
バッファは Emacs で編集するテキストを持っているオブジェクトです。
簡単に言うと、文字列みたいなものです。
MVC の Model だと思えば良いです。
ウィンドウはバッファを表示する領域です。
MVC の View だと思えば良いです。
複数のウィンドウに同じバッファを表示する事ができます。
今編集してるテキストに何かしたり、ファイルを開いて何かするプログラムを書くには
バッファの扱い方を知る必要があります。
試してみる
以下を評価すると "*Messages*" バッファを今アクティブになってるウィンドウに表示
します。
(let ((buffer (get-buffer "*Messages*")))
(switch-to-buffer buffer))
=> #<buffer *Messages*>
カレントバッファ
各種関数が処理対象とするデフォルトのバッファをカレントバッファと言います。
カレントバッファを設定するには `set-buffer' 関数を使います。
カレントバッファを取得するには `current-buffer' 関数を使います。
何もしていない場合、アクティブなウィンドウが表示しているバッファがカレントバッファです。
以下がサンプルです。
(let ((buffer (get-buffer-create "*カレントバッファテスト*")))
;; `*カレントバッファテスト*' バッファをカレントバッファにする
(set-buffer buffer)
;; カレントバッファに文字列を書き込む
(insert "カレントバッファ??")
;; カレントバッファを表示する
(display-buffer (current-buffer)))
ポイントって?
カーソルが指している位置の事をポイントと言います。
ポイントは文字の上ではなく、文字と文字の間にあります。
ポイントの値はバッファの先頭を1として、1文字進むごとに1増える整数です。
全角文字1文字でもポイントの値は1しか増えません(バイト数ではない)。
例えば以下の文字列があるバッファの場合、
abcあいうポイントの値は以下のようになります。
`a'の手前が1
`a'の後ろが2
`あ'の手前が4
`あ'の後ろが5
`う'の後ろが7
つまり以下のようになります。
a b c あ い う
1 2 3 4 5 6 7
カーソルが`a'の上にある場合、そのカーソル位置のポイントは1です。
ポイントの位置を知る
以下を評価するとカーソル位置のポイントがわかります。
(point)
=> 99
プログラムを書いてみる
java のソースから関数一覧を取得するプログラムを書いてみます。
ここでは "Test.java" というソースファイルを使います。
Test.java:
public class Test {
private privateMethod1() {
}
private privateMethod2() {
}
protected protectedMethod1() {
}
protected protectedMethod2() {
}
public publicMethod2() {
}
public publicMethod2() {
}
}
まずは Test.java のバッファを選択する
覚える関数: set-buffer
コード:
(set-buffer "Test.java")
=> #<buffer Test.java>
`set-buffer' でバッファを選択できます。
選択したバッファが表示はされません。
Test.java からメソッドを探して、リストに詰める
覚える関数: re-search-forward, match-string, goto-char, point-min, push
コード:
(save-excursion
(set-buffer "Test.java")
;; 先頭へ移動
(goto-char (point-min))
(let ((case-fold-search nil)
methods)
;; メソッドを検索
(while (re-search-forward "\\(private\\|protected\\|public\\) +\\([a-zA-Z0-9_]+\\)(" nil t)
;; リストに詰める
(push (match-string 2) methods))
methods))
=> ("publicMethod2" "publicMethod2" "protectedMethod2" "protectedMethod1" "privateMethod2" "privateMethod1")
メソッドの一覧が取れました。
ただし、順番が逆になっています。
`re-search-forward' 関数を使うと正規表現で文字列を探す事ができます。
引数は (REGEXP BOUND NOERROR) で、REGEXP に正規表現を指定します。BOUND は nil,
NOERROR は t を指定して下さい(今のところはおまじない)。文字列が見つかるとその
位置に移動します。while で回すとマッチする全ての文字列を順々に検索します。
`re-search-forward' の結果は `match-string' 関数で取得します。引数には何番目のカッコの値が欲しいのか
を指定します。0の場合はマッチした文字列全体です。
`goto-char' で指定したポイントに移動します。
`point-min' でバッファの先頭のポイントを取得します。ちなみに、末尾は
`point-max' で取得できます。
# 本当は先頭以外のときもありますが。
`push' はリストに値を追加します。
リストを正しい順番に並びかえる
覚える関数: nreverse
コード:
(save-excursion
(set-buffer "Test.java")
(goto-char (point-min))
(let ((case-fold-search nil)
methods)
(while (re-search-forward "\\(private\\|protected\\|public\\) +\\([a-zA-Z0-9_]+\\)(" nil t)
(push (match-string 2) methods))
;; 逆順にするように変更
(nreverse methods)))
=> ("privateMethod1" "privateMethod2" "protectedMethod1" "protectedMethod2" "publicMethod2" "publicMethod2")
`nreverse' を使うとリストを逆順に並びかえる事ができます。この関数を使うと、も
とのリストの値が変更されるので注意して下さい。
結果を別のウィンドウに表示する
覚える関数: interactive, get-buffer-create, erase-buffer, display-buffer, insert
ここからはコマンドを作っていきます。
まずは、今選択してるバッファのメソッド一覧を別ウィンドウに表示する関数を作って
みます。
コード:
;; 今まで作ってきたのを関数にしておく
(defun list-methods-get-methods (buffer)
(save-excursion
(set-buffer buffer)
(goto-char (point-min))
(let ((case-fold-search nil)
methods)
(while (re-search-forward "\\(private\\|protected\\|public\\) +\\([a-zA-Z0-9_]+\\)(" nil t)
(push (match-string 2) methods))
(nreverse methods))))
;; 新しいコマンド
(defun list-methods ()
(interactive)
(let ((methods (list-methods-get-methods (current-buffer)))
(buffer (get-buffer-create "*test*")))
(set-buffer buffer)
(erase-buffer)
(dolist (method methods)
(insert method "\n"))
(display-buffer buffer)))
Java のソースを開いたバッファ上で M-x list-methods とする
と、メソッドの一覧が表示されます。
関数の先頭に `interactive' を書いておくと、その関数はコマンドになります。
`get-buffer-create' は新しいバッファを作る関数です。すでに存在する場合は、もと
のバッファを返します。
`erase-buffer' はバッファの中の文字列を全て削除します。
`display-buffer' はそのバッファを新しいウィンドウにポップアップします。このウィ
ンドウは選択されません。
`insert' は文字列をバッファに挿入します。
メソッドを選んだらソースに飛ぶようにする
覚える関数: put-text-property, get-text-property
表示するだけではつまらないので、今カーソルがある位置のメソッドにジャンプできる
ようにします。
ジャンプする為に、一覧表示のメソッド名にジャンプ先のバッファとポイントをテキス
トプロパティとして設定します。
テキストプロパティは名前と値の対で、これを使うと文字列に好きな情報を持たせる事ができます。
コード:
(defun list-methods-get-methods (buffer)
(save-excursion
(set-buffer buffer)
(goto-char (point-min))
(let ((case-fold-search nil)
methods)
(while (re-search-forward "\\(private\\|protected\\|public\\) +\\([a-zA-Z0-9_]+\\)(" nil t)
;; バッファと位置も返すように変更
(push (list (match-string 2) buffer (point))
methods))
(nreverse methods))))
(defun list-methods ()
(interactive)
(let ((methods (list-methods-get-methods (current-buffer)))
(orig-buffer (current-buffer))
(buffer (get-buffer-create "*test*")))
(set-buffer buffer)
(erase-buffer)
(dolist (method methods)
(let ((pos (point)))
(insert (car method) "\n")
;; テキストプロパティを設定するように変更
(put-text-property pos (point)
'list-methods-method-buffer (nth 1 method))
(put-text-property pos (point)
'list-methods-method-position (nth 2 method))))
(pop-to-buffer buffer)
(goto-char (point-min))))
;; 新しいコマンド
(defun list-methods-select-method ()
(interactive)
(let ((buffer (get-text-property (point) 'list-methods-method-buffer))
(pos (get-text-property (point) 'list-methods-method-position)))
(when (and buffer pos)
(pop-to-buffer buffer)
(goto-char pos))))
M-x list-methods で表示された結果のメソッド名の上で
M-x list-methods-select-method とするとそのメソッドのある場所にジャンプします。
`put-text-property' を使うとテキストプロパティというものを文字列に設定できます。
ここでは list-methods-method-position, list-methods-method-buffer に元のメソッ
ドの位置とバッファを設定しています。
`get-text-property' は指定した位置の文字列からテキストプロパティを取得します。
一覧表示をメジャーモードにする
覚える関数: make-sparse-keymap, define-key, use-local-map
覚える変数: major-mode, mode-name
コード:
;; list-methods-mode 用のキーマップ
(defvar list-methods-mode-map nil)
(unless list-methods-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "n" 'next-line)
(define-key map "p" 'previous-line)
(define-key map "q" 'bury-buffer)
(define-key map "\r" 'list-methods-select-method)
(setq list-methods-mode-map map)))
(defun list-methods-mode ()
(interactive)
(kill-all-local-variables)
(use-local-map list-methods-mode-map)
(setq major-mode 'list-methods-mode)
(setq mode-name "List Methods")
(run-hooks 'list-methods-mode-hook))
(defun list-methods ()
(interactive)
(let ((methods (list-methods-get-methods (current-buffer)))
(orig-buffer (current-buffer))
(buffer (get-buffer-create "*test*")))
(set-buffer buffer)
(erase-buffer)
(dolist (method methods)
(let ((pos (point)))
(insert (car method) "\n")
(put-text-property pos (point)
'list-methods-method-buffer (nth 1 method))
(put-text-property pos (point)
'list-methods-method-position (nth 2 method))))
(pop-to-buffer buffer)
(goto-char (point-min))
;; モード設定を追加
(list-methods-mode)))
これで M-x list-methods で表示されたバッファが list-methods-mode になりました。
p, n で上下に移動、RET で選択、q で終了します。
メジャーモードを作るときの手順は
そのモード用のキーマップを作る
major-mode 用のコマンドを作る。
そのコマンドでは
変数 major-mode にそのモードを表すシンボルを設定
変数 mode-name にそのモードの名前を設定
`use-local-map' でモード用のキーマップを設定
キーマップを作るには `make-sparse-keymap' で空のキーマップを作って
`define-key' でキーと関数を指定します。
完成
コード:
(defvar list-methods-mode-map nil)
(unless list-methods-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "n" 'next-line)
(define-key map "p" 'previous-line)
(define-key map "q" 'bury-buffer)
(define-key map "\r" 'list-methods-select-method)
(setq list-methods-mode-map map)))
(defun list-methods-mode ()
(interactive)
(kill-all-local-variables)
(use-local-map list-methods-mode-map)
(setq major-mode 'list-methods-mode)
(setq mode-name "List Methods")
(run-hooks 'list-methods-mode-hook))
(defun list-methods-get-methods (buffer)
(save-excursion
(set-buffer buffer)
(goto-char (point-min))
(let ((case-fold-search nil)
methods)
(while (re-search-forward "\\(private\\|protected\\|public\\) +\\([a-zA-Z0-9_]+\\)(" nil t)
;; バッファと位置も返すように変更
(push (list (match-string 2) buffer (point))
methods))
(nreverse methods))))
(defun list-methods ()
(interactive)
(let ((methods (list-methods-get-methods (current-buffer)))
(orig-buffer (current-buffer))
(buffer (get-buffer-create "*test*")))
(set-buffer buffer)
(erase-buffer)
(dolist (method methods)
(let ((pos (point)))
(insert (car method) "\n")
(put-text-property pos (point)
'list-methods-method-buffer (nth 1 method))
(put-text-property pos (point)
'list-methods-method-position (nth 2 method))))
(pop-to-buffer buffer)
(goto-char (point-min))
;; モード設定を追加
(list-methods-mode)))
(defun list-methods-select-method ()
(interactive)
(let ((buffer (get-text-property (point) 'list-methods-method-buffer))
(pos (get-text-property (point) 'list-methods-method-position)))
(when (and buffer pos)
(pop-to-buffer buffer)
(goto-char pos))))
- Category(s)
- 勉強会
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/sugawara/emacs-lisp-52c95f374f1a-30c330d530a1306830a630a330f330a67de8/tbping
- ¦
- Main
- ¦
- trac-ticket.el
Re:Emacs Lisp 勉強会(バッファとウィンドウ編)
からリンクを張りました。
Re:Emacs Lisp 勉強会(バッファとウィンドウ編)
Emacs では、名前空間が一つしかありませんので、何かプログラムを書く場合は、他のプログラムとはぶつからない文字列を選び出し、自作変数/関数の名前の前に付ける必要があります。
このプログラムでは、push や dolist と言った Common Lisp の関数を使っていますので、(require 'cl) が必要です。(エラーにならないのは、あなたの環境でたまたま cl.el が読み込まれているからです。) 別の方法として、それぞれ cons や apply といった Emacs Lisp の組み込み関数で書き直すこともできます。
勉強、頑張って下さい。:)