emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/crdt 60bb2ac 07/80: lots of functionalities


From: ELPA Syncer
Subject: [elpa] externals/crdt 60bb2ac 07/80: lots of functionalities
Date: Sat, 28 Aug 2021 10:57:30 -0400 (EDT)

branch: externals/crdt
commit 60bb2ac7033757513a9f35078e553b8a4d6c2e71
Author: Qiantan Hong <qhong@mit.edu>
Commit: Qiantan Hong <qhong@mit.edu>

    lots of functionalities
    
    - multiple buffers
    - multiple sessions
    - buffer-menu, session-menu
    - synchronize overlay
---
 README.org |   26 +-
 crdt.el    | 1185 ++++++++++++++++++++++++++++++++++++++++++++----------------
 2 files changed, 894 insertions(+), 317 deletions(-)

diff --git a/README.org b/README.org
index 67f8936..9ecf55d 100644
--- a/README.org
+++ b/README.org
@@ -1,19 +1,35 @@
 * Introduction
 ~crdt.el~ is a real-time collaborative editing environment for Emacs using 
Conflict-free Replicated Data Types.
+
+Highlights:
+- [[https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type][CRDT]], 
darling child of collaborative editing researches...
+- Share multiple buffer in one session
+- See other users' cursor and region
+- (experimental) synchronize Org mode folding status
 * Usage
 ** Installation
 Just `M-x load-file` `crdt.el`, or `M-x eval-buffer` in `crdt.el`,
 or `(require 'crdt)`. Or whatever package management tool you use.
 ** Share a buffer
-In that buffer, `M-x crdt-serve-buffer`. Then enter port, optional password
-and your display name.
+In that buffer, `M-x crdt-share-buffer`. Then enter session name.
+
+If a new session is to be created, enter port, optional password and your 
display name.
+If there's a existing session with the name, current buffer is added to that 
session.
 ** Connect to a shared buffer
 `M-x crdt-connect`
 ** List active users.
 In a CRDT shared buffer (either server or client), `M-x crdt-list-users`.
 
-In the displayed user list, press `RET` on an entry to goto that user's cursor 
position.
+In the displayed user list, press ~RET~ on an entry to goto that user's cursor 
position.
+** List all sessions, and buffer in current session.
+`M-x crdt-list-sessions` lists all sessions.
+`M-x crdt-list-buffers` lists all buffers in current session. Or you can also 
+press ~RET~ in the session list to see buffers in the selected session.
 ** Stop sharing.
-For server, `M-x crdt-stop-serve-buffer`, or just kill the buffer,
+`M-x crdt-stop-session` stops the current session. You can also press ~k~ in 
the session list.
 
-For client, `M-x crdt-stop-client`, or just kill the buffer.
+`M-x crdt-stop-share-buffer` removes current buffer from its CRDT session 
+(this operation is only allowed at server side). Or press ~k~ in the buffer 
list.
+** Synchronizing Org folding status
+Turn on `crdt-org-sync-overlay-mode`. All peers that have this enabled have 
their
+folding status synchronized. Peers without enabling this minor mode are 
unaffected.
diff --git a/crdt.el b/crdt.el
index 3b874dc..b6a618e 100644
--- a/crdt.el
+++ b/crdt.el
@@ -22,20 +22,23 @@
 ;;; Commentary:
 ;; * Algorithm
 ;;   This packages implements the Logoot split algorithm
-;;   André, Luc, et al.  "Supporting adaptable granularity of changes for 
massive-scale collaborative editing." 9th IEEE International Conference on 
Collaborative Computing: Networking, Applications and Worksharing.  IEEE, 2013.
+;;     André, Luc, et al.
+;;     "Supporting adaptable granularity of changes for massive-scale 
collaborative editing."
+;;     9th IEEE International Conference on Collaborative Computing: 
Networking, Applications and Worksharing.
+;;     IEEE, 2013.
 ;; * Protocol
 ;;   Text-based version
 ;;   (it should be easy to migrate to a binary version.  Using text for better 
debugging for now)
 ;;   Every message takes the form (type . body)
-;;   type can be: insert delete cursor hello challenge sync
+;;   type can be: insert delete cursor hello challenge sync overlay
 ;;   - insert
-;;     body takes the form (crdt-id position-hint content)
+;;     body takes the form (buffer-name crdt-id position-hint content)
 ;;     - position-hint is the buffer position where the operation happens at 
the site
 ;;       which generates the operation.  Then we can play the trick that start 
search
 ;;       near this position at other sites to speedup crdt-id search
 ;;     - content is the string to be inserted
 ;;   - delete
-;;     body takes the form (position-hint (crdt-id . length)*)
+;;     body takes the form (buffer-name position-hint (crdt-id . length)*)
 ;;   - cursor
 ;;     body takes the form
 ;;          (site-id point-position-hint point-crdt-id mark-position-hint 
mark-crdt-id)
@@ -44,28 +47,50 @@
 ;;       - "", which means (point-max)
 ;;   - contact
 ;;     body takes the form
-;;          (site-id name address)
+;;          (site-id name address port)
 ;;     when name is nil, clear the contact for this site-id
+;;   - focus
+;;     body takes the form (site-id buffer-name)
 ;;   - hello
 ;;     This message is sent from client to server, when a client connect to 
the server.
 ;;     body takes the form (client-name &optional response)
 ;;   - challenge
 ;;     body takes the form (salt)
+;;   - login
+;;     It's always sent after server receives a hello message.
+;;     Assigns an ID to the client
+;;     body takes the form (site-id).
 ;;   - sync
 ;;     This message is sent from server to client to get it sync to the state 
on the server.
-;;     It's always sent after server receives a hello message.
 ;;     Might be used for error recovery or other optimization in the future.
 ;;     One optimization I have in mind is let server try to merge all CRDT 
item into a single
 ;;     one and try to synchronize this state to clients at best effort.
-;;     body takes the form (site-id major-mode content . crdt-id-list)
-;;     - site-id is the site ID the server assigned to the client
+;;     body takes the form (buffer-name major-mode content . crdt-id-list)
 ;;     - major-mode is the major mode used at the server site
 ;;     - content is the string in the buffer
 ;;     - crdt-id-list is generated from CRDT--DUMP-IDS
+;;   - desync
+;;     Indicates that the server has stopped sharing a buffer.
+;;     body takes the form (buffer-name)
+;;   - overlay-add
+;;     body takes the form (buffer-name site-id logical-clock species
+;;                          front-advance rear-advance
+;;                          start-position-hint start-crdt-id
+;;                          end-position-hint end-crdt-id)
+;;   - overlay-move
+;;     body takes the form (buffer-name site-id logical-clock
+;;                          start-position-hint start-crdt-id
+;;                          end-position-hint end-crdt-id)
+;;   - overlay-put
+;;     body takes the form (buffer-name site-id logical-clock prop value)
+;;   - overlay-remove
+;;     body takes the form (buffer-name site-id logical-clock)
 
 
 ;;; Code:
 
+
+;;; Customs
 (defgroup crdt nil
   "Collaborative editing using Conflict-free Replicated Data Types."
   :prefix "crdt-"
@@ -82,16 +107,17 @@
 
 (require 'cl-lib)
 
+;;; Pseudo cursor/region utils
 (require 'color)
 (defvar crdt-cursor-region-colors
   (let ((n 10))
     (cl-loop for i below n
-             for hue by (/ 1.0 n)
-             collect (cons
-                      (apply #'color-rgb-to-hex
-                             (color-hsl-to-rgb hue 0.5 0.5))
-                      (apply #'color-rgb-to-hex
-                             (color-hsl-to-rgb hue 0.2 0.5))))))
+          for hue by (/ 1.0 n)
+          collect (cons
+                   (apply #'color-rgb-to-hex
+                          (color-hsl-to-rgb hue 0.5 0.5))
+                   (apply #'color-rgb-to-hex
+                          (color-hsl-to-rgb hue 0.2 0.5))))))
 
 (defun crdt--get-cursor-color (site-id)
   "Get cursor color for SITE-ID."
@@ -119,7 +145,7 @@
   "Move pseudo marked region overlay OV to mark between POS and MARK."
   (move-overlay ov (min pos mark) (max pos mark)))
 
-
+;;; CRDT ID utils
 ;; CRDT IDs are represented by unibyte strings (for efficient comparison)
 ;; Every two bytes represent a big endian encoded integer
 ;; For base IDs, last two bytes are always representing site ID
@@ -172,11 +198,11 @@ and HIGH-OFFSET.  (to save two copying from using 
CRDT--ID-REPLACE-OFFSET)"
   (let* ((l (crdt--get-two-bytes-with-offset low-id low-offset 0 0))
          (h (crdt--get-two-bytes-with-offset high-id high-offset 0 
crdt--max-value))
          (bytes (cl-loop for pos from 2 by 2
-                         while (< (- h l) 2)
-                         append (list (lsh l -8)
-                                      (logand l crdt--low-byte-mask))
-                         do (setq l (crdt--get-two-bytes-with-offset low-id 
low-offset pos 0))
-                         do (setq h (crdt--get-two-bytes-with-offset high-id 
high-offset pos crdt--max-value))))
+                      while (< (- h l) 2)
+                      append (list (lsh l -8)
+                                   (logand l crdt--low-byte-mask))
+                      do (setq l (crdt--get-two-bytes-with-offset low-id 
low-offset pos 0))
+                      do (setq h (crdt--get-two-bytes-with-offset high-id 
high-offset pos crdt--max-value))))
          (m (+ l 1 (random (- h l 1)))))
     (apply #'unibyte-string
            (append bytes (list (lsh m -8)
@@ -206,15 +232,18 @@ Return NIL otherwise."
 Assume the stored literal ID is STARTING-ID."
   (let* ((start-pos (previous-single-property-change (1+ pos) 'crdt-id obj (or 
limit (point-min)))))
     (+ (- pos start-pos) (crdt--id-offset starting-id))))
+
+;;; CRDT ID and text property utils
 (defsubst crdt--get-id (pos &optional obj left-limit right-limit)
   "Get the real CRDT ID at POS."
   (let ((right-limit (or right-limit (point-max)))
         (left-limit (or left-limit (point-min))))
-    (if (< pos right-limit)
-        (let* ((starting-id (crdt--get-starting-id pos obj))
-               (left-offset (crdt--get-id-offset starting-id pos obj 
left-limit)))
-          (crdt--id-replace-offset starting-id left-offset))
-      "")))
+    (cond ((>= pos right-limit) "")
+          ((< pos left-limit) nil)
+          (t
+           (let* ((starting-id (crdt--get-starting-id pos obj))
+                  (left-offset (crdt--get-id-offset starting-id pos obj 
left-limit)))
+             (crdt--id-replace-offset starting-id left-offset))))))
 
 (defsubst crdt--set-id (pos id &optional end-of-block-p obj limit)
   "Set the crdt ID and END-OF-BLOCK-P at POS in OBJ.
@@ -243,11 +272,11 @@ with ID and END-OF-BLOCK-P."
      ,@body))
 (defmacro crdt--split-maybe ()
   '(when (and not-end (eq starting-id (crdt--get-starting-id end end-obj)))
-     ;; need to split id block
-     (crdt--set-id end (crdt--id-replace-offset starting-id (1+ left-offset))
-                   (crdt--end-of-block-p left-pos beg-obj) end-obj end-limit)
-     (rplacd (get-text-property left-pos 'crdt-id beg-obj) nil) ;; clear 
end-of-block flag
-     t))
+    ;; need to split id block
+    (crdt--set-id end (crdt--id-replace-offset starting-id (1+ left-offset))
+     (crdt--end-of-block-p left-pos beg-obj) end-obj end-limit)
+    (rplacd (get-text-property left-pos 'crdt-id beg-obj) nil) ;; clear 
end-of-block flag
+    t))
 
 (defsubst crdt--same-base-p (a b)
   (let* ((a-length (string-bytes a))
@@ -256,40 +285,198 @@ with ID and END-OF-BLOCK-P."
          (let ((base-length (- a-length 2)))
            (eq t (compare-strings a 0 base-length b 0 base-length))))))
 
+;;; Buffer local variables
 (defmacro crdt--defvar-permanent-local (name &optional val docstring)
   `(progn
      (defvar-local ,name ,val ,docstring)
      (put ',name 'permanent-local t)))
-(crdt--defvar-permanent-local crdt--local-id nil "Local site-id.")
-(crdt--defvar-permanent-local crdt--inhibit-update nil "When set, don't call 
CRDT--LOCAL-* on change.
+(crdt--defvar-permanent-local crdt--status-buffer)
+(defsubst crdt--assimilate-status-buffer (buffer)
+  (let ((status-buffer crdt--status-buffer))
+    (with-current-buffer buffer
+      (setq crdt--status-buffer status-buffer))))
+(defmacro crdt--defvar-session (name &optional val docstring)
+  (let ((setter-name (intern (format "%s-setter" name))))
+    `(progn
+       (defvar-local ,name ,val ,docstring)
+       (defun ,name ()
+         (when crdt--status-buffer
+           (with-current-buffer crdt--status-buffer ,name)))
+       (defun ,setter-name (val)
+         (when crdt--status-buffer
+           (with-current-buffer crdt--status-buffer (setq ,name val))))
+       (gv-define-simple-setter ,name ,setter-name))))
+
+(crdt--defvar-session crdt--local-id nil "Local site-id.")
+(crdt--defvar-session crdt--local-clock 0 "Local logical clock.")
+(defvar crdt--inhibit-update nil "When set, don't call CRDT--LOCAL-* on change.
 This is useful for functions that apply remote change to local buffer,
 to avoid recusive calling of CRDT synchronization functions.")
 (crdt--defvar-permanent-local crdt--changed-string nil)
 (crdt--defvar-permanent-local crdt--last-point nil)
 (crdt--defvar-permanent-local crdt--last-mark nil)
-(crdt--defvar-permanent-local crdt--overlay-table nil
+(crdt--defvar-permanent-local crdt--pseudo-cursor-table nil
                               "A hash table that maps SITE-ID to CONSes of the 
form (CURSOR-OVERLAY . REGION-OVERLAY).")
-(crdt--defvar-permanent-local crdt--contact-table nil
-                              "A hash table that maps SITE-ID to LISTs of the 
form (DISPLAY-NAME ADDRESS).")
-(crdt--defvar-permanent-local crdt--local-name nil)
-(crdt--defvar-permanent-local crdt--user-list-buffer nil)
+(cl-defstruct (crdt--contact-metadata
+                (:constructor crdt--make-contact-metadata (display-name 
focused-buffer-name host service)))
+  display-name host service focused-buffer-name)
+(crdt--defvar-session crdt--contact-table nil
+                      "A hash table that maps SITE-ID to 
CRDT--CONTACT-METADATAs.")
+(cl-defstruct (crdt--overlay-metadata
+                (:constructor crdt--make-overlay-metadata
+                              (lamport-timestamp species front-advance 
rear-advance plist))
+                (:copier crdt--copy-overlay-metadata))
+  ""
+  lamport-timestamp species front-advance rear-advance plist)
+(crdt--defvar-permanent-local crdt--overlay-table nil
+                              "A hash table that maps CONSes of the form 
(SITE-ID . LOGICAL-CLOCK) to overlays.")
+(defvar crdt--track-overlay-species nil)
+(crdt--defvar-permanent-local crdt--enabled-overlay-species nil)
+(crdt--defvar-permanent-local crdt--buffer-network-name)
 
-(defvar crdt-user-list-mode-map
+(crdt--defvar-session crdt--local-name nil)
+(crdt--defvar-session crdt--focused-buffer-name nil)
+(crdt--defvar-session crdt--user-menu-buffer nil)
+(crdt--defvar-session crdt--buffer-menu-buffer nil)
+(defvar crdt--session-alist nil)
+(defvar crdt--session-menu-buffer nil)
+
+;;; Session menu
+(defun crdt--session-menu-goto ()
+  (interactive)
+  (with-current-buffer
+      (process-get (tabulated-list-get-id) 'status-buffer)
+    (crdt-list-buffer)))
+(defun crdt--session-menu-kill ()
+  (interactive)
+  (with-current-buffer
+      (process-get (tabulated-list-get-id) 'status-buffer)
+    (crdt-stop-session)))
+(defvar crdt-session-menu-mode-map
   (let ((map (make-sparse-keymap)))
-    (define-key map (kbd "RET") #'crdt-user-list-goto)
+    (define-key map (kbd "RET") #'crdt--session-menu-goto)
+    (define-key map (kbd "k") #'crdt--session-menu-kill)
     map))
-(define-derived-mode crdt-user-list-mode tabulated-list-mode
+(define-derived-mode crdt-session-menu-mode tabulated-list-mode
   "CRDT User List"
-  (setq tabulated-list-format [("Display name" 15 t)
-                               ("Address" 15 t)
-                               ("Port" 7 t)]))
-(defun crdt-user-list-goto ()
+  (setq tabulated-list-format [("Session Name" 15 t)
+                               ("Role" 7 t)
+                               ("My Display Name" 15 t)
+                               ("Buffers" 15 t)
+                               ("Users" 15 t)]))
+(defun crdt-list-sessions (&optional crdt-buffer display-buffer)
+  "Display a list of active CRDT sessions.
+If DISPLAY-BUFFER is provided, display the output there. Otherwise use a 
dedicated
+buffer for displaying active users on CRDT-BUFFER."
+  (interactive)
+  (unless display-buffer
+    (unless (and crdt--session-menu-buffer (buffer-live-p 
crdt--session-menu-buffer))
+      (setf crdt--session-menu-buffer
+            (generate-new-buffer "*CRDT Sessions*")))
+    (setq display-buffer crdt--session-menu-buffer))
+  (crdt-refresh-sessions display-buffer)
+  (switch-to-buffer-other-window display-buffer))
+(defun crdt-refresh-sessions (display-buffer)
+  (with-current-buffer display-buffer
+    (crdt-session-menu-mode)
+    (setq tabulated-list-entries nil)
+    (mapc (lambda (pair)
+            (cl-destructuring-bind (name . s) pair
+              (push
+               (list s (with-current-buffer (process-get s 'status-buffer)
+                         (vector name (if (process-contact s :server) "Server" 
"Client")
+                                 crdt--local-name
+                                 (mapconcat (lambda (v) (format "%s" v))
+                                            (hash-table-keys 
crdt--buffer-table)
+                                            ", ")
+                                 (mapconcat (lambda (v) (format "%s" v))
+                                            (let (users)
+                                              (maphash (lambda (k v)
+                                                         (push 
(crdt--contact-metadata-display-name v) users))
+                                                       crdt--contact-table)
+                                              (cons crdt--local-name users))
+                                            ", "))))
+               tabulated-list-entries)))
+          crdt--session-alist)
+    (tabulated-list-init-header)
+    (tabulated-list-print)))
+(defsubst crdt--refresh-sessions-maybe ()
+  (when (and crdt--session-menu-buffer (buffer-live-p 
crdt--session-menu-buffer))
+    (crdt-refresh-sessions crdt--session-menu-buffer)))
+
+;;; Buffer menu
+(defun crdt--buffer-menu-goto ()
+  (interactive)
+  (switch-to-buffer-other-window (tabulated-list-get-id)))
+(defun crdt--buffer-menu-kill ()
+  (interactive)
+  (with-current-buffer (tabulated-list-get-id)
+    (crdt-stop-share-buffer)))
+(defvar crdt-buffer-menu-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "RET") #'crdt--buffer-menu-goto)
+    (define-key map (kbd "k") #'crdt--buffer-menu-kill)
+    map))
+(define-derived-mode crdt-buffer-menu-mode tabulated-list-mode
+  "CRDT User List"
+  (setq tabulated-list-format [("Buffer" 15 t)
+                               ("Network Name" 15 t)]))
+(defun crdt-list-buffer (&optional crdt-buffer display-buffer)
+  "Display a list of buffers shared in the current CRDT session.
+If DISPLAY-BUFFER is provided, display the output there. Otherwise use a 
dedicated
+buffer for displaying active users on CRDT-BUFFER."
+  (interactive)
+  (with-current-buffer (or crdt-buffer (current-buffer))
+    (unless (or crdt-mode crdt--network-process)
+      (error "Not a CRDT shared buffer."))
+    (unless display-buffer
+      (unless (and (crdt--buffer-menu-buffer) (buffer-live-p 
(crdt--buffer-menu-buffer)))
+        (setf (crdt--buffer-menu-buffer)
+              (generate-new-buffer (concat (buffer-name (current-buffer))
+                                           " buffers")))
+        (crdt--assimilate-status-buffer (crdt--buffer-menu-buffer)))
+      (setq display-buffer (crdt--buffer-menu-buffer)))
+    (with-current-buffer crdt--status-buffer
+      (crdt-refresh-buffers display-buffer))
+    (switch-to-buffer-other-window display-buffer)))
+(defun crdt-refresh-buffers (display-buffer)
+  (with-current-buffer display-buffer
+    (crdt-buffer-menu-mode)
+    (setq tabulated-list-entries nil)
+    (maphash (lambda (k v)
+               (push (list v (vector (buffer-name v) k))
+                     tabulated-list-entries))
+             (crdt--buffer-table))
+    (tabulated-list-init-header)
+    (tabulated-list-print)))
+(defsubst crdt--refresh-buffers-maybe ()
+  (when (and (crdt--buffer-menu-buffer) (buffer-live-p 
(crdt--buffer-menu-buffer)))
+    (crdt-refresh-buffers (crdt--buffer-menu-buffer)))
+  (crdt--refresh-sessions-maybe))
+
+;;; User menu
+(defun crdt--user-menu-goto ()
   (interactive)
-  (let ((site-id (tabulated-list-get-id)))
-    (switch-to-buffer-other-window crdt--user-list-parent)
+  (let* ((site-id (tabulated-list-get-id))
+         (focused-buffer
+          (with-current-buffer crdt--status-buffer
+            (gethash
+             (crdt--contact-metadata-focused-buffer-name
+              (gethash site-id crdt--contact-table))
+             crdt--buffer-table))))
+    (switch-to-buffer-other-window focused-buffer)
     (when site-id
-      (goto-char (overlay-start (car (gethash site-id 
crdt--overlay-table)))))))
-(crdt--defvar-permanent-local crdt--user-list-parent nil "Set to the CRDT 
shared buffer, local in a CRDT-USER-LIST buffer.")
+      (goto-char (overlay-start (car (gethash site-id 
crdt--pseudo-cursor-table)))))))
+(defvar crdt-user-menu-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "RET") #'crdt--user-menu-goto)
+    map))
+(define-derived-mode crdt-user-menu-mode tabulated-list-mode
+  "CRDT User List"
+  (setq tabulated-list-format [("Display Name" 15 t)
+                               ("Focused Buffer" 15 t)
+                               ("Address" 15 t)
+                               ("Port" 7 t)]))
 (defun crdt-list-users (&optional crdt-buffer display-buffer)
   "Display a list of active users working on a CRDT-shared buffer CRDT-BUFFER.
 If DISPLAY-BUFFER is provided, display the output there. Otherwise use a 
dedicated
@@ -299,25 +486,30 @@ buffer for displaying active users on CRDT-BUFFER."
     (unless crdt-mode
       (error "Not a CRDT shared buffer."))
     (unless display-buffer
-      (unless (and crdt--user-list-buffer (buffer-live-p 
crdt--user-list-buffer))
-        (let ((crdt-buffer (current-buffer)))
-          (setq crdt--user-list-buffer
-                (generate-new-buffer (concat (buffer-name (current-buffer))
-                                             " users")))
-          (with-current-buffer crdt--user-list-buffer
-            (setq crdt--user-list-parent crdt-buffer))))
-      (setq display-buffer crdt--user-list-buffer))
-    (crdt-refresh-users display-buffer)
+      (unless (and (crdt--user-menu-buffer) (buffer-live-p 
(crdt--user-menu-buffer)))
+        (setf (crdt--user-menu-buffer)
+              (generate-new-buffer (concat (buffer-name (current-buffer))
+                                           " users")))
+        (crdt--assimilate-status-buffer (crdt--user-menu-buffer)))
+      (setq display-buffer (crdt--user-menu-buffer)))
+    (with-current-buffer crdt--status-buffer
+      (crdt-refresh-users display-buffer))
     (switch-to-buffer-other-window display-buffer)))
 (defun crdt-refresh-users (display-buffer)
-  (let ((table crdt--contact-table)
-        (local-name crdt--local-name))
+  "Must be called with CURRENT-BUFFER set to a CRDT status buffer."
+  (let (table local-name local-id)
+    (setq table crdt--contact-table)
+    (setq local-name crdt--local-name)
+    (setq local-id crdt--local-id)
     (with-current-buffer display-buffer
-      (crdt-user-list-mode)
+      (crdt-user-menu-mode)
       (setq tabulated-list-entries nil)
-      (push (list crdt--local-id (vector local-name "*myself*" "--")) 
tabulated-list-entries)
+      (push (list local-id (vector local-name (or (crdt--focused-buffer-name) 
"--") "*myself*" "--")) tabulated-list-entries)
       (maphash (lambda (k v)
-                 (push (list k (cl-destructuring-bind (name contact) v
+                 (push (list k (let ((name 
(crdt--contact-metadata-display-name v))
+                                     (host (crdt--contact-metadata-host v))
+                                     (service (crdt--contact-metadata-service 
v))
+                                     (focused-buffer-name (or 
(crdt--contact-metadata-focused-buffer-name v) "--")))
                                  (let ((colored-name (concat name " ")))
                                    (put-text-property 0 (1- (length 
colored-name))
                                                       'face `(:background 
,(crdt--get-region-color k))
@@ -325,15 +517,19 @@ buffer for displaying active users on CRDT-BUFFER."
                                    (put-text-property (1- (length 
colored-name)) (length colored-name)
                                                       'face `(:background 
,(crdt--get-cursor-color k))
                                                       colored-name)
-                                   (vector colored-name (car contact) (format 
"%s" (cadr contact))))))
+                                   (vector colored-name focused-buffer-name 
host (format "%s" service)))))
                        tabulated-list-entries))
                table)
       (tabulated-list-init-header)
       (tabulated-list-print))))
 (defsubst crdt--refresh-users-maybe ()
-  (when (and crdt--user-list-buffer (buffer-live-p crdt--user-list-buffer))
-    (crdt-refresh-users crdt--user-list-buffer)))
+  (when (and (crdt--user-menu-buffer) (buffer-live-p (crdt--user-menu-buffer)))
+    (crdt-refresh-users (crdt--user-menu-buffer)))
+  (crdt--refresh-sessions-maybe))
 
+;;; CRDT insert/delete
+(defsubst crdt--base64-encode-maybe (str)
+  (when str (base64-encode-string str)))
 (defun crdt--local-insert (beg end)
   "To be called after a local insert happened in current buffer from BEG to 
END.
 Returns a list of (insert type) messages to be sent."
@@ -342,7 +538,7 @@ Returns a list of (insert type) messages to be sent."
      (beg end)
      (unless (crdt--split-maybe)
        (when (and not-begin
-                  (eq (crdt--id-site starting-id) crdt--local-id)
+                  (eq (crdt--id-site starting-id) (crdt--local-id))
                   (crdt--end-of-block-p left-pos))
          ;; merge crdt id block
          (let* ((max-offset crdt--max-value)
@@ -351,16 +547,18 @@ Returns a list of (insert type) messages to be sent."
              (put-text-property beg merge-end 'crdt-id starting-id-pair)
              (let ((virtual-id (substring starting-id)))
                (crdt--set-id-offset virtual-id (1+ left-offset))
-               (push `(insert ,(base64-encode-string virtual-id) ,beg
+               (push `(insert ,crdt--buffer-network-name
+                              ,(base64-encode-string virtual-id) ,beg
                               ,(buffer-substring-no-properties beg merge-end))
                      resulting-commands))
              (cl-incf left-offset (- merge-end beg))
              (setq beg merge-end)))))
      (while (< beg end)
        (let ((block-end (min end (+ crdt--max-value beg))))
-         (let ((new-id (crdt--generate-id starting-id left-offset ending-id 
right-offset crdt--local-id)))
+         (let ((new-id (crdt--generate-id starting-id left-offset ending-id 
right-offset (crdt--local-id))))
            (put-text-property beg block-end 'crdt-id (cons new-id t))
-           (push `(insert ,(base64-encode-string new-id) ,beg
+           (push `(insert ,crdt--buffer-network-name
+                          ,(base64-encode-string new-id) ,beg
                           ,(buffer-substring-no-properties beg block-end))
                  resulting-commands)
            (setq beg block-end)
@@ -369,8 +567,9 @@ Returns a list of (insert type) messages to be sent."
     ;; (crdt--verify-buffer)
     (nreverse resulting-commands)))
 
-(defun crdt--find-id (id pos)
-  "Find the first position *after* ID.  Start the search from POS."
+(defun crdt--find-id (id pos &optional before)
+  "Find the first position *after* ID if BEFORE is NIL, or *before* ID 
otherwise.
+Start the search from POS."
   (let* ((left-pos (previous-single-property-change (if (< pos (point-max)) 
(1+ pos) pos)
                                                     'crdt-id nil (point-min)))
          (left-id (crdt--get-starting-id left-pos))
@@ -395,12 +594,12 @@ Returns a list of (insert type) messages to be sent."
               (t
                ;; will unibyte to multibyte conversion cause any problem?
                (cl-return
-                (if (eq t (compare-strings left-id 0 (- (string-bytes left-id) 
2)
-                                           id 0 (- (string-bytes left-id) 2)))
-                    (min right-pos (+ left-pos 1
-                                      (- (crdt--get-two-bytes id (- 
(string-bytes left-id) 2))
-                                         (crdt--id-offset left-id))))
-                  right-pos))))))))
+                 (if (eq t (compare-strings left-id 0 (- (string-bytes 
left-id) 2)
+                                            id 0 (- (string-bytes left-id) 2)))
+                     (min right-pos (+ left-pos (if before 0 1)
+                                       (- (crdt--get-two-bytes id (- 
(string-bytes left-id) 2))
+                                          (crdt--id-offset left-id))))
+                   right-pos))))))))
 (defun crdt--remote-insert (id position-hint content)
   (let ((crdt--inhibit-update t))
     (let* ((beg (crdt--find-id id position-hint)) end)
@@ -429,17 +628,19 @@ Returns a list of (insert type) messages to be sent."
        (let* ((not-end (< outer-end (point-max)))
               (ending-id (when not-end (crdt--get-starting-id outer-end))))
          (when (and not-end (eq starting-id (crdt--get-starting-id outer-end)))
-           (crdt--set-id outer-end (crdt--id-replace-offset starting-id (+ 1 
left-offset (length crdt--changed-string))))))))
+           (crdt--set-id outer-end
+                         (crdt--id-replace-offset starting-id (+ 1 left-offset 
(length crdt--changed-string))))))))
     (crdt--with-insertion-information
      ((length crdt--changed-string) outer-end crdt--changed-string nil 0 nil)
      (crdt--split-maybe)))
   ;; (crdt--verify-buffer)
-  `(delete ,beg ,@ (crdt--dump-ids 0 (length crdt--changed-string) 
crdt--changed-string t)))
+  `(delete ,crdt--buffer-network-name
+           ,beg ,@ (crdt--dump-ids 0 (length crdt--changed-string) 
crdt--changed-string t)))
 (defun crdt--remote-delete (position-hint id-pairs)
   (dolist (id-pair id-pairs)
     (cl-destructuring-bind (length . id) id-pair
       (while (> length 0)
-        (goto-char (1- (crdt--find-id id position-hint)))
+        (goto-char (crdt--find-id id position-hint t))
         (let* ((end-of-block (next-single-property-change (point) 'crdt-id nil 
(point-max)))
                (block-length (- end-of-block (point))))
           (cl-case (cl-signum (- length block-length))
@@ -467,7 +668,7 @@ Returns a list of (insert type) messages to be sent."
           (when (eq (overlay-get ov 'category) 'crdt-pseudo-cursor)
             (crdt--move-cursor ov beg)))
         (overlays-in beg (min (point-max) (1+ beg))))
-  (when crdt--local-id ; CRDT--LOCAL-ID is NIL when a client haven't received 
the first sync message
+  (when (crdt--local-id) ; CRDT--LOCAL-ID is NIL when a client haven't 
received the first sync message
     (unless crdt--inhibit-update
       (let ((crdt--inhibit-update t))
         ;; we're only interested in text change
@@ -481,18 +682,20 @@ Returns a list of (insert type) messages to be sent."
             (with-silent-modifications
               (unless (= length 0)
                 (crdt--broadcast-maybe
-                 (format "%S" (crdt--local-delete beg end))))
+                 (crdt--format-message (crdt--local-delete beg end))))
               (unless (= beg end)
                 (dolist (message (crdt--local-insert beg end))
                   (crdt--broadcast-maybe
-                   (format "%S" message)))))))))))
+                   (crdt--format-message message)))))))))))
+
+;;; CRDT point/mark synchronization
 (defsubst crdt--id-to-pos (id hint)
   (if (> (string-bytes id) 0)
-      (1- (crdt--find-id id hint))
+      (crdt--find-id id hint t)
     (point-max)))
 (defun crdt--remote-cursor (site-id point-position-hint point-crdt-id 
mark-position-hint mark-crdt-id)
   (when site-id
-    (let ((ov-pair (gethash site-id crdt--overlay-table)))
+    (let ((ov-pair (gethash site-id crdt--pseudo-cursor-table)))
       (if point-crdt-id
           (let* ((point (crdt--id-to-pos point-crdt-id point-position-hint))
                  (mark (if mark-crdt-id
@@ -505,11 +708,11 @@ Returns a list of (insert type) messages to be sent."
                 (overlay-put new-cursor 'category 'crdt-pseudo-cursor)
                 (overlay-put new-region 'face `(:background 
,(crdt--get-region-color site-id) :extend t))
                 (setq ov-pair (puthash site-id (cons new-cursor new-region)
-                                       crdt--overlay-table))))
+                                       crdt--pseudo-cursor-table))))
             (crdt--move-cursor (car ov-pair) point)
             (crdt--move-region (cdr ov-pair) point mark))
         (when ov-pair
-          (remhash site-id crdt--overlay-table)
+          (remhash site-id crdt--pseudo-cursor-table)
           (delete-overlay (car ov-pair))
           (delete-overlay (cdr ov-pair)))))))
 
@@ -528,14 +731,18 @@ Returns a list of (insert type) messages to be sent."
       (setq crdt--last-mark mark)
       (let ((point-id-base64 (base64-encode-string (crdt--get-id point)))
             (mark-id-base64 (when mark (base64-encode-string (crdt--get-id 
mark)))))
-        `(cursor ,crdt--local-id
+        `(cursor ,crdt--buffer-network-name ,(crdt--local-id)
                  ,point ,point-id-base64 ,mark ,mark-id-base64)))))
 (defun crdt--post-command ()
+  (unless (eq crdt--buffer-network-name (crdt--focused-buffer-name))
+    (crdt--broadcast-maybe
+     (crdt--format-message `(focus ,(crdt--local-id) 
,crdt--buffer-network-name)))
+    (setf (crdt--focused-buffer-name) crdt--buffer-network-name))
   (let ((cursor-message (crdt--local-cursor)))
     (when cursor-message
-      (crdt--broadcast-maybe (format "%S" cursor-message)))))
-
+      (crdt--broadcast-maybe (crdt--format-message cursor-message)))))
 
+;;; CRDT ID (de)serialization
 (defun crdt--dump-ids (beg end object &optional omit-end-of-block-p)
   "Serialize all CRDT ids in OBJECT from BEG to END into a list.
 The list contains CONSes of the form (LENGTH CRDT-ID-BASE64 . END-OF-BLOCK-P),
@@ -578,18 +785,22 @@ Verify that CRDT IDs in a document follows ascending 
order."
             (setq pos next-pos)
             (setq id next-id))))))
 
-(crdt--defvar-permanent-local crdt--network-process nil)
-(crdt--defvar-permanent-local crdt--network-clients nil)
-(crdt--defvar-permanent-local crdt--next-client-id nil)
+;;; Network protocol
+(crdt--defvar-session crdt--network-process nil)
+(crdt--defvar-session crdt--network-clients nil)
+(crdt--defvar-session crdt--next-client-id)
+(crdt--defvar-session crdt--buffer-table)
+(defun crdt--format-message (args)
+  (format "%S" args))
 (cl-defun crdt--broadcast-maybe (message-string &optional (without t))
   "Broadcast or send MESSAGE-STRING.
 If CRDT--NETWORK-PROCESS is a server process, broadcast MESSAGE-STRING
 to clients except the one of which CLIENT-ID property is EQ to WITHOUT.
-If CRDT--NETWORK-PROCESS is a server process, send MESSAGE-STRING
-to server unless WITHOUT is NIL."
-  ;; (message "Send %s" message-string)
-  (if (process-contact crdt--network-process :server)
-      (dolist (client crdt--network-clients)
+If CRDT--NETWORK-PROCESS is a client process, send MESSAGE-STRING
+to server when WITHOUT is T."
+  (message "Send %s" message-string)
+  (if (process-contact (crdt--network-process) :server)
+      (dolist (client (crdt--network-clients))
         (when (and (eq (process-status client) 'open)
                    (not (eq (process-get client 'client-id) without)))
           (process-send-string client message-string)
@@ -597,268 +808,597 @@ to server unless WITHOUT is NIL."
           ;; ^ quick dirty way to simulate network latency, for debugging
           ))
     (when without
-      (process-send-string crdt--network-process message-string)
+      (process-send-string (crdt--network-process) message-string)
       ;; (run-at-time 1 nil #'process-send-string crdt--network-process 
message-string)
       )))
+(defsubst crdt--overlay-add-message (id clock species front-advance 
rear-advance beg end)
+  `(overlay-add ,crdt--buffer-network-name ,id ,clock
+                ,species ,front-advance ,rear-advance
+                ,beg ,(if front-advance
+                          (base64-encode-string (crdt--get-id beg))
+                        (crdt--base64-encode-maybe (crdt--get-id (1- beg))))
+                ,end ,(if rear-advance
+                          (base64-encode-string (crdt--get-id end))
+                        (crdt--base64-encode-maybe (crdt--get-id (1- end))))))
 (defun crdt--generate-challenge ()
   (apply #'unibyte-string (cl-loop for i below 32 collect (random 256))))
 (defun crdt--greet-client (process)
-  (cl-pushnew process crdt--network-clients)
-  (let ((client-id (process-get process 'client-id)))
-    (unless client-id
-      (unless (< crdt--next-client-id crdt--max-value)
-        (error "Used up client IDs. Need to implement allocation algorithm."))
-      (process-put process 'client-id crdt--next-client-id)
-      (setq client-id crdt--next-client-id)
-      (cl-incf crdt--next-client-id))
-    (process-send-string process (format "%S" `(sync
-                                                ,client-id
-                                                ,major-mode
-                                                
,(buffer-substring-no-properties (point-min) (point-max))
-                                                ,@ (crdt--dump-ids (point-min) 
(point-max) nil))))
-    (maphash (lambda (site-id ov-pair)
-               (cl-destructuring-bind (cursor-ov . region-ov) ov-pair
-                 (let* ((point (overlay-start cursor-ov))
-                        (region-beg (overlay-start region-ov))
-                        (region-end (overlay-end region-ov))
-                        (mark (if (eq point region-beg)
-                                  (unless (eq point region-end) region-end)
-                                region-beg))
-                        (point-id-base64 (base64-encode-string (crdt--get-id 
point)))
-                        (mark-id-base64 (when mark (base64-encode-string 
(crdt--get-id mark)))))
-                   (process-send-string process
-                                        (format "%S"
-                                                `(cursor ,site-id
-                                                         ,point 
,point-id-base64 ,mark ,mark-id-base64))))))
-             crdt--overlay-table)
-    (process-send-string process (format "%S" (crdt--local-cursor nil)))
-    (maphash (lambda (k v)
-               (process-send-string process (format "%S" `(contact ,k ,@v))))
-             crdt--contact-table)
-    (process-send-string process
-                         (format "%S" `(contact ,crdt--local-id
-                                                ,crdt--local-name nil)))
-    (let ((contact-message `(contact ,client-id ,(process-get process 
'client-name)
-                                     ,(process-contact process))))
-      (crdt--broadcast-maybe (format "%S" contact-message) client-id)
-      (crdt-process-message contact-message nil))))
+  (with-current-buffer (process-get process 'status-buffer)
+    (cl-pushnew process crdt--network-clients)
+    (let ((client-id (process-get process 'client-id)))
+      (unless client-id
+        (unless (< crdt--next-client-id crdt--max-value)
+          (error "Used up client IDs. Need to implement allocation 
algorithm."))
+        (process-put process 'client-id crdt--next-client-id)
+        (setq client-id crdt--next-client-id)
+        (process-send-string process (crdt--format-message `(login 
,client-id)))
+        (cl-incf crdt--next-client-id))
+      (maphash (lambda (k buffer)
+                 (with-current-buffer buffer
+                   (process-send-string process (crdt--format-message `(sync
+                                                                        
,crdt--buffer-network-name
+                                                                        
,major-mode
+                                                                        
,(buffer-substring-no-properties (point-min) (point-max))
+                                                                        ,@ 
(crdt--dump-ids (point-min) (point-max) nil))))
+                   ;; synchronize cursor
+                   (maphash (lambda (site-id ov-pair)
+                              (cl-destructuring-bind (cursor-ov . region-ov) 
ov-pair
+                                (let* ((point (overlay-start cursor-ov))
+                                       (region-beg (overlay-start region-ov))
+                                       (region-end (overlay-end region-ov))
+                                       (mark (if (eq point region-beg)
+                                                 (unless (eq point region-end) 
region-end)
+                                               region-beg))
+                                       (point-id-base64 (base64-encode-string 
(crdt--get-id point)))
+                                       (mark-id-base64 (when mark 
(base64-encode-string (crdt--get-id mark)))))
+                                  (process-send-string process
+                                                       (crdt--format-message
+                                                        `(cursor 
,crdt--buffer-network-name ,site-id
+                                                                 ,point 
,point-id-base64 ,mark ,mark-id-base64))))))
+                            crdt--pseudo-cursor-table)
+                   (process-send-string process (crdt--format-message 
(crdt--local-cursor nil)))))
+               crdt--buffer-table)
+      ;; synchronize contact
+      (maphash (lambda (k v)
+                 (process-send-string
+                  process (crdt--format-message `(contact ,k 
,(crdt--contact-metadata-display-name v)
+                                                          
,(crdt--contact-metadata-host v)
+                                                          
,(crdt--contact-metadata-service v))))
+                 (process-send-string
+                  process (crdt--format-message `(focus ,k 
,(crdt--contact-metadata-focused-buffer-name v)))))
+               crdt--contact-table)
+      (process-send-string process
+                           (crdt--format-message `(contact ,(crdt--local-id)
+                                                           
,(crdt--local-name))))
+      (process-send-string process
+                           (crdt--format-message `(focus ,(crdt--local-id)
+                                                         
,(crdt--focused-buffer-name))))
+      (let ((contact-message `(contact ,client-id ,(process-get process 
'client-name)
+                                       ,(process-contact process :host)
+                                       ,(process-contact process :service))))
+        (crdt-process-message contact-message process))
+      ;; synchronize tracked overlay
+      (maphash (lambda (k buffer)
+                 (with-current-buffer buffer
+                   (maphash (lambda (k ov)
+                              (let ((meta (overlay-get ov 'crdt-meta)))
+                                (process-send-string
+                                 process
+                                 (crdt--format-message 
(crdt--overlay-add-message
+                                                        (car k) (cdr k)
+                                                        
(crdt--overlay-metadata-species meta)
+                                                        
(crdt--overlay-metadata-front-advance meta)
+                                                        
(crdt--overlay-metadata-rear-advance meta)
+                                                        (overlay-start ov)
+                                                        (overlay-end ov))))
+                                (cl-loop for (prop value) on 
(crdt--overlay-metadata-plist meta) by #'cddr
+                                      do (process-send-string
+                                          process
+                                          (crdt--format-message `(overlay-put 
,(car k) ,(cdr k) ,prop ,value))))))
+                            crdt--overlay-table)))
+               crdt--buffer-table))))
+
+(defmacro crdt--with-buffer-name (name &rest body)
+  "Find CRDT shared buffer associated with NAME and evaluate BODY in it.
+Must be called when CURRENT-BUFFER is a CRDT status buffer."
+  `(let (crdt-buffer)
+     (setq crdt-buffer (gethash ,name crdt--buffer-table))
+     (if crdt-buffer
+         (with-current-buffer crdt-buffer
+           (save-excursion
+             (widen)
+             ,@body))
+       (unless (process-contact crdt--network-process :server)
+         (setq crdt-buffer (generate-new-buffer (format "crdt - %s" ,name)))
+         (puthash ,name crdt-buffer crdt--buffer-table)
+         (with-current-buffer crdt-buffer
+           (setq crdt--buffer-network-name ,name)
+           (setq crdt--status-buffer (process-get process 'status-buffer))
+           (crdt-mode)
+           (save-excursion
+             (widen)
+             ,@body))))))
 
 (cl-defgeneric crdt-process-message (message process))
+(cl-defmethod crdt-process-message (message process)
+  (message "Unrecognized message %S from %s:%s."
+           message (process-contact process :host) (process-contact process 
:service)))
 (cl-defmethod crdt-process-message ((message (head insert)) process)
-  (cl-destructuring-bind (type crdt-id position-hint content) message
-    (crdt--remote-insert (base64-decode-string crdt-id) position-hint content))
-  (crdt--broadcast-maybe (format "%S" message) (process-get process 
'client-id)))
+  (cl-destructuring-bind (buffer-name crdt-id position-hint content) (cdr 
message)
+    (crdt--with-buffer-name
+     buffer-name
+     (crdt--remote-insert (base64-decode-string crdt-id) position-hint 
content)))
+  (crdt--broadcast-maybe (crdt--format-message message) (process-get process 
'client-id)))
 (cl-defmethod crdt-process-message ((message (head delete)) process)
-  (cl-destructuring-bind (type position-hint . id-base64-pairs) message
+  (crdt--broadcast-maybe (crdt--format-message message) (process-get process 
'client-id))
+  (cl-destructuring-bind (buffer-name position-hint . id-base64-pairs) (cdr 
message)
     (mapc (lambda (p) (rplacd p (base64-decode-string (cdr p)))) 
id-base64-pairs)
-    (crdt--remote-delete position-hint id-base64-pairs))
-  (crdt--broadcast-maybe (format "%S" message) (process-get process 
'client-id)))
+    (crdt--with-buffer-name
+     buffer-name
+     (crdt--remote-delete position-hint id-base64-pairs))))
 (cl-defmethod crdt-process-message ((message (head cursor)) process)
-  (cl-destructuring-bind (type site-id point-position-hint point-crdt-id 
mark-position-hint mark-crdt-id) message
-    (crdt--remote-cursor site-id point-position-hint
-                         (and point-crdt-id (base64-decode-string 
point-crdt-id))
-                         mark-position-hint
-                         (and mark-crdt-id (base64-decode-string 
mark-crdt-id))))
-  (crdt--broadcast-maybe (format "%S" message) (process-get process 
'client-id)))
+  (cl-destructuring-bind (buffer-name site-id point-position-hint point-crdt-id
+                                      mark-position-hint mark-crdt-id)
+      (cdr message)
+    (crdt--with-buffer-name
+     buffer-name
+     (crdt--remote-cursor site-id point-position-hint
+                          (and point-crdt-id (base64-decode-string 
point-crdt-id))
+                          mark-position-hint
+                          (and mark-crdt-id (base64-decode-string 
mark-crdt-id)))))
+  (crdt--broadcast-maybe (crdt--format-message message) (process-get process 
'client-id)))
 (cl-defmethod crdt-process-message ((message (head sync)) process)
-  (unless (crdt--server-p) ; server shouldn't receive this
-    (erase-buffer)
-    (cl-destructuring-bind (id mode content . ids) (cdr message)
-      (if (fboundp mode)
-          (unless (eq major-mode mode)
-            (funcall mode) ; trust your server...
-            (crdt-mode))
-        (message "Server uses %s, but not available locally." mode))
-      (insert content)
-      (setq crdt--local-id id)
-      (crdt--load-ids ids)
-      (puthash 0 (list nil (process-contact process)) crdt--contact-table))))
+  (unless (crdt--server-p)             ; server shouldn't receive this
+    (cl-destructuring-bind (buffer-name mode content . ids) (cdr message)
+      (crdt--with-buffer-name
+       buffer-name
+       (erase-buffer)
+       (if (fboundp mode)
+           (unless (eq major-mode mode)
+             (funcall mode)             ; trust your server...
+             (crdt-mode))
+         (message "Server uses %s, but not available locally." mode))
+       (insert content)
+       (crdt--load-ids ids)))
+    (crdt--refresh-buffers-maybe)))
+(cl-defmethod crdt-process-message ((message (head desync)) process)
+  (cl-destructuring-bind (buffer-name) (cdr message)
+    (let ((buffer (gethash buffer-name crdt--buffer-table)))
+      (when buffer
+        (with-current-buffer buffer
+          (crdt-mode 0)
+          (setq crdt--status-buffer nil))
+        (remhash buffer-name crdt--buffer-table)
+        (message "Server stopped sharing %s." buffer-name))))
+  (crdt--broadcast-maybe (crdt--format-message message)
+                         (when process (process-get process 'client-id)))
+  (crdt--refresh-buffers-maybe))
+(cl-defmethod crdt-process-message ((message (head login)) process)
+  (cl-destructuring-bind (id) (cdr message)
+    (puthash 0 (crdt--make-contact-metadata nil nil
+                                            (process-contact process :host)
+                                            (process-contact process :service))
+             crdt--contact-table)
+    (setq crdt--local-id id)
+    (crdt--refresh-sessions-maybe)))
 (cl-defmethod crdt-process-message ((message (head challenge)) process)
-  (unless (crdt--server-p) ; server shouldn't receive this
+  (unless (crdt--server-p)             ; server shouldn't receive this
     (message nil)
     (let ((password (read-passwd
                      (format "Password for %s:%s: "
-                             (process-contact crdt--network-process :host)
-                             (process-contact crdt--network-process 
:service)))))
-      (crdt--broadcast-maybe (format "%S"
-                                     `(hello nil ,(gnutls-hash-mac 'SHA1 
password (cadr message))))))))
+                             (process-contact (crdt--network-process) :host)
+                             (process-contact (crdt--network-process) 
:service)))))
+      (crdt--broadcast-maybe (crdt--format-message
+                              `(hello nil ,(gnutls-hash-mac 'SHA1 password 
(cadr message))))))))
 (cl-defmethod crdt-process-message ((message (head contact)) process)
-  (cl-destructuring-bind (site-id display-name address) (cdr message)
+  (cl-destructuring-bind
+        (site-id display-name &optional host service) (cdr message)
     (if display-name
-        (puthash site-id (list display-name
-                               (or address (cadr (gethash site-id 
crdt--contact-table))))
-                 crdt--contact-table)
+        (if host
+            (puthash site-id (crdt--make-contact-metadata
+                              display-name nil host service)
+                     crdt--contact-table)
+          (let ((existing-item (gethash site-id crdt--contact-table)))
+            (setf (crdt--contact-metadata-display-name existing-item) 
display-name)))
       (remhash site-id crdt--contact-table))
-    (crdt--refresh-users-maybe)))
+    (crdt--refresh-users-maybe))
+  (crdt--broadcast-maybe (crdt--format-message message) (process-get process 
'client-id)))
+(cl-defmethod crdt-process-message ((message (head focus)) process)
+  (cl-destructuring-bind
+        (site-id buffer-name) (cdr message)
+    (let ((existing-item (gethash site-id crdt--contact-table)))
+      (setf (crdt--contact-metadata-focused-buffer-name existing-item) 
buffer-name))
+    (when (and (= site-id 0) (not crdt--focused-buffer-name))
+      (setq crdt--focused-buffer-name buffer-name)
+      (switch-to-buffer (gethash buffer-name crdt--buffer-table)))
+    (crdt--refresh-users-maybe))
+  (crdt--broadcast-maybe (crdt--format-message message) (process-get process 
'client-id)))
 
 (defsubst crdt--server-p ()
-  (process-contact crdt--network-process :server))
+  (process-contact (crdt--network-process) :server))
 
 (defun crdt--network-filter (process string)
-  (unless (process-buffer process)
+  (unless (and (process-buffer process)
+               (buffer-live-p (process-buffer process)))
     (set-process-buffer process (generate-new-buffer "*crdt-server*"))
     (set-marker (process-mark process) 1))
-  (when (buffer-live-p (process-buffer process))
-    (with-current-buffer (process-buffer process)
-      (save-excursion
-        (goto-char (process-mark process))
-        (insert string)
-        (set-marker (process-mark process) (point))
-        (goto-char (point-min))
-        (let (message)
-          (while (setq message (ignore-errors (read (current-buffer))))
-            ;; (print message)
-            (with-current-buffer (process-get process 'crdt-buffer)
-              (condition-case err
-                  (save-excursion
-                    (widen)
-                    (if (or (not (crdt--server-p)) (process-get process 
'authenticated))
-                        (let ((crdt--inhibit-update t))
-                          (crdt-process-message message process))
-                      (cl-block nil
-                        (when (eq (car message) 'hello)
-                          (cl-destructuring-bind (name &optional response) 
(cdr message)
-                            (when (or (not (process-get process 'password)) ; 
server password is empty
-                                      (and response (string-equal response 
(process-get process 'challenge))))
-                              (process-put process 'authenticated t)
-                              (process-put process 'client-name name)
-                              (crdt--greet-client process)
-                              (cl-return))))
-                        (let ((challenge (crdt--generate-challenge)))
-                          (process-put process 'challenge
-                                       (gnutls-hash-mac 'SHA1 (substring 
(process-get process 'password)) challenge))
-                          (process-send-string process (format "%S" 
`(challenge ,challenge)))))))
+  (with-current-buffer (process-buffer process)
+    (unless crdt--status-buffer
+      (setq crdt--status-buffer (process-get process 'status-buffer)))
+    (save-excursion
+      (goto-char (process-mark process))
+      (insert string)
+      (set-marker (process-mark process) (point))
+      (goto-char (point-min))
+      (let (message)
+        (while (setq message (ignore-errors (read (current-buffer))))
+          (print message)
+          (cl-macrolet ((body ()
+                          '(if (or (not (crdt--server-p)) (process-get process 
'authenticated))
+                            (let ((crdt--inhibit-update t))
+                              (with-current-buffer crdt--status-buffer
+                                (crdt-process-message message process)))
+                            (cl-block nil
+                              (when (eq (car message) 'hello)
+                                (cl-destructuring-bind (name &optional 
response) (cdr message)
+                                  (when (or (not (process-get process 
'password)) ; server password is empty
+                                            (and response (string-equal 
response (process-get process 'challenge))))
+                                    (process-put process 'authenticated t)
+                                    (process-put process 'client-name name)
+                                    (crdt--greet-client process)
+                                    (cl-return))))
+                              (let ((challenge (crdt--generate-challenge)))
+                                (process-put process 'challenge
+                                             (gnutls-hash-mac 'SHA1 (substring 
(process-get process 'password)) challenge))
+                                (process-send-string process 
(crdt--format-message `(challenge ,challenge))))))))
+            (if debug-on-error (body)
+              (condition-case err (body)
                 (error (message "%s error when processing message from %s:%s, 
disconnecting." err
                                 (process-contact process :host) 
(process-contact process :service))
                        (if (crdt--server-p)
                            (delete-process process)
-                         (crdt-stop-client)))))
-            (delete-region (point-min) (point))
-            (goto-char (point-min))))))))
+                         (crdt-stop-client))))))
+          (delete-region (point-min) (point))
+          (goto-char (point-min)))))))
 (defun crdt--server-process-sentinel (client message)
-  (with-current-buffer (process-get client 'crdt-buffer)
-    (unless (eq (process-status client) 'open)
+  (with-current-buffer (process-get client 'status-buffer)
+    (unless (or (process-contact client :server) ; it's actually server itself
+                (eq (process-status client) 'open))
       ;; client disconnected
-      (setq crdt--network-clients (delete client crdt--network-clients))
+      (setq crdt--network-clients (delq client crdt--network-clients))
+      (when (process-buffer client) (kill-buffer (process-buffer client)))
       ;; generate a clear cursor message and a clear contact message
       (let* ((client-id (process-get client 'client-id))
-             (clear-cursor-message `(cursor ,client-id 1 nil 1 nil))
-             (clear-contact-message `(contact ,client-id nil nil)))
-        (crdt-process-message clear-cursor-message client)
+             (clear-contact-message `(contact ,client-id nil)))
         (crdt-process-message clear-contact-message client)
+        (maphash
+         (lambda (k v)
+           (crdt-process-message
+            `(cursor ,k ,client-id 1 nil 1 nil)
+            client))
+         crdt--buffer-table)
         (crdt--refresh-users-maybe)))))
 (defun crdt--client-process-sentinel (process message)
-  (with-current-buffer (process-get process 'crdt-buffer)
+  (with-current-buffer (process-get process 'status-buffer)
     (unless (eq (process-status process) 'open)
-      (crdt-stop-client))))
+      (crdt-stop-session))))
+
+;;; UI commands
 (defun crdt--read-name ()
   (if crdt-ask-for-name
       (let ((input (read-from-minibuffer (format "Display name (default %S): " 
crdt-default-name))))
         (if (> (length input) 0) input crdt-default-name))
     crdt-default-name))
-(defun crdt-serve-buffer (port &optional password name)
-  "Share the current buffer on PORT."
-  (interactive "nPort: ")
-  (crdt-mode)
-  (setq crdt--local-id 0)
-  (setq crdt--network-clients nil)
-  (setq crdt--local-clock 0)
-  (setq crdt--next-client-id 1)
-  (save-excursion
-    (widen)
-    (let ((crdt--inhibit-update t))
-      (with-silent-modifications
-        (crdt--local-insert (point-min) (point-max)))))
-  (add-hook 'kill-buffer-hook #'crdt-stop-serve-buffer nil t)
-  (unless password
-    (setq password
-          (when crdt-ask-for-password
-            (read-from-minibuffer "Set password (empty for no authentication): 
"))))
-  (unless name
-    (setq name (crdt--read-name)))
-  (setq crdt--local-name name)
-  (setq crdt--network-process
-        (make-network-process
-         :name "CRDT Server"
-         :server t
-         :family 'ipv4
-         :host "0.0.0.0"
-         :service port
-         :filter #'crdt--network-filter
-         :sentinel #'crdt--server-process-sentinel
-         :plist `(crdt-buffer ,(current-buffer) password
-                              ,(when (and password (> (length password) 0)) 
password)))))
-(defsubst crdt--clear-overlay-table ()
-  (when crdt--overlay-table
+(defun crdt--share-buffer (buffer session)
+  (if (process-contact session :server)
+      (with-current-buffer buffer
+        (setq crdt--status-buffer (process-get session 'status-buffer))
+        (puthash (buffer-name buffer) buffer (crdt--buffer-table))
+        (setq crdt--buffer-network-name (buffer-name buffer))
+        (crdt-mode)
+        (save-excursion
+          (widen)
+          (let ((crdt--inhibit-update t))
+            (with-silent-modifications
+              (crdt--local-insert (point-min) (point-max))))
+          (crdt--broadcast-maybe
+           (crdt--format-message `(sync
+                                   ,crdt--buffer-network-name
+                                   ,major-mode
+                                   ,(buffer-substring-no-properties 
(point-min) (point-max))
+                                   ,@ (crdt--dump-ids (point-min) (point-max) 
nil)))))
+        (crdt--refresh-buffers-maybe)
+        (crdt--refresh-sessions-maybe))
+    (message "Only server can add new buffer.")))
+(defun crdt-share-buffer (session-name)
+  "Share the current buffer in the CRDT session with name SESSION-NAME.
+Create a new one if such a CRDT session doesn't exist.
+If SESSION-NAME is empty, use the buffer name of the current buffer."
+  (interactive
+   (list (let ((session-name (completing-read "Enter a session name (create if 
not exist): "
+                                              crdt--session-alist)))
+           (unless (and session-name (> (length session-name) 0))
+             (setq session-name (buffer-name (current-buffer))))
+           session-name)))
+  (if (and crdt-mode crdt--status-buffer)
+      (message "Current buffer is already shared in a CRDT session.")
+    (let ((session (assoc session-name crdt--session-alist)))
+      (if session
+          (crdt--share-buffer (current-buffer) (cdr session))
+        (let ((port (read-from-minibuffer "Create new session on Port (default 
1333): " nil nil t nil "1333")))
+          (crdt--share-buffer (current-buffer) (crdt-new-session port 
session-name)))))))
+(defun crdt-stop-share-buffer ()
+  "Stop sharing the current buffer."
+  (interactive)
+  (if crdt-mode
+      (if (crdt--server-p)
+          (let ((buffer-name crdt--buffer-network-name))
+            (with-current-buffer crdt--status-buffer
+              (let ((desync-message `(desync ,buffer-name)))
+                (crdt-process-message desync-message nil))))
+        (message "Only server can stop sharing a buffer."))
+    (message "Not a CRDT shared buffer.")))
+(defun crdt-new-session (port session-name &optional password display-name)
+  "Start a new CRDT session on PORT."
+  (let ((new-session
+         (with-current-buffer (generate-new-buffer " *crdt-status*")
+           (condition-case err
+               (setq crdt--network-process
+                     (make-network-process
+                      :name "CRDT Server"
+                      :server t
+                      :family 'ipv4
+                      :host "0.0.0.0"
+                      :buffer (current-buffer)
+                      :service port
+                      :filter #'crdt--network-filter
+                      :sentinel #'crdt--server-process-sentinel
+                      :plist `(status-buffer ,(current-buffer))))
+             (t (kill-buffer (current-buffer))
+                (signal (car err) (cdr err))))
+           (setq crdt--local-id 0)
+           (setq crdt--network-clients nil)
+           (setq crdt--local-clock 0)
+           (setq crdt--next-client-id 1)
+           (unless password
+             (setq password
+                   (when crdt-ask-for-password
+                     (read-from-minibuffer "Set password (empty for no 
authentication): "))))
+           (when (and password (> (length password) 0))
+             (process-put crdt--network-process 'password password))
+           (unless display-name
+             (setq display-name (crdt--read-name)))
+           (setq crdt--local-name display-name)
+           (setq crdt--contact-table (make-hash-table :test 'equal))
+           (setq crdt--buffer-table (make-hash-table :test 'equal))
+           (setq crdt--status-buffer (current-buffer))
+           crdt--network-process)))
+    (push (cons session-name new-session) crdt--session-alist)
+    new-session))
+(defsubst crdt--clear-pseudo-cursor-table ()
+  (when crdt--pseudo-cursor-table
     (maphash (lambda (key pair)
                (delete-overlay (car pair))
                (delete-overlay (cdr pair)))
-             crdt--overlay-table)
-    (setq crdt--overlay-table nil)))
-(defun crdt-stop-serve-buffer ()
-  "Stop sharing the current buffer."
-  (interactive)
-  (if (or (not crdt--network-process)
-          (not (process-contact crdt--network-process :server)))
-      (message "No CRDT server running on current buffer.")
-    (when (process-buffer crdt--network-process)
-      (kill-buffer (process-buffer crdt--network-process)))
-    (delete-process crdt--network-process)
-    (dolist (client crdt--network-clients)
-      (when (process-live-p client)
-        (delete-process client))
-      (when (process-buffer client)
-        (kill-buffer (process-buffer client))))
-    (setq crdt--network-process nil)
-    (setq crdt--network-clients nil)
-    (crdt--clear-overlay-table)
-    (setq crdt--local-id nil)
-    (setq crdt--contact-table nil))
-  (crdt-mode 0))
-(defun crdt-stop-client ()
-  "Stop the CRDT client running on current buffer if any.
-Leave the buffer open."
+             crdt--pseudo-cursor-table)
+    (setq crdt--pseudo-cursor-table nil)))
+(defun crdt-stop-session ()
+  "Stop sharing the current session."
   (interactive)
-  (if (or (not crdt--network-process) (process-contact crdt--network-process 
:server))
-      (message "No CRDT client running on current buffer.")
-    (when (process-buffer crdt--network-process)
-      (kill-buffer (process-buffer crdt--network-process)))
-    (delete-process crdt--network-process)
-    (setq crdt--network-process nil)
-    (crdt--clear-overlay-table)
-    (setq crdt--local-id nil)
-    (setq crdt--contact-table nil)
-    (message "Disconnected from server."))
-  (crdt-mode 0))
+  (if (not crdt--status-buffer)
+      (message "No CRDT session running on current buffer.")
+    (let ((status-buffer crdt--status-buffer))
+      (with-current-buffer status-buffer
+        (dolist (client crdt--network-clients)
+          (when (process-live-p client)
+            (delete-process client))
+          (when (process-buffer client)
+            (kill-buffer (process-buffer client))))
+        (when crdt--user-menu-buffer
+          (kill-buffer crdt--user-menu-buffer))
+        (maphash
+         (lambda (k v)
+           (with-current-buffer v
+             (setq crdt--status-buffer nil)
+             (crdt-mode 0)))
+         crdt--buffer-table)
+        (setq crdt--session-alist
+              (delq (cl-find-if (lambda (p) (eq (cdr p) crdt--network-process))
+                                crdt--session-alist)
+                    crdt--session-alist))
+        (crdt--refresh-sessions-maybe)
+        (delete-process crdt--network-process)
+        (message "Disconnected."))
+      (kill-buffer status-buffer))))
+
 (defun crdt-connect (address port &optional name)
   "Connect to a CRDT server running at ADDRESS:PORT.
 Open a new buffer to display the shared content."
   (interactive "MAddress: \nnPort: ")
-  (switch-to-buffer (generate-new-buffer "CRDT Client"))
   (unless name
     (setq name (crdt--read-name)))
-  (setq crdt--local-name name)
-  (setq crdt--network-process
-        (make-network-process
-         :name "CRDT Client"
-         :buffer (generate-new-buffer "*crdt-client*")
-         :host address
-         :family 'ipv4
-         :service port
-         :filter #'crdt--network-filter
-         :sentinel #'crdt--client-process-sentinel
-         :plist `(crdt-buffer ,(current-buffer))))
-  (crdt-mode)
-  (add-hook 'kill-buffer-hook #'crdt-stop-client nil t)
-  (process-send-string crdt--network-process
-                       (format "%S" `(hello ,name)))
-  (insert (format "Connected to server %s:%s, synchronizing..." address port)))
+  (setq crdt--status-buffer
+        (with-current-buffer (generate-new-buffer "*crdt-client*")
+          (setq crdt--local-name name)
+          (condition-case err
+              (setq crdt--network-process
+                    (make-network-process
+                     :name "CRDT Client"
+                     :buffer (current-buffer)
+                     :host address
+                     :family 'ipv4
+                     :service port
+                     :filter #'crdt--network-filter
+                     :sentinel #'crdt--client-process-sentinel
+                     :plist `(status-buffer ,(current-buffer))))
+            (t (kill-buffer (current-buffer))
+               (signal (car err) (cdr err))))
+          (push (cons address crdt--network-process) crdt--session-alist)
+          (setq crdt--local-clock 0)
+          (process-send-string crdt--network-process
+                               (crdt--format-message `(hello ,name)))
+          (setq crdt--contact-table (make-hash-table :test 'equal))
+          (setq crdt--buffer-table (make-hash-table :test 'equal))
+          (setq crdt--status-buffer (current-buffer)))))
 (defun crdt-test-client ()
   (interactive)
   (crdt-connect "127.0.0.1" 1333))
 (defun crdt-test-server ()
   (interactive)
-  (crdt-serve-buffer 1333))
+  (crdt--share-buffer (current-buffer) (crdt-new-session 1333 "test")))
+
+;;; overlay tracking
+(defun crdt--enable-overlay-species (species)
+  (push species crdt--enabled-overlay-species)
+  (when crdt-mode
+    (let ((crdt--inhibit-overlay-advices t))
+      (maphash (lambda (k ov)
+                 (let ((meta (overlay-get ov 'crdt-meta)))
+                   (when (eq species (crdt--overlay-metadata-species meta))
+                     (cl-loop for (prop value) on 
(crdt--overlay-metadata-plist meta) by #'cddr
+                           do (overlay-put ov prop value)))))
+               crdt--overlay-table))))
+(defun crdt--disable-overlay-species (species)
+  (setq crdt--enabled-overlay-species (delq species 
crdt--enabled-overlay-species))
+  (when crdt-mode
+    (let ((crdt--inhibit-overlay-advices t))
+      (maphash (lambda (k ov)
+                 (let ((meta (overlay-get ov 'crdt-meta)))
+                   (when (eq species (crdt--overlay-metadata-species meta))
+                     (cl-loop for (prop value) on 
(crdt--overlay-metadata-plist meta) by #'cddr
+                           do (overlay-put ov prop nil)))))
+               crdt--overlay-table))))
+(defun crdt--make-overlay-advice (orig-fun beg end &optional buffer 
front-advance rear-advance)
+                                        ; should we check if we are in the 
current buffer?
+  (let ((new-overlay (funcall orig-fun beg end buffer front-advance 
rear-advance)))
+    (when crdt-mode
+      (when crdt--track-overlay-species
+        (crdt--broadcast-maybe
+         (crdt--format-message
+          (crdt--overlay-add-message (crdt--local-id) (crdt--local-clock)
+                                     crdt--track-overlay-species front-advance 
rear-advance
+                                     beg end)))
+        (let* ((key (cons (crdt--local-id) (crdt--local-clock)))
+               (meta (crdt--make-overlay-metadata key 
crdt--track-overlay-species
+                                                  front-advance rear-advance 
nil)))
+          (puthash key new-overlay crdt--overlay-table)
+          (let ((crdt--inhibit-overlay-advices t)
+                (crdt--modifying-overlay-metadata t))
+            (overlay-put new-overlay 'crdt-meta meta)))
+        (cl-incf (crdt--local-clock))))
+    new-overlay))
+(cl-defmethod crdt-process-message ((message (head overlay-add)) process)
+  (cl-destructuring-bind
+        (buffer-name site-id logical-clock species
+                     front-advance rear-advance start-hint start-id-base64 
end-hint end-id-base64)
+      (cdr message)
+    (crdt--with-buffer-name
+     buffer-name
+     (let* ((crdt--track-overlay-species nil)
+            (start (crdt--find-id (base64-decode-string start-id-base64) 
start-hint front-advance))
+            (end (crdt--find-id (base64-decode-string end-id-base64) end-hint 
rear-advance))
+            (new-overlay
+             (make-overlay start end nil front-advance rear-advance))
+            (key (cons site-id logical-clock))
+            (meta (crdt--make-overlay-metadata key species
+                                               front-advance rear-advance 
nil)))
+       (puthash key new-overlay crdt--overlay-table)
+       (let ((crdt--inhibit-overlay-advices t)
+             (crdt--modifying-overlay-metadata t))
+         (overlay-put new-overlay 'crdt-meta meta)))))
+  (crdt--broadcast-maybe (crdt--format-message message) (process-get process 
'client-id)))
+(defun crdt--move-overlay-advice (orig-fun ov beg end &rest args)
+  (when crdt-mode
+    (unless crdt--inhibit-overlay-advices
+      (let ((meta (overlay-get ov 'crdt-meta)))
+        (when meta ;; to be fixed
+          (let ((key (crdt--overlay-metadata-lamport-timestamp meta))
+                (front-advance (crdt--overlay-metadata-front-advance meta))
+                (rear-advance (crdt--overlay-metadata-rear-advance meta)))
+            (crdt--broadcast-maybe
+             (crdt--format-message
+              `(overlay-move ,crdt--buffer-network-name ,(car key) ,(cdr key)
+                             ,beg ,(if front-advance
+                                       (base64-encode-string (crdt--get-id 
beg))
+                                     (crdt--base64-encode-maybe (crdt--get-id 
(1- beg))))
+                             ,end ,(if rear-advance
+                                       (base64-encode-string (crdt--get-id 
end))
+                                     (crdt--base64-encode-maybe (crdt--get-id 
(1- end))))))))))))
+  (apply orig-fun ov beg end args))
+(cl-defmethod crdt-process-message ((message (head overlay-move)) process)
+  (cl-destructuring-bind (buffer-name site-id logical-clock
+                                      start-hint start-id-base64 end-hint 
end-id-base64)
+      (cdr message)
+    (crdt--with-buffer-name
+     buffer-name
+     (let* ((key (cons site-id logical-clock))
+            (ov (gethash key crdt--overlay-table)))
+       (when ov
+         (let* ((meta (overlay-get ov 'crdt-meta))
+                (front-advance (crdt--overlay-metadata-front-advance meta))
+                (rear-advance (crdt--overlay-metadata-rear-advance meta))
+                (start (crdt--find-id (base64-decode-string start-id-base64) 
start-hint front-advance))
+                (end (crdt--find-id (base64-decode-string end-id-base64) 
end-hint rear-advance)))
+           (let ((crdt--inhibit-overlay-advices t))
+             (move-overlay ov start end)))))))
+  (crdt--broadcast-maybe (crdt--format-message message) (process-get process 
'client-id)))
+(defvar crdt--inhibit-overlay-advices nil)
+(defvar crdt--modifying-overlay-metadata nil)
+(defun crdt--delete-overlay-advice (orig-fun ov)
+  (unless crdt--inhibit-overlay-advices
+    (when crdt-mode
+      (let ((meta (overlay-get ov 'crdt-meta)))
+        (when meta
+          (let ((key (crdt--overlay-metadata-lamport-timestamp meta)))
+            (remhash key crdt--overlay-table)
+            (crdt--broadcast-maybe (crdt--format-message
+                                    `(overlay-remove 
,crdt--buffer-network-name ,(car key) ,(cdr key)))))))))
+  (funcall orig-fun ov))
+(cl-defmethod crdt-process-message ((message (head overlay-remove)) process)
+  (cl-destructuring-bind (buffer-name site-id logical-clock) (cdr message)
+    (crdt--with-buffer-name
+     buffer-name
+     (let* ((key (cons site-id logical-clock))
+            (ov (gethash key crdt--overlay-table)))
+       (when ov
+         (remhash key crdt--overlay-table)
+         (let ((crdt--inhibit-overlay-advices t))
+           (delete-overlay ov))))))
+  (crdt--broadcast-maybe (crdt--format-message message) (process-get process 
'client-id)))
+(defun crdt--overlay-put-advice (orig-fun ov prop value)
+  (unless (and (eq prop 'crdt-meta)
+               (not crdt--modifying-overlay-metadata))
+    (when crdt-mode
+      (unless crdt--inhibit-overlay-advices
+        (let ((meta (overlay-get ov 'crdt-meta)))
+          (when meta
+            (setf (crdt--overlay-metadata-plist meta) (plist-put 
(crdt--overlay-metadata-plist meta) prop value))
+            (let* ((key (crdt--overlay-metadata-lamport-timestamp meta))
+                   (message (crdt--format-message `(overlay-put 
,crdt--buffer-network-name
+                                                                ,(car key) 
,(cdr key) ,prop ,value))))
+              (condition-case nil
+                  (progn ; filter non-readable object
+                    (read-from-string message)
+                    (crdt--broadcast-maybe message))
+                (invalid-read-syntax)))))))
+    (funcall orig-fun ov prop value)))
+(cl-defmethod crdt-process-message ((message (head overlay-put)) process)
+  (cl-destructuring-bind (buffer-name site-id logical-clock prop value) (cdr 
message)
+    (crdt--with-buffer-name
+     buffer-name
+     (let ((ov (gethash (cons site-id logical-clock) crdt--overlay-table)))
+       (when ov
+         (let ((meta (overlay-get ov 'crdt-meta)))
+           (setf (crdt--overlay-metadata-plist meta)
+                 (plist-put (crdt--overlay-metadata-plist meta) prop value))
+           (when (memq (crdt--overlay-metadata-species meta) 
crdt--enabled-overlay-species)
+             (let ((crdt--inhibit-overlay-advices t))
+               (overlay-put ov prop value))))))))
+  (crdt--broadcast-maybe (crdt--format-message message) (process-get process 
'client-id)))
+(advice-add 'make-overlay :around #'crdt--make-overlay-advice)
+(advice-add 'move-overlay :around #'crdt--move-overlay-advice)
+(advice-add 'delete-overlay :around #'crdt--delete-overlay-advice)
+(advice-add 'overlay-put :around #'crdt--overlay-put-advice)
 (defun crdt--install-hooks ()
   (add-hook 'after-change-functions #'crdt--after-change nil t)
   (add-hook 'before-change-functions #'crdt--before-change nil t)
@@ -868,15 +1408,36 @@ Open a new buffer to display the shared content."
   (remove-hook 'before-change-functions #'crdt--before-change t)
   (remove-hook 'post-command-hook #'crdt--post-command t))
 (define-minor-mode crdt-mode
-  "CRDT mode" nil " CRDT" nil
-  (if crdt-mode
+    "CRDT mode" nil " CRDT" nil
+    (if crdt-mode
+        (progn
+          (setq crdt--pseudo-cursor-table (make-hash-table))
+          (setq crdt--overlay-table (make-hash-table :test 'equal))
+          (crdt--install-hooks))
+      (crdt--uninstall-hooks)
+      (crdt--clear-pseudo-cursor-table)
+      (setq crdt--overlay-table nil)))
+
+;;; Org integration
+(defun crdt--org-overlay-advice (orig-fun &rest args)
+  (if crdt-org-sync-overlay-mode
+      (let ((crdt--track-overlay-species 'org))
+        (apply orig-fun args))
+    (apply orig-fun args)))
+(cl-loop for command in '(org-cycle org-shifttab)
+      do (advice-add command :around #'crdt--org-overlay-advice))
+(define-minor-mode crdt-org-sync-overlay-mode ""
+  nil " Sync Org Overlay" nil
+  (if crdt-org-sync-overlay-mode
       (progn
-        (setq crdt--overlay-table (make-hash-table))
-        (setq crdt--contact-table (make-hash-table))
-        (crdt--install-hooks))
-    (crdt--uninstall-hooks)
-    (when crdt--user-list-buffer
-      (kill-buffer crdt--user-list-buffer)
-      (setq crdt--user-list-buffer nil))))
+        (save-excursion
+          (widen)
+          ;; heuristic to remove existing org overlays
+          (cl-loop for ov in (overlays-in (point-min) (point-max))
+                do (when (memq (overlay-get ov 'invisible)
+                               '(outline org-hide-block))
+                     (delete-overlay ov))))
+        (crdt--enable-overlay-species 'org))
+    (crdt--disable-overlay-species 'org)))
 
 (provide 'crdt)



reply via email to

[Prev in Thread] Current Thread [Next in Thread]