view redit-client-sm.el @ 62:5e44d3723fa9 v20080828-1

*** empty log message ***
author gongo
date Thu, 28 Aug 2008 18:23:27 +0900
parents 2dea55a8d0e4
children 0f7d2aab0e04
line wrap: on
line source

;; 	$Id$

;; cl-macs でマクロとして定義されているため
;; cl.el をインクルードしておく
(require 'cl)

;;
;; REP Command
;;
;;  32 bits    32       32       32      32       32      textsiz
;; o-------o--------o--------o-------o--------o---------o-------...
;; +-------+--------+--------+-------+--------+---------+------+
;; | cmd   | session| editor | seqid | lineno | textsiz | text |
;; |       | id     | id     |       |        |         |      |
;; +-------+--------+--------+-------+--------+---------+------+
;; o-------header section (network order)---------------o-------...
;;
;; textsiz:
;;    This size is number of bytes for character string encoded with UTF8.
;;    
;; text:
;;    This size is variable length (textsiz).
;;

(defvar redit-client-process nil
  "Remote-edit client process.")

(defvar redit-client-buffer nil
  "Remote-edit client's buffer.")

(defvar redit-client-buffer-name nil
  "Remote-edit client's buffer-name.")

(defvar redit-client-buffer-number nil
  "Remote-edit client's buffer-number.")

;; obsolete
;; SessionInfo で管理する
;;(defvar redit-client-editor-id "0"
;;"Remote-edit client's editor-id.")

;; obsolete
;; SessionInfo で管理する
;;(defvar redit-client-session-id 0
;;"Remote-edit client's session-id.")

;; editor local sequence number
(defvar redit-client-seqno 0
  "Remote-edit client's editor-id.")

(defvar redit-client-line-max-in-server nil
  "Value of point-max in server's buffer.")

;; obsolete
;; Session と繋がっている各バッファで保持する事になったので
;; SessionInfo に持たせている
;;(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.")

(defvar redit-client-previous-line 1
  "Behind cursor line of remote-edit client.")

(defvar redit-max-string-length 1004
  "Read-Write max length of string.")

(defvar redit-process-filter-resume nil
  "String of the unprocessing in redit-client-process-filter")

;; REP が使用する文字コード
(defconst redit-string-coding 'utf-8
  "Default coding that REP use")

;; port
(defconst redit-process-port 8766
  "Default port that REP use")

(defvar redit-client-before-begin-line nil
  "Begin edited line of redit-client-before-change-function")
(defvar redit-client-before-end-line nil
  "End edited line of redit-client-before-change-function")
(defvar redit-client-after-begin-line nil
  "Begin edited line of redit-client-after-change-function")
(defvar redit-client-after-end-line nil
  "End edited line of redit-client-after-change-function")


;; REP command header size
(defconst redit-command-header-size 24)

(defconst redit-command-offset-cmd   0)
(defconst redit-command-offset-sid   4)
(defconst redit-command-offset-eid   8)
(defconst redit-command-offset-seq  12)
(defconst redit-command-offset-lno  16)
(defconst redit-command-offset-siz  20)
(defconst redit-command-offset-txt  24)



(defconst redit-open-command           1)
(defconst redit-open-ack               2)
(defconst redit-read-command           3)
(defconst redit-read-ack               4)
(defconst redit-read-finish-ack        5) ;; obsolete
(defconst redit-insert-command         6)
(defconst redit-insert-ack             7)
;;(defconst redit-write-still-ack        "7")
;;(defconst redit-write-finish-ack       "8")
(defconst redit-delete-line-command    9)
(defconst redit-delete-line-ack       10)
(defconst redit-close-command         11)
(defconst redit-close-ack             12)
(defconst redit-replace-command       13)
(defconst redit-replace-ack           14)
(defconst redit-nop-command           15)

;; REP Session
(defconst redit-join-command           41)
(defconst redit-join-ack-command       42)
(defconst redit-get-command            43) ;; obsolete
(defconst redit-get-ack-command        44) ;; obsolete
(defconst redit-put-command            45)
(defconst redit-put-ack-command        46)
(defconst redit-select-command         47) ;; obsolete
(defconst redit-select-ack-command     48)
(defconst redit-register-command       49) ;; obsolete
(defconst redit-register-ack-command   50) ;; obsolete
(defconst redit-deregister-command     51) ;; obsolete
(defconst redit-deregister-ack-command 52) ;; obsolete
(defconst redit-quit-command           53)
(defconst redit-quit-ack-command       54)


(defconst redit-debug-flag t)
(defun redit-debug-message (string)
  (if (eq redit-debug-flag t)
      (message string)))

;;;;;;;;;;;;;;;;;;
;; Queue struct ;;
;;;;;;;;;;;;;;;;;;
(defstruct Queue (front nil) (rear nil))

;; memo
;;  car: return first element of list
;;  cdr: return elements list since the second of list
;;  (ex.)
;;     (car '(rose violet daisy buttercup)) => rose
;;     (cdr '(rose violet daisy buttercup)) => (violet daisy buttercup)

;; enqueue item in queue
(defun enqueue (queue item)
  (let ((new-cell (list item)))
    (if (Queue-front queue)
  	;; update last cell
  	(setf (cdr (Queue-rear queue)) new-cell)
      ;; if queue is empty
      (setf (Queue-front queue) new-cell))
    (setf (Queue-rear queue) new-cell)))

;; dequeue
(defun dequeue (queue)
  (if (Queue-front queue)
      (prog1
  	  (pop (Queue-front queue))
  	(unless (Queue-front queue)
  	  ;; if queue is empty
  	  (setf (Queue-rear queue) nil)))))

(defun dequeue-all (queue)
  "clean queue"
  (while (Queue-front queue)
    (dequeue queue)))

(defun redit-get-command-from-queue (queue)
  (nth 0 (Queue-front queue)))
(defun redit-get-line-from-queue (queue)
  (nth 1 (Queue-front queue)))
(defun redit-get-uid-from-queue (queue)
  (nth 2 (Queue-front queue)))
(defun redit-get-text-from-queue (queue)
  (nth 3 (Queue-front queue)))

;;;;;;;;;;;;;;;;;;;;;;;;;
;; Session Info struct ;;
;;;;;;;;;;;;;;;;;;;;;;;;;

;; sid: Session ID
;; eid: Editor ID
;; prevline: この Session に対応するバッファで最後に編集された行
;; send_cmdlist: rep_command list from user to SessionManager
;; recv_cmdlist: rep_command list from SessionManager to user
(defstruct SessionInfo (sid nil) (eid nil) (prevline 1)
  (send_cmdlist nil) (recv_cmdlist nil))

;; create and initialize
(defun redit-create-session-info (sid eid)
  (make-SessionInfo :sid sid
		    :eid eid
		    :send_cmdlist (make-Queue)
		    :recv_cmdlist (make-Queue)))

(defun redit-get-session-id-from-session-info (sinfo)
  (SessionInfo-sid sinfo))
(defun redit-get-editor-id-from-session-info (sinfo)
  (SessionInfo-eid sinfo))
(defun redit-get-sendqueue-from-session-info (sinfo)
  (SessionInfo-send_cmdlist sinfo))
(defun redit-get-recvqueue-from-session-info (sinfo)
  (SessionInfo-recv_cmdlist sinfo))

;; hash table of SessionInfo

(defvar htable-sid2bufname (make-hash-table))
(defvar htable-bufname2sinfo (make-hash-table))

(defun redit-sinfo-put-sid2buf (sid bufname)
  (setf (gethash sid htable-sid2bufname) bufname))
(defun redit-sinfo-get-sid2buf (sid)
  (gethash sid htable-sid2bufname))
(defun redit-sinfo-rm-sid2buf (sid)
  (remhash sid htable-sid2bufname))

(defun redit-sinfo-put-buf2sinfo (bufname sinfo)
  (setf (gethash bufname htable-bufname2sinfo) sinfo))
(defun redit-sinfo-get-buf2sinfo (bufname)
  (gethash bufname htable-bufname2sinfo))
(defun redit-sinfo-rm-buf2sinfo (bufname)
  (remhash bufname htable-bufname2sinfo))


;;;;;;;;;;;;;;;;;;;
;; pack / unpack ;;
;;;;;;;;;;;;;;;;;;;
(defun redit-pack-int-loop (num count)
  (if (> count 0)
      (concat
       (redit-pack-int-loop (/ num 256) (- count 1))
       (char-to-string (% num 256)))))

(defun redit-pack-int (num)
  (redit-pack-int-loop num 4))

(defun redit-pack (cmd sid eid seq lno siz)
  (concat
   (redit-pack-int cmd) (redit-pack-int sid) (redit-pack-int eid)
   (redit-pack-int seq) (redit-pack-int lno) (redit-pack-int siz)))

(defun redit-unpack-int-loop (pkt pos count)
  (if (> count 0)
      (+
       (* (string-to-char (substring pkt pos (1+ pos))) (expt 256 (1- count)))
       (redit-unpack-int-loop pkt (1+ pos) (1- count)))
    0
    ))

(defun redit-unpack-int (pkt pos)
  (redit-unpack-int-loop pkt pos 4))

(defun redit-make-packet (cmd sid eid seq lno siz text)
  (concat (redit-pack cmd sid eid seq lno siz) text))

(defun redit-get-command-from-pkt (pkt)
  (redit-unpack-int pkt redit-command-offset-cmd))
(defun redit-get-session-id-from-pkt (pkt)
  (redit-unpack-int pkt redit-command-offset-sid))
(defun redit-get-editor-id-from-pkt (pkt)
  (redit-unpack-int pkt redit-command-offset-eid))
(defun redit-get-sequence-id-from-pkt (pkt)
  (redit-unpack-int pkt redit-command-offset-seq))
(defun redit-get-line-number-from-pkt (pkt)
  (redit-unpack-int pkt redit-command-offset-lno))
(defun redit-get-text-size-from-pkt (pkt)
  (redit-unpack-int pkt redit-command-offset-siz))

;; text のサイズが指定されていればそれを使い、
;; そうでなければ、redit-get-text-size-from-pkt を実行する
(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))
    (if (= size 0) ""
      (progn
	(setq text (substring pkt offset (+ offset size)))
	(decode-coding-string text redit-string-coding)))))

;;;;;;;;;;;;;;;;;;
;; User Command ;;
;;;;;;;;;;;;;;;;;;

(defun redit-join-command ()
  "Allow this Emacs process to be a remote-edit session manager for client processes."
  (interactive)
  (let (bufname)
    (setq bufname (encode-coding-string
		   (buffer-name)
		   redit-string-coding))

    (if redit-client-process
	(progn
	  (set-process-sentinel redit-client-process nil)
	  (condition-case () ;;
	      (delete-process redit-client-process) (error nil))))
    
    (setq redit-client-process (open-network-stream
				"redit-client" nil "localhost"
				redit-process-port))

    ;; called when the process changes state
    (set-process-sentinel redit-client-process 'redit-client-sentinel)
    
    ;; called when the process receive packet
    (set-process-filter redit-client-process 'redit-client-process-filter)
    
    ;; 送受信するデータはバイナリで受け取る。
    (set-process-coding-system redit-client-process 'binary 'binary)
    
    ;; プロセスが生きてるとき、 emacs を終了しようとすると
    ;; 「processがうんたらで、本当に切ってもいいの y/n」
    ;; って聞かれる。nil てやっておくと聞かれない。
    ;; quit を入れるんなら t にして有効にすべきなんだろうか。
    ;; 今は nil で
    (set-process-query-on-exit-flag redit-client-process nil)
    
    ;; not necessary since Emacs 21.1
    ;; (make-local-hook 'before-change-functions)
    ;; (make-local-hook 'after-change-functions)
    
    (if (string= "*scratch*" bufname) 
	(progn
	  ;; JOIN if buffer-name is "*scratch*"
	  (process-send-string redit-client-process
			       (redit-make-packet
				redit-join-command 0 0 0 0 7 "bufname"))
	  (redit-debug-message
	   (format "join-command: %d 0 0 0 0 0" redit-join-command))
	  )
      ;; PUT if buffer-name is not "*scratch*"
      (progn
	(process-send-string redit-client-process
			     (redit-make-packet
			      redit-put-command 0 0 0 0
			      (string-bytes bufname) bufname))
	(redit-debug-message
	 (format "put-command: %d 0 0 0 0 %d %s"
		 redit-put-command (string-bytes bufname) (buffer-name)))
	)
      )))

(defun redit-quit-command ()
  "quit"
  (interactive)
  ;; process kill とかしよう
  )


;; obsolete
;;(defun redit-client-open (file)
;;  (interactive "P")
;;  (if redit-client-process
;;      (if (setq file (read-string "Filename: "))
;;	  (progn
;;	    ;; redit-client-process の input に文字列を送信する
;;	    (process-send-string
;;	     redit-client-process
;;	     ;; redit-open-command (01) と file を連結させる
;;	     (concat redit-open-command file))
;;	    ;; redit-client-process から出力を得るまでループ
;;	    (while (eq nil (accept-process-output redit-client-process)))))
;;    (error "redit-client is not running.")))

;; redit-client-process にreadコマンドとバッファ番号、行番号を送り、
;; サーバからの出力を得る
;;(defun redit-client-read-line (linenum)
;;  (if redit-client-process
;;      (save-excursion
;;	(progn
;;	  (process-send-string
;;	   redit-client-process
;;	   (format "%10d%10d%10d%10d%10d%10d\n" redit-read-command redit-client-session-id redit-client-editor-id (gen-seqno) linenum 0))
;;	  (while (eq nil
;;		     (accept-process-output redit-client-process)))))
;;    (error "redit-client is not running.")))


;; linenumで指定された行の編集をサーバへ送る
;; redit-client-process に insert コマンドと
;; バッファ(sid)番号、行番号、行の内容を送り、 Ack を受け取る
;; _text が指定されている場合、バッファのlinenumの文字列ではなく
;; _text で指定された文字列を送信する
(defun redit-client-insert-line (linenum isnewline &optional _text)
  (if redit-client-process
      (save-excursion
	(let (sinfo packet text text-size)
	  (if (null _text)
	      (let (beginp endp)
		(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
			       (concat (buffer-substring beginp endp)
				       (if (eq isnewline t) "\n"))
			       redit-string-coding)
			      )))
	    (setq text _text))
	  (setq text-size (string-bytes text))
	  (setq sinfo (redit-sinfo-get-buf2sinfo (buffer-name)))
	  (if sinfo
	      (let ((sid (SessionInfo-sid sinfo))
		    (eid (SessionInfo-eid sinfo)) (packet))
		
		(setq packet (redit-make-packet
			      redit-insert-command sid eid
			      (gen-seqno) linenum text-size text))
		
		(process-send-string redit-client-process packet)
		
		(redit-debug-message
		 (format "insert-command: %d %d %d %d %d %d %s"
			 redit-insert-command sid eid (gen-seqno)
			 linenum text-size text))
		))
	  ))
    (error "redit-client is not running.")))

;; linenum で指定した行の削除命令を redit-client-process に送信する
(defun redit-client-delete-line (linenum)
  (if redit-client-process
      (save-excursion
	(let (sinfo)
	  (setq sinfo (redit-sinfo-get-buf2sinfo (buffer-name)))
	  (if sinfo
	      (let ((sid (SessionInfo-sid sinfo))
		    (eid (SessionInfo-eid sinfo)) (packet))
		;; insert lines on server buffer
		(setq packet (redit-make-packet
			      redit-delete-line-command sid eid
			      (gen-seqno) linenum
			      5 "55555"))
	    
		(process-send-string redit-client-process packet)

		(redit-debug-message
		 (format "delete-line-command: %d %d %d %d %d %d %s"
			 redit-delete-line-command sid eid (gen-seqno)
			 linenum 5 "55555"))
		))
	  ))
    (error "redit-client is not running.")))

;; redit-client-process へcloseコマンドを送る
;;(defun redit-client-close ()
;;  "Save File and exit Remote-edit client."
;;  (interactive)
;;  (let ((line redit-client-previous-line))
;;    (redit-client-insert-line line nil)
;;    (process-send-string
;;     redit-client-process
;;     (concat
;;      ;; obsolete send string
;;      ;; (format "%10d%10d%10d%10d%10d%10d" (string-to-number redit-close-command) redit-client-session-id redit-client-editor-id (gen-seqno) 0 0)
;;      (redit-pack
;;       redit-close-command
;;       redit-client-session-id
;;       redit-client-editor-id
;;       (gen-seqno) 0 0) "\n"))))

;; redit-client-process を終了させる
;;(defun redit-client-kill ()
;;  "Stop Remote-Edit client process."
;;  (interactive)
;;  (if redit-client-process
;;      (kill-process redit-client-process))
;;  (if redit-client-buffer
;;      (kill-buffer redit-client-buffer))
;;  (kill-all-local-variables))

;; proc == redit-client-process
;; string == redit-client-process からの output
(defun redit-client-process-filter (proc string)
  ;; バッファローカルなフックリスト before-change-functions から
  ;; redit-client-before-change-function を取り除く. after も同じ
  ;; proc からの出力に hook しないように
  (remove-hook 'before-change-functions
	       'redit-client-before-change-function)
  (remove-hook 'after-change-functions
	       'redit-client-after-change-function)
    
  (let (cmd sid eid seq lno siz txt allsize cursize)
    ;; 前回に余った奴があれば、それを前方追加する
    (setq string (concat redit-process-filter-resume string))
    (setq allsize (length string))
    
    (block loop
      ;; process-filter が受け取れるのは1024byteまで。
      ;; なので、string に入っているパケットが途中で切れてる可能性がある
      (while (> allsize redit-command-header-size)
	(setq siz (redit-get-text-size-from-pkt string))
	
	;; ヘッダ (redit-command-header-size) + テキストサイズ
	(setq cursize (+ redit-command-header-size siz))

	;; パケットが 1024 byte の壁で途切れてる場合、
	;; テキストが読めないので、ループを抜ける
	(if (> cursize allsize) (return-from loop))

	(setq cmd (redit-get-command-from-pkt string))
	(setq sid (redit-get-session-id-from-pkt string))
	(setq eid (redit-get-editor-id-from-pkt string))
	(setq seq (redit-get-sequence-id-from-pkt string))
	(setq lno (redit-get-line-number-from-pkt string))
	(setq txt (redit-get-text-from-pkt string))
	
	;; command がどの命令かを判断し、対応した処理をする。case みたい
	(cond 
	 ;; FIXME
	 ;;((if (= command redit-open-ack)
	 ;;(if (/= redit-client-editor-id eid)
	 ;;(redit-client-exec-open-ack string))))

	 ;;((if (= cmd redit-read-ack)
	 ;;(if (= redit-client-editor-id (redit-get-editor-id-from-pkt string))
	 ;;(redit-client-exec-read-ack string))))
	   
	 ;;((if (= command redit-insert-ack)
	 ;;(if (= redit-client-editor-id (redit-get-editor-id-from-pkt string))
	 ;;(redit-client-exec-insert-ack string))))
	   
	 ;;((if (= command redit-delete-line-ack)
	 ;;(if (= redit-client-editor-id (redit-get-editor-id-from-pkt string))
	 ;;(redit-client-exec-delete-line-ack string))))
	   
	 ;;((if (= command redit-close-ack)
	 ;;(if (= redit-client-editor-id (redit-get-editor-id-from-pkt string))
	 ;;(redit-client-exec-close-ack string))))
	   
	 ((if (= cmd redit-insert-command)
	      (redit-client-exec-insert-line sid lno txt)))
	 
	 ((if (= cmd redit-join-ack-command)
	      (redit-client-exec-join txt sid eid)))
	   
	 ((if (= cmd redit-put-ack-command)
	      (redit-client-exec-put sid eid)))

	 ((if (= cmd redit-delete-line-command)
	      (redit-client-exec-delete-line sid lno)))
	 )

	(setq allsize (- allsize cursize))
	;; string の分割
	(setq string (substring string cursize))
	)
      )
    )
      
  ;; 途切れたやつは次の process-filter の string に入っているので
  ;; この時点で余った奴を保存し、
  ;; 次の process-filter の string の前に連結する
  (setq redit-process-filter-resume string)
  
  (add-hook 'before-change-functions
	    'redit-client-before-change-function t)
  (add-hook 'after-change-functions
	    'redit-client-after-change-function t)
  )

;; window-scroll-functions に hook される。
;; window がスクロールする度に呼ばれる
;;(defun redit-client-update-function (window window-start)
;;  (if (equal (window-buffer) redit-client-buffer)
;;      (save-excursion
;;	(remove-hook 'before-change-functions
;;		     'redit-client-before-change-function t)
;;	(remove-hook 'after-change-functions
;;		     'redit-client-after-change-function t)
;;	;; read part.
;;	(while (and (= (point-max)
;;		       (window-end nil t))
;;		    (> redit-client-line-max-in-server
;;		       (real-count-lines (window-end nil t))))
;;	  ;; 		       (count-lines (window-end nil t))))
;;	  (redit-client-read-line (real-count-lines (window-end nil t))))
;;	
;;	(if (not (= (count-lines (point-min) (window-end nil t))
;;		    (real-count-lines (window-end nil t))))
;;	    (redit-client-read-line
;;	     (progn (goto-line redit-client-line-max-in-server)
;;		    (real-count-lines (point)))))
;;	
;;	(if (< redit-client-line-max-in-server
;;	       (real-count-lines (window-end nil t)))
;;	    (progn (goto-line (window-end nil t))
;;		   (delete-region
;;		    (progn (beginning-of-line) (backward-char) (point))
;;		    (progn (forward-char) (end-of-line) (point)))))
;;	
;;	(add-hook 'before-change-functions
;;		  'redit-client-before-change-function t t)
;;	(add-hook 'after-change-functions
;;		  'redit-client-after-change-function t t))))

;; currentp の位置の行数を返す
(defun real-count-lines (currentp)
  (+ (count-lines (point-min) currentp)
     (if (= (save-excursion (goto-char currentp) (current-column))
	    0)
	 1  0)))

;;(defun limited-save-excursion (&rest exprs)
;;  (let ((saved-point (point)))
;;    (while exprs
;;      (eval (car exprs))
;;      (setq exprs (cdr exprs)))
;;    (goto-char saved-point)))

;; before-change-functions に hook される
;; バッファが変更される直前に呼ばれる。
;; begin と end には変更前の変更部分の始まりと終わりの point が入る
(defun redit-client-before-change-function (begin 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)
  (let (sinfo)
    (setq sinfo (redit-sinfo-get-buf2sinfo (buffer-name)))
    ;; sinfo が nil であれば、編集されたバッファは
    ;; REP と無関係のバッファってことで、以降無視。
    (if sinfo
	(let ((editl) (cur-max-line) (max-line-diff)
	      (prev-edit-line) (flg nil))

	  (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 (SessionInfo-prevline sinfo))

	  ;; 詳しくは ChangeLog [2008-08-28] を見て
	  (if (= redit-client-before-begin-line redit-client-after-end-line)
	      (progn
		(setq max-line-diff
		      (- redit-client-previous-max-line cur-max-line))

		;; delete-line
		(setq editl redit-client-before-end-line)
		(while (> editl redit-client-before-begin-line)
		  (redit-client-delete-line editl)
		  (setq editl (1- editl)))

		;; 前回編集行が、今回の編集の範囲に無ければ insert
		(if (not
		     (is-value-in-range redit-client-before-begin-line
					prev-edit-line
					redit-client-before-end-line))
		     (progn
		       ;; 行の削除による prev-edit-line のずれを考慮
		       (redit-client-insert-line
			(if (> prev-edit-line redit-client-before-end-line)
			    (- prev-edit-line max-line-diff) prev-edit-line)
			nil)
		       (redit-client-insert-line
			redit-client-before-begin-line nil)
		       )
		  )
		)
	    (progn
	      ;; 行の追加によって最終行が増えていれば
	      ;; insert する行の最後尾に "\n" を付加する。
	      ;; また prev-edit-line が変化している可能性があるのでそれの修正
	      (setq max-line-diff
		    (- cur-max-line redit-client-previous-max-line))
	      (if (> max-line-diff 0)
		  (setq flg t)) ;; "\n" の付加

	      ;; insert
	      (setq editl redit-client-before-begin-line)
	      (while (< editl redit-client-after-end-line)
		(redit-client-insert-line editl flg)
		(setq editl (1+ editl))
		)
	      (redit-client-insert-line redit-client-after-end-line nil)

	      ;; 前回編集行が、今回の編集の範囲に無ければ insert
	      (if (not (is-value-in-range redit-client-before-begin-line
					  prev-edit-line
					  redit-client-after-end-line))
		  (redit-client-insert-line
		   (if (> prev-edit-line redit-client-before-begin-line)
		       (+ prev-edit-line max-line-diff) prev-edit-line)
		   nil)
		)
	      )
	    )
	  
	  (setf (SessionInfo-prevline sinfo) redit-client-after-end-line)
))))

;; 引き数で与えられた string (line_num + text_data) から
;; 指定された行を削除し、そこに text_data を挿入する
(defun redit-client-exec-insert-line (sid editlineno text)
  (let ((curlineno) (bufname) (sinfo))
    ;; SessionID から、このテキストを挿入するべき
    ;; バッファ名を取得する
    (setq bufname (redit-sinfo-get-sid2buf sid))

    (save-excursion
      (set-buffer bufname)

      ;; 指定行番号へ移動
      ;; diff には、editlineno と 実際に移動した行番号の差が入る
      ;; バッファの末尾の行が指定した行に足りない場合に diff > 0 になる
      (goto-line editlineno) ;; 指定行番号へ移動
      (setq curlineno (real-count-lines (point))) ;; 現在の行番号
      (if (> editlineno curlineno)
	  ;; buffer の 最後の行番号が、editlineno に足りない場合、
	  ;; その行数だけ改行し、その行へ移動する。
	  ;; newline なので、下のようにテキストを削除する必要は無い
	  (progn (end-of-line)
		 (newline (- editlineno curlineno))
		 (goto-line editlineno))
	
	;; 行頭から末尾までのテキストを削除
	(delete-region (progn (beginning-of-line) (point))
		       (progn (end-of-line) (point)))
	)

      ;; 新しい行を挿入
      (insert text))))

;; 引き数 string (line_num + text_data) で指定された行を削除する
(defun redit-client-exec-delete-line (sid linenum)
  (let ((diff) (bufname (redit-sinfo-get-sid2buf sid)))
    (save-excursion
      (set-buffer bufname)
      (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 みたいなもん?
;; 引き数 string (buf_num + line_num + text_data) 
;;(defun redit-client-exec-open-ack (string)
;;  (save-excursion
;;    (let ((bufnum (redit-get-editor-id-from-pkt string))
;;	  (linenum (redit-get-line-number-from-pkt string)))
;;      (make-variable-buffer-local 'redit-client-buffer-name)
;;      (make-variable-buffer-local 'redit-client-editor-id)
;;      (setq redit-client-buffer-name
;;	    (redit-get-text-from-pkt string))
;;      (setq redit-client-buffer (get-buffer-create redit-client-buffer-name))
;;      ;; プロセスとバッファを関連づける
;;      (set-process-buffer redit-client-process redit-client-buffer)
;;      (switch-to-buffer redit-client-buffer) ;; windowのバッファを変更する
;;      (make-variable-buffer-local 'redit-client-previous-edited-line)
;;      (make-variable-buffer-local 'redit-client-after-edited-line)
;;      (make-variable-buffer-local 'redit-client-line-max-in-server)
;;      (make-variable-buffer-local 'redit-client-previous-line)
;;      (setq redit-client-line-max-in-server linenum)
;;      (setq redit-client-editor-id bufnum)
;;      (setq redit-client-previous-edited-line 1)
;;      (setq redit-client-after-edited-line 1)
;;      (setq redit-client-previous-line 1)
;;
;;      ;;(add-hook 'before-change-functions
;;      ;;'redit-client-before-change-function t t)
;;      ;;(add-hook 'after-change-functions
;;      ;;'redit-client-after-change-function t t)
;;
;;      ;; not necessary since Emacs 21.1
;;      ;; (make-local-hook 'window-scroll-functions)
;;
;;      ;; window がスクロールする度に呼ばれる関数群
;;      (add-hook 'window-scroll-functions
;;		'redit-client-update-function t t))))


;; string (line_num + text_data) で指定された行に
;; text_data を挿入する
;;(defun redit-client-exec-read-ack (string)
;;  (save-excursion
;;    (let ((lines (redit-get-line-number-from-pkt string)))
;;      ;; redit-client-buffer をカレントバッファにする
;;      (set-buffer redit-client-buffer)
;;      (goto-line lines)
;;      (beginning-of-line) ;; 行の始めへ移動
;;      ;;      (delete-region (progn (beginning-of-line) (point))
;;      ;; 		     (progn (end-of-line) (point)))
;;      (insert (redit-get-text-from-pkt string))))) ;; テキストデータを挿入

;; redit-client-line-max-in-server に行番号を入れてる(だけ)
;;(defun redit-client-exec-insert-ack (string)
;;  (save-excursion
;;    (set-buffer redit-client-buffer)
;;    (let ((linenum (redit-get-line-number-from-pkt string )))
;;      (setq redit-client-line-max-in-server linenum))))
;;
;;(defun redit-client-exec-delete-line-ack (string)
;;  (save-excursion
;;    (set-buffer redit-client-buffer)
;;    (let ((linenum (redit-get-line-number-from-pkt string)))
;;      (setq redit-client-line-max-in-server linenum))))

;; プロセスとバッファ、ローカル変数を削除する
;;(defun redit-client-exec-close-ack (string)
;;  (if redit-client-process
;;      (kill-process redit-client-process))
;;  (if redit-client-buffer
;;      (kill-buffer redit-client-buffer))
;;  (kill-all-local-variables))

;; 引き数で与えられた string から Editor IDを取得する
(defun redit-client-exec-join (filename sid eid)
  (let ((bufname) (sinfo))
    ;; SessionInfo の生成
    (setq sinfo (redit-create-session-info sid eid))

    ;; SessionManager から受け取った、このセッションのファイル名を
    ;; バッファ名とし、そのバッファを生成する。
    (setq bufname (buffer-name (generate-new-buffer filename)))

    ;; SessionID => BufferName => SessionInfo のテーブル生成
    (redit-sinfo-put-sid2buf sid bufname)
    (redit-sinfo-put-buf2sinfo bufname sinfo)

    (switch-to-buffer bufname)

    (redit-debug-message (format "join-ack: Session [%d] %s" sid bufname))
    (redit-debug-message (format "Your editor id = %d" eid))

))

;; //引き数で与えられた string から Session IDを取得する
;; 新仕様で、(put = ファイル有りjoin) ってことになって
;; editor id を返すことになったので、sid と共に eid もセーブする
(defun redit-client-exec-put (sid eid)
  (let (sinfo bufname)
    ;; SessionInfo の生成
    (setq sinfo (redit-create-session-info sid eid))

    ;; put (ファイル名付きjoin) を行ったバッファで
    ;; すでにファイルが開かれている(はずな)ので、それをバッファ名とする
    (setq bufname (buffer-name))

    ;; SessionID => BufferName => SessionInfo のテーブル生成
    (redit-sinfo-put-sid2buf sid bufname)
    (redit-sinfo-put-buf2sinfo bufname sinfo)

    (switch-to-buffer bufname)
    
    (redit-debug-message (format "put-ack Session [%d] %s" sid bufname))
    (redit-debug-message (format "Your editor id = %d" eid))
))
  
;; SessionIDを名前とした、新しいバッファを開く
;; FIXME
;;   SessionIDではなく、Session Nameが望ましい?
;;   バッファを開いた後、Sessionで開いてるファイルの中身を表示するべき
;;(defun redit-client-exec-select ()
;;  (switch-to-buffer (get-buffer-create
;;		     (number-to-string redit-client-session-id))))

;; プロセスの状態を見て、対応したメッセージを表示
(defun redit-client-sentinel (proc msg)
  (cond ((eq (process-status proc) 'exit)
	 (redit-debug-message "Client subprocess exited"))
	((eq (process-status proc) 'signal)
	 (redit-debug-message "Client subprocess killed"))))

(defun gen-seqno ()
  "generate editor local sequence number."
  (setq redit-client-seqno (+ redit-client-seqno 1)))


;;(defun redit-line-translate-out (cque rque)
;;  "redit line translate for output"
;;  (let ((cc) (rc) (xcc) (xrc) (ccc) (crc) (cignore 0) (rignore 0))
;;    (setq xcc 0)
;;    (setq xrc 0)
;;    (setq cignore 0)
;;    (setq rignore 0)
;;    (while cque
;;      (setq cc (car cque))
;;      (while rque
;;	(setq rc (car rque))
;;	;; -------- translation ------------
;;
;;	(if (< (+ (redit-get-line-from-queue cc) xcc) (+ (redit-get-line-from-queue rc) xrc))
;;	    (if (= (redit-get-command-from-queue cc) redit-insert-command) (setq xrc (- xrc 1)))
;;	  (if (= (redit-get-command-from-queue cc) redit-delete-line-command) (setq xrc (- xrc 1))))
;;
;;	(if (> (+ (redit-get-line-from-queue cc) xcc) (+ (redit-get-line-from-queue rc) xrc))
;;	    (if (= (redit-get-command-from-queue rc) redit-insert-command) (setq xcc (+ xcc 1)))
;;	  (if (= (redit-get-command-from-queue rc) redit-delete-line-command) (setq xcc (- xcc 1))))
;;
;;	(if (= (+ (redit-get-line-from-queue cc) xcc) (+ (redit-get-line-from-queue rc) xrc))
;;	    (if (= (redit-get-command-from-queue rc) redit-insert-command) (setq xcc (+ xcc 1)))
;;	  (if (= (redit-get-command-from-queue rc) redit-replace-command)
;;	      (if (= (redit-get-command-from-queue cc) redit-insert-command) (setq xrc (+ xrc 1)))
;;	    (if (= (redit-get-command-from-queue cc) redit-replace-command) (setq cignore 1))
;;	    (if (= (redit-get-command-from-queue cc) redit-delete-line-command) (setq crc redit-insert-command) (setq cignore 1))))
;;
;;	(if (= (redit-get-command-from-queue rc) redit-delete-line-command)
;;	    (if (= (redit-get-command-from-queue cc) redit-insert-command) (setq xrc (+ (redit-get-line-from-queue rc) 1)))
;;	  (if (= (redit-get-command-from-queue cc) redit-replace-command) (setq cignore 1))
;;	  (if (= (redit-get-command-from-queue cc) redit-delete-line-command) (setq cignore 1) (setq rignore 1)))
;;
;;	;; -------- translation ------------
;;
;;	(setq rque (cdr rque)))	;; while rque
;;
;;      ;; ignore
;;      (if (= cignore 1)
;;	  ;; xxx
;;	  (setq cignore 0))
;;      (if (= rignore 1)
;;	  ;; xxx
;;	  (setq cignore 0))
;;
;;      ;; -------- add after que ------------
;;      (enqueue after-CQ (concat (format "%2d%2d%9d" (redit-get-command-from-queue cc) (redit-get-uid-from-queue cc) (+ (redit-get-line-from-queue cc) xcc)) (redit-get-text-from-queue cc)))
;;      ;; -------- add after que ------------
;;
;;      (setq cque (cdr cque))) ;; while cque
;;    )			      ;; let
;;  )			      ;; defun
;;
;;(defun redit-line-translate-in (cque rque)
;;  "redit line translate for input"
;;  (let ((cc) (rc) (xcc) (xrc) (ccc) (crc) (cignore 0) (rignore 0))
;;    (setq xcc 0)
;;    (setq xrc 0)
;;    (setq cignore 0)
;;    (setq rignore 0)
;;    (while rque
;;      (setq rc (car rque))
;;      (setq cignore 0)
;;      (setq rignore 0)
;;      (while cque
;;	(setq cc (car cque))
;;	;; -------- translation ------------
;;
;;	(if (and (= cignore 1) (= rignore 1))
;;	    (if (< (+ (redit-get-line-from-queue rc) xrc) (+ (redit-get-line-from-queue cc) xcc))
;;		(if (= (redit-get-command-from-queue rc) redit-insert-command) (setq xcc (+ xcc 1)))
;;	      (if (= (redit-get-command-from-queue rc) redit-delete-line-command) (setq xcc (- xcc 1))))
;;
;;	  (if (> (+ (redit-get-line-from-queue rc) xrc) (+ (redit-get-line-from-queue cc) xcc))
;;	      (if (= (redit-get-command-from-queue cc) redit-insert-command) (setq xrc (- xrc 1)))
;;	    (if (= (redit-get-command-from-queue cc) redit-delete-line-command) (setq xrc (+ xrc 1))))
;;
;;	  (if (= (+ (redit-get-line-from-queue rc) xrc) (+ (redit-get-line-from-queue cc) xcc))
;;	      (if (= (redit-get-command-from-queue cc) redit-insert-command) (setq xcc (+ xcc 1)))
;;	    (if (= (redit-get-command-from-queue cc) redit-replace-command)
;;		(if (= (redit-get-command-from-queue rc) redit-insert-command) (setq xcc (+ xcc 1)))
;;	      (if (= (redit-get-command-from-queue rc) redit-replace-command) (setq cignore 1))
;;	      (if (= (redit-get-command-from-queue rc) redit-delete-line-command) (setq crc redit-insert-command) (setq cignore 1))))
;;	  (if (= (redit-get-command-from-queue cc) redit-delete-line-command)
;;	      (if (= (redit-get-command-from-queue rc) redit-insert-command) (setq xcc (+ (redit-get-line-from-queue cc) 1)))
;;	    (if (= (redit-get-command-from-queue rc) redit-replace-command) (setq crc redit-insert-command) (setq cignore 1))
;;	    (if (= (redit-get-command-from-queue rc) redit-delete-line-command) (setq cignore 1) (setq rignore 1))))
;;
;;	;; -------- translation ------------
;;
;;	(setq cque (cdr cque)))	;; while rque
;;
;;      ;; ignore
;;      (if (= cignore 1)
;;	  ;; xxx
;;	  (setq cignore 0))
;;      (if (= rignore 1)
;;	  ;; xxx
;;	  (setq rignore 0))
;;
;;      ;; -------- add after que ------------
;;      (enqueue after-RQ (concat (format "%2d%2d%9d" (redit-get-command-from-queue rc) (redit-get-uid-from-queue rc) (+ (redit-get-line-from-queue rc) xrc)) (redit-get-text-from-queue rc)))
;;      ;; -------- add after que ------------
;;
;;      (setq rque (cdr rque))) ;; while cque
;;    )			      ;; let
;;  )			      ;; defun

;; for debug
;;(defun redit-client-print-command-queue ()
;;  "Print command queue."
;;  (interactive)
;;  ;;(message redit-client-command-queue))
;;  (let (queue)
;;    (setq queue redit-client-command-queue)
;;    (while (Queue-front queue)
;;      (message (Queue-front queue))
;;      (setq queue (cdr (Queue-rear queue)))
;;      )))
;;
;;(defun redit-client-print-recive-queue ()
;;  "Print command queue."
;;  (interactive)
;;  (print redit-client-receive-queue))
;;
;;(defun redit-client-print-send-queue ()
;;  "Print command queue."
;;  (interactive)
;;  (print redit-client-send-queue))
;;

(defun is-value-in-range (min value max)
  (if (and (<= min value) (<= value max))
      t nil))