# HG changeset patch # User gongo # Date 1219820780 -32400 # Node ID 91236a2c6e2df7c2f0aee532c4244265a0093f32 # Parent 3bbcdd5060a636bd96cd161fffb516039a5f48ba *** empty log message *** diff -r 3bbcdd5060a6 -r 91236a2c6e2d ChangeLog --- a/ChangeLog Tue Aug 26 20:45:22 2008 +0900 +++ b/ChangeLog Wed Aug 27 16:06:20 2008 +0900 @@ -1,3 +1,232 @@ +2008-08-27 Wataru MIYAGUNI + + * redit-client-sm.el (redit-client-after-change-function): memo + insert/delete 発行判別の説明 + + バッファで編集が行われたとき、次の二つの hook が反応します。 + + - before-change-functions + バッファが変更される『直前』に呼ばれる。 + 引数には変更される予定の範囲の begin point と end point を受け取る。 + + - after-change-functions + バッファが変更された『直後』に呼ばれる。 + 引数には変更された範囲の begin point と end point 、 + そして二つの間にあった文字列の長さ length を受け取る。 + + 例えば + + 1: hoge (n: は n行目 を表す) + + というバッファがあったとする。そこで、hoge の後ろで改行を入れてみると + + 1: hoge + 2: + + となる。この時の before, after が受け取る begin と end の行数は + + before: begin=1 end=1 + after : begin=1 end=2 + + また、hoge の後ろに文字を追加すると + + 1: hogea + + この時は以下のようになる。 + + before: begin=1 end=1 + after : begin=1 end=1 + + 次に、以下のようなバッファがあるとする + + 1: huge + 2: sanma + + この時、2行目の先頭で backspace (DEL) をすると + + 1: hugesanma + + となる。この時の begin/end は以下のようになる。 + + before: begin=1 end=2 + after : begin=1 end=1 + + 以上の動作を複数行でやってみる。 + + 1: a + 2: b + 3: c + + というバッファで、1〜3を C-Space, M-w, C-y というカット&ペーストで + 3行目の c の直後で行う。 + + 1: a + 2: b + 3: ca + 4: b + 5: c + + before: begin=3 end=3 + after : begin=3 end=5 + before: begin=3 end=5 + after : begin=3 end=5 + before: begin=5 end=5 + after : begin=5 end=5 + + C-y (yank) を行うと、何故か 3回呼ばれる。(要ソース読み) + 次に + + 1: a + 2: b + 3: ca + 4: b + 5: c + + というバッファがあるとして、5行目 から 2行目のbの直後までを + C-Space,C-W でカットする。すると + + 1: a + 2: b + + before: begin=2 end=5 + after : begin=2 end=2 + + となる。 + + 以上のことをふまえて、以下の法則を見つけた(気がする) + + if (before-begin == after-end) { + (before-begin == before-end) { + if (previous-edited-line == before-end) { + // 前回と違う行を編集しているので + // previous-edited-line を insert + // before-end を次の previous-edited-line にする。 + } else { + // 同じ行での編集なので何もしない + } + } else { + // before-begin < line <= before-end を delete (改行コードも) + // before-begin を insert + // #1 + } + } else { + // 1. 編集前と後でバッファの最終行が変更されていれば、 + // その差だけ改行を insert で before-begin に送る + // #2 + + + // 2. 1. で得られた差の数値を、 + // previous-edited-line が before-begin より下であれば + // previous-edited-line に 加える。 + // #3 + + // 3. before-begin > previous-edited-line + // && after-end < previous-edited-line + // の時、previous-edited-line を insert + + // 4. before-begin <= line <= after-end までを insert + } + + - #1 + before-begin を delete でない理由ですが、 + 例えばこんな感じ。まあ上にもあるけど + + 1: a + 2: b + + ってあって、b の先頭で DEL すると + + 1: ab + + になる。このとき、before-end である 2 行目は削除されるわけですが + before-begin である 1 行目は 変更されている。 + なので、before-begin だけは insert を行っているというわけ。 + + 1: + 2: + + で、2行目で DEL だと + + 1: + + になるわけで、この場合は変更してないので送る必要はないが、 + そこまで判別するのもアレなので、今は同じく送るようにしている。 + 実害ないし。無駄なのが送られるってのはアレだが。 + + - #2 + 改行を入れる必要性について。 + 例えば + + 1: a + 2: b + 3: c + 4: d + + ってあったとき、1〜3をカットし、3の直後にペースとしたとすると + + 1: a + 2: b + 3: ca + 4: b + 5: c + 6: d + + ってなるのが理想的なわけです。しかし、改行を入れてないと + + 1: a + 2: b + 3: ca + 4: b + 5: c + + と、もともと 4 行目にあった d が上書きされてしまう。 + exec-insert-line では、バッファ範囲外の line number に + insert 命令が来ると、自動的にその行まで改行を入れるように + 記述してあります。なので、この場合だと、変更があったのは 3〜5なので + 相手側では 5 行目までは行が確保されるわけ。しかし、insert そのものは、 + line number の文字列を消去して書き直す、 + 上書きみたいなものなので、こんなことがおこったと。 + ですから、予め改行しておいて下の行をずらしておき、 + 書く予定の行を確保しておく。 + イメージ的には、改行いれた後は以下の感じ。 + 最終行が 4 から 6 に変更されてるので、受け取り側には + 改行を 2 個送って、そのあと変更部分 (3〜5) を送る + + 改行のみ + 1: a + 2: b + 3: c + 4: + 5: + 6: d + insert + 1: a + 2: b + 3: ca + 4: b + 5: c + 6: d + + #3 + 差の分を previous-edited-line に加えているのは、 + 改行によって行数がずれているからです。 + before-begin より上の行であれば影響されないので無視。 + + ======= + + 以上。 + これでいいんじゃないかと思いました。 + 現在この感じで実装していますが、今のところちゃんと動いてます(きっと) + yank で before/after が 3回も呼ばれているのは謎です。 + ちらっとソース読んでみたんですが。。。。ふーんみたいな(何 + 今のままだと無駄に3回 insert が呼ばれてしまうので + optimize 的な意味では、ちゃんと yank を理解すべき。 + 今は、正確に適切な行が insert/delete されているかを + 確かめる状況なので、ひとまず置いておく。 + + 上の条件式で、「もっとコンパクトにできる」「意味不明」ってのがあるかも + 誰かヘルプみー + 2008-08-26 Wataru MIYAGUNI * redit-client-sm.el (redit-client-delete-line): TODO diff -r 3bbcdd5060a6 -r 91236a2c6e2d redit-client-sm.el --- a/redit-client-sm.el Tue Aug 26 20:45:22 2008 +0900 +++ b/redit-client-sm.el Wed Aug 27 16:06:20 2008 +0900 @@ -50,6 +50,9 @@ (defvar redit-client-previous-edited-line 1 "Current cursor line of remote-edit client.") +(defvar redit-client-previous-max-line 1 + "The max line in the current buffer") + (defvar redit-client-after-edited-line 1 "Current cursor line of remote-edit client.") @@ -70,13 +73,13 @@ (defconst redit-process-port 8766 "Default port that REP use") -(defvar redit-client-begin-line-before-func nil +(defvar redit-client-before-begin-line nil "Begin edited line of redit-client-before-change-function") -(defvar redit-client-end-line-before-func nil +(defvar redit-client-before-end-line nil "End edited line of redit-client-before-change-function") -(defvar redit-client-begin-line-after-func nil +(defvar redit-client-after-begin-line nil "Begin edited line of redit-client-after-change-function") -(defvar redit-client-end-line-after-func nil +(defvar redit-client-after-end-line nil "End edited line of redit-client-after-change-function") @@ -268,8 +271,10 @@ (defun redit-get-text-from-pkt (pkt &optional _siz) (let ((size) (text) (offset redit-command-offset-txt)) (setq size (if (null _siz) (redit-get-text-size-from-pkt pkt) _siz)) - (setq text (substring pkt offset (+ offset size))) - (decode-coding-string text redit-string-coding))) + (if (= size 0) "" + (progn + (setq text (substring pkt offset (+ offset size))) + (decode-coding-string text redit-string-coding))))) ;;;;;;;;;;;;;;;;;; ;; User Command ;; @@ -370,18 +375,24 @@ ;; linenumで指定された行の編集をサーバへ送る ;; redit-client-process に insert コマンドと ;; バッファ番号、行番号、行の内容を送り、 Ack を受け取る -(defun redit-client-insert-line (linenum isnewline) +;; _text が指定されている場合、バッファのlinenumの文字列ではなく +;; _text で指定された文字列を送信する +(defun redit-client-insert-line (linenum isnewline &optional _text) (if redit-client-process (save-excursion (let ((beginp) (endp) (text) (text-size) (packet) (sinfo)) - (setq beginp - (progn (goto-line linenum) (beginning-of-line) (point))) - (setq endp - (progn (goto-line linenum) (end-of-line) (point))) - (setq text - (concat (encode-coding-string - (buffer-substring beginp endp) redit-string-coding) - (if (eq isnewline t) "\n"))) + (if (null _text) + (progn + (setq beginp + (progn (goto-line linenum) (beginning-of-line) (point))) + (setq endp + (progn (goto-line linenum) (end-of-line) (point))) + (setq text + (concat (encode-coding-string + (buffer-substring beginp endp) + redit-string-coding) + (if (eq isnewline t) "\n")))) + (setq text _text)) (setq text-size (string-bytes text)) (setq sinfo (redit-sinfo-get-buf2sinfo (buffer-name))) @@ -415,8 +426,12 @@ redit-client-editor-id (gen-seqno) linenum 5 "55555")) - (process-send-string redit-client-process packet))) - (error "redit-client is not running."))) + (process-send-string redit-client-process packet)) + (message (format + "delete-line-command: %d %d %d %d %d %d %s" + redit-delete-line-command redit-client-session-id + redit-client-editor-id 0 linenum 5 "55555"))) + (error "redit-client is not running."))) ;; redit-client-process へcloseコマンドを送る (defun redit-client-close () @@ -455,8 +470,6 @@ 'redit-client-before-change-function t) (remove-hook 'after-change-functions 'redit-client-after-change-function t) - - (message (format "redit-client-process-filter: %s" string)) (let (command textsize text allsize cursize) ;; 前回に余った奴があれば、それを前方追加する @@ -589,39 +602,67 @@ ;; begin と end には変更前の変更部分の始まりと終わりの point が入る (defun redit-client-before-change-function (begin end) ;; (message "before-change-function") - (setq redit-client-begin-line-before-func (real-count-lines begin)) - (setq redit-client-end-line-before-func (real-count-lines end))) + (setq redit-client-before-begin-line (real-count-lines begin)) + (setq redit-client-before-end-line (real-count-lines end)) + (setq redit-client-previous-max-line (real-count-lines (point-max))) + ) ;; after-change-functions に hook される ;; バッファが変更された直後に呼ばれる ;; begin と end には変更後の変更部分の始まりと終わりの point が入る (defun redit-client-after-change-function (begin end length) ;; (message "after-change-function") - (let (endl) - (setq redit-client-begin-line-after-func (real-count-lines begin)) - (setq redit-client-end-line-after-func (real-count-lines end)) + (let ((endl) (cur-max-line) (max-line-diff) (prev-edit-line)) + (setq redit-client-after-begin-line (real-count-lines begin)) + (setq redit-client-after-end-line (real-count-lines end)) + (setq cur-max-line (real-count-lines (point-max))) + (setq prev-edit-line redit-client-previous-edited-line) - (if (and - (= redit-client-begin-line-before-func - redit-client-end-line-after-func) - (not (= redit-client-begin-line-before-func - redit-client-end-line-before-func))) + ;; 詳しくは ChangeLog [2008-08-27] を見て + (if (= redit-client-before-begin-line + redit-client-after-end-line) + ;; 同じ行での編集 + (if (= redit-client-before-begin-line + redit-client-before-end-line) + ;; 前回編集した所と違う行を編集していれば、 + ;; 前回編集した行を insert する + (if (not (= redit-client-after-end-line + redit-client-previous-edited-line)) + (redit-client-insert-line + redit-client-previous-edited-line nil)) + (progn + ;; 行の削除が行われた + (setq endl redit-client-before-end-line) + (while (> endl redit-client-before-begin-line) + (redit-client-delete-line endl) + (setq endl (1- endl))) + (redit-client-insert-line + redit-client-before-begin-line nil))) (progn - ;; delete - (setq endl redit-client-end-line-before-func) - (while (> endl redit-client-begin-line-before-func) - (redit-client-delete-line endl) - (setq endl (1- endl))) - (redit-client-insert-line redit-client-begin-line-before-func nil)) - (progn - ;; insert - (setq endl redit-client-end-line-after-func) - (while (>= endl redit-client-begin-line-before-func) + ;; 行の追加が行われた + + ;; 改行の追加 + (setq max-line-diff (- cur-max-line redit-client-previous-max-line)) + (if (> max-line-diff 0) + (progn + (if (> prev-edit-line redit-client-before-begin-line) + (setq prev-edit-line (+ prev-edit-line max-line-diff))) + (let (line-feed) + (setq line-feed (make-string max-line-diff ?\n)) + (redit-client-insert-line redit-client-before-begin-line + nil line-feed)))) + (if (not (is-value-in-range redit-client-before-begin-line + prev-edit-line + redit-client-after-end-line)) + (redit-client-insert-line prev-edit-line nil) + ) + (setq endl redit-client-after-end-line) + (while (>= endl redit-client-before-begin-line) (redit-client-insert-line endl nil) (setq endl (1- endl)))) ) - - (setq redit-client-previous-edited-line endl))) + + (setq redit-client-previous-edited-line redit-client-after-end-line))) ;; 引き数で与えられた string (line_num + text_data) から ;; 指定された行を削除し、そこに text_data を挿入する @@ -661,17 +702,20 @@ ;; 引き数 string (line_num + text_data) で指定された行を削除する (defun redit-client-exec-delete-line (string) - (let ((linenum (redit-get-line-number-from-pkt string))) - (goto-line linenum) - ;; 行頭から末尾までのテキストを削除 - (delete-region (progn (beginning-of-line) (point)) - (progn (end-of-line) (point))) - ;; 指定された行自体を削除 - ;; 指定された行番号は別の行の番号を表すことになる - (if (= (point) (point-max)) - (delete-backward-char 1) - (delete-char 1))) - ) + (let ((diff) (linenum (redit-get-line-number-from-pkt string))) + (setq diff (goto-line linenum)) + (if (= diff 0) + (progn + ;; 行頭から末尾までのテキストを削除 + (delete-region (progn (beginning-of-line) (point)) + (progn (end-of-line) (point))) + ;; 指定された行自体を削除 + ;; 指定された行番号は別の行の番号を表すことになる + (if (= (point) (point-max)) + (delete-backward-char 1) + (delete-char 1))) + ) + )) ;; 何用?相手が開いてるファイルを取得するのかな? ;; read みたいなもん? @@ -949,35 +993,6 @@ ;; (print redit-client-send-queue)) ;; -;; -;; insert-line-to-buffer.el -;; 指定したバッファの、指定した行に、指定した文字列を挿入。 -;; バッファの最後尾行が指定した行に足りない場合、その数だけ改行する -;; -;; buffer : 編集するバッファ(名) -;; lineno : 編集するbufの行番号 -;; string : lineno行目に挿入する文字列 -(defun insert-line-to-buffer (buffer lineno string) - "Insert STRING at line LINENO of BUFFER. -The line LINENO is deleted, and STRING is inserted." - (let (curlineno) - (save-excursion - (set-buffer (get-buffer-create buffer)) - (goto-line lineno) ;; 指定行番号へ移動 - (setq curlineno (real-count-lines (point))) ;; 現在の行番号 - - (if (> lineno curlineno) - ;; buffer の 最後の行番号が、指定した lineno に足りない場合、 - ;; その行数だけ改行し、その行へ移動する。 - ;; newline なので、下のようにテキストを削除する必要は無い - (progn (end-of-line) - (newline (- lineno curlineno)) - (goto-line lineno)) - - ;; 行頭から末尾までのテキストを削除 - (delete-region (progn (beginning-of-line) (point)) - (progn (end-of-line) (point))) - ) - - ;; 新しい行を挿入 - (insert string)))) +(defun is-value-in-range (min value max) + (if (and (<= min value) (<= value max)) + t nil)) \ No newline at end of file