2010-07-28
オートインデントはどうやってるのか
テキストエディタでLispの自動インデントをどうやっているのか知りたい。あわよくばJavaScriptに持っていってブラウザのフォームに組み込んで、BiwaSchemeで使えるようにしたい。
xyzzyのソースを追ってみる。
リターンキーにバインドされた関数を調べる
M-x describe-bindings
...
TAB lisp-indent-line
RET lisp-newline-and-indent
lisp-newline-and-indent
lispmode.l:262
(defun lisp-newline-and-indent (&optional (arg 1)) (interactive "*p") (delete-trailing-spaces) (insert #\LFD arg) (lisp-indent-line))
- lisp-indent-lineがインデントする関数
lisp-indent-line
lispmode.l:229
(defun lisp-indent-line () (interactive "*") (if (or (not (interactive-p)) *lisp-tab-always-indent* (save-excursion (skip-chars-backward " \t") (bolp))) (smart-indentation (save-excursion (goto-bol) (if (protect-match-data (looking-at "[ \t]*;;;")) 0 (max 0 (calc-lisp-indent (point)))))) (insert "\t")) t)
- TABのバインディングもこの関数
- タブを押したときに、行の頭のほうだったらsmart-indentaion、そうじゃなかったらタブ挿入
- インデントの位置は(calc-lisp-indent (point))で計算
- goto-bol 行頭に移動します
- bol = begining of line?
- save-excursion 処理の前後でカレントバッファとポイントを保存します。
calc-lisp-indent
lispmode.l:159
(defun calc-lisp-indent (opoint) (protect-match-data (let ((begin-paren (and lisp-indent-close-paren (looking-at "[ \t]*)")))) (goto-bol) (when (and (looking-at "\\s(") (forward-char -1)) (skip-white-backward) (forward-char 1)) (or (up-list -1 t) (return-from calc-lisp-indent 0)) (cond (begin-paren (+ (current-column) lisp-paren-imaginary-offset)) ((or (looking-back "#") (and (not (looking-back "#'")) (looking-back "'"))) (+ (current-column) 1)) (t (let ((package (or (and (stringp *buffer-package*) (find-package *buffer-package*)) *package*))) (when (save-excursion (when (and (up-list -1 t) (looking-for "((") (up-list -1 t)) (forward-char 1) (multiple-value-bind (symbol found) (calc-lisp-indent-current-symbol package) (and found (get symbol 'lisp-indent-flet))))) (return-from calc-lisp-indent (+ (current-column) *lisp-body-indention*))) (let ((column (progn (forward-char 1) (current-column)))) (multiple-value-bind (symbol found pkg-marker-p) (calc-lisp-indent-current-symbol package) (when pkg-marker-p (return-from calc-lisp-indent column)) (let ((method (when found (or (let ((method (get symbol 'lisp-indent-handler))) (and method (save-excursion (and (up-list -1 t) (forward-list -1 t) (up-list -1 t) (progn (forward-char 1) (multiple-value-bind (symbol found) (calc-lisp-indent-current-symbol package) (eq symbol 'handler-case))))) method)) (get symbol 'lisp-indent-hook))))) (cond ((numberp method) (let ((count -1)) (while (< (point) opoint) (skip-white-forward) (setq count (+ count 1)) (or (forward-sexp 1 t) (return))) (+ column -1 (if (< count method) (* *lisp-body-indent* 2) *lisp-body-indent*)))) (method (+ column -1 *lisp-body-indention*)) (t (skip-chars-forward " \t") (if (or (eolp) (looking-for ";")) (if *lisp-indent-offset* (+ column *lisp-indent-offset*) column) (current-column)))))))))))))
- うへ~!読めない!計算してるのはここなんだろうけど
- looking-at: 現在のカーソル位置で前方向に正規表現でマッチしたらt、しなかったらnilを返します。
- looking-for: 現在のカーソル位置から前方向にマッチしたらt、しなかったらnilを返します。
- こっちは正規表現じゃなくて、現在の場所のテキストがマッチするかどうか
- up-list: カーソルを ARG 個外側の括弧の後ろに移します。
- こいつはbuiltin関数...
- lisp-indent-hook シンボルにプロパティを設定することで lisp-indent-line でのインデント量を制御します。
- forward-sexp lisp-modeでS式を1つ進めます。[ESC C-f]
- forward-list 前方のリストの終端へ移動します。[ESC C-n]
- (defvar *lisp-body-indention* 2) ; lispmode.l:25
- 最初の(up-list -1 t)でインデントしたい位置の1つ上に上がって、begin-parenじゃなくてコメント行じゃなかったら calc-lisp-indent-current-symbol で、そのポイントのシンボルを得る
- シンボルに数値が設定されていたら(if=2, when=1, など)その数だけ(* *lisp-body-indent* 2)インデント
(if (predicate) ; <- (get 'if 'lisp-indent-hook) = 2 なので ここ ; 2個目の引数までは (* *lisp-body-indent* 2) ここ ; それ以降は *lisp-body-indent*
- 最初の引数が同じ行に書いてあったら、その位置
(hoge fuga ; <- 最初の引数 fuga があるので ここ) ; <- そこと同じ位置
- そうでなければ開始括弧の位置+*lisp-indent-offset*
(hoge ; <- 最初の引数がないので ここ) ; <- 括弧の次の位置
calc-lisp-indent-current-symbol
(defun calc-lisp-indent-current-symbol (package) (skip-syntax-spec-forward " ") (if (looking-for ":") (values nil nil t) (multiple-value-bind (symbol found maybe-symbol) (lookup-symbol (point) (progn (skip-syntax-spec-forward "w_") (point)) package) (values symbol (and found maybe-symbol)))))
- キーワード(":" で始まってる)だったら (nil nil t)
- skip-syntax-spec-forward: シンタックステーブルのカテゴリ基づいて文字を前方方向にスキップします。
- "w_" ワードの区切りに移動・英字
- lookup-symbol: ビルトイン関数
- 1番目の引数のポイントから2番目の引数のポイントまでの文字を3番目のパッケージから探して、(シンボルか?、数値じゃないか?)の多値を返す?
store-match-data
(defmacro protect-match-data (&body body) `(let* ((#1=#:match-data (match-data (car *match-data-pool*))) (*match-data-pool* (cdr *match-data-pool*))) (unwind-protect (progn ,@body) (store-match-data #1#))))
- match-data: scan-buffer で検索時点の状態を保持します。複数の検索をした後で、元の検索の結果で match-string / match-beginning / match-end / replace-match を行うことが可能です。
- store-match-data: match-data で退避しておいた検索時点の状態を戻します。
結局
コメントを書く
トラックバック - http://cadr.g.hatena.ne.jp/mokehehe/20100728