branch: externals/crdt commit dfc98d35257e812922da103a0a54e96fb155490d Author: Qiantan Hong <qh...@alum.mit.edu> Commit: Qiantan Hong <qh...@alum.mit.edu>
add crdt-goto-{next,prev}-user --- README.org | 123 ++++++++++++++++++++++++++++++++++++------------------------- crdt.el | 72 ++++++++++++++++++++++++++++++------ 2 files changed, 134 insertions(+), 61 deletions(-) diff --git a/README.org b/README.org index 1dcd4edca5..5d07211b38 100644 --- a/README.org +++ b/README.org @@ -1,9 +1,11 @@ * Introduction -~crdt.el~ is a real-time collaborative editing environment for Emacs using Conflict-free Replicated Data Types. +~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... +- [[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 - Synchronize Org mode folding status @@ -16,26 +18,30 @@ Highlights: ~crdt.el~ is now on GNU ELPA! Just =M-x package-install crdt=. -*Caution!!!* Please make sure that you and your peers are on the same ~crdt.el~ version! -It turns out to be one of the most common causes of ~crdt.el~ not working. -Because currently the network protocol is not stablized, behavior when using mismatched versions is unexpectable. +*Caution!!!* Please make sure that you and your peers are on the same +~crdt.el~ version! It turns out to be one of the most common causes +of ~crdt.el~ not working. Because currently the network protocol is +not stablized, behavior when using mismatched versions is +unexpectable. - Strictly speaking, it should works when =crdt-protocol-version= are defined and the same on all peers. But why not save some hassle and keep everyone on the latest version. - To upgrade, just =M-x package-reinstall crdt=, then preferably restart Emacs. ** Start a shared session -A shared session is a place that can contains multiple buffers (or files), -and multiple users can join to collaboratively edit those buffers (or files). -Think about a meeting room with some people working together on some papers. +A shared session is a place that can contains multiple buffers (or +files), and multiple users can join to collaboratively edit those +buffers (or files). Think about a meeting room with some people +working together on some papers. In some buffer, =M-x crdt-share-buffer=. Then enter session name. This add the current buffer to the existing session with that name. -If no such exists, it creates a new session with the provided session name, -and initially contains the current buffer as a shared buffer. +If no such exists, it creates a new session with the provided session +name, and initially contains the current buffer as a shared buffer. -If a new session is to be created, you need to enter port (default to 6530), -optional password and your display name (default to your current =(user-full-name)=). +If a new session is to be created, you need to enter port (default to +6530), optional password and your display name (default to your +current =(user-full-name)=). ** Join a session @@ -43,10 +49,16 @@ optional password and your display name (default to your current =(user-full-nam ** List active users -In a CRDT shared buffer (either server or client), =M-x crdt-list-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. -Press ~f~ to follow that user, and press ~f~ again or =M-x crdt-stop-follow= to stop following. +In the displayed user list, press ~RET~ on an entry to goto that +user's cursor position. Press ~f~ to follow that user, and press ~f~ +again or =M-x crdt-stop-follow= to stop following. + +You can also use =M-x crdt-goto-next-user= and =M-x +crdt-goto-prev-user= to cycle through users' cursor positions from any +CRDT shared buffer (don't need to be in the user list buffer). ** List all sessions, and buffer in current session @@ -57,10 +69,12 @@ press ~RET~ in the session list to see buffers in the selected session. ** Stop sharing -=M-x crdt-stop-session= stops a session you've started and disconnect all other users from it. -This will ask for your confirmation, customize =crdt-confirm-stop-session= if you want to disable it. +=M-x crdt-stop-session= stops a session you've started and disconnect +all other users from it. This will ask for your confirmation, +customize =crdt-confirm-stop-session= if you want to disable it. -You can also press ~k~ or ~d~ in the session list (show it by =M-x crdt-list-sessions=). +You can also press ~k~ or ~d~ in the session list (show it by =M-x +crdt-list-sessions=). =M-x crdt-stop-share-buffer= removes current buffer from its CRDT session (this operation is only allowed at server side). You can also @@ -70,66 +84,75 @@ press ~k~ or ~d~ in the buffer list. =M-x crdt-disconnect=, then choose a session to disconnect from. -You can also press ~k~ or ~d~ in the session list (show it by =M-x crdt-list-sessions=). +You can also press ~k~ or ~d~ in the session list (show it by =M-x +crdt-list-sessions=). -The server Emacs has the privilege to disconnect a user from a session. -To do so, press ~k~ or ~d~ on an entry in the user list (show it by =M-x crdt-list-users=). +The server Emacs has the privilege to disconnect a user from a +session. To do so, press ~k~ or ~d~ on an entry in the user list +(show it by =M-x crdt-list-users=). ** Visualizing author of parts of the document -Turn on =crdt-visualize-author-mode=. Colored underlines are added to each part of the document, -based on which user authored it. + +Turn on =crdt-visualize-author-mode=. Colored underlines are added to +each part of the document, based on which user authored it. ** 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. +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. ** Comint integration -Just go ahead and share you comint REPL buffer! Tested: ~shell~ and ~cmuscheme~. -By default, when sharing a comint buffer, ~crdt.el~ temporarily reset input history (as in =M-n= =M-p=) -so others don't spy into your =.bash_history= and alike. -You can customize this behavior using variable =crdt-comint-share-input-history=. +Just go ahead and share you comint REPL buffer! Tested: ~shell~ and +~cmuscheme~. By default, when sharing a comint buffer, ~crdt.el~ +temporarily reset input history (as in =M-n= =M-p=) so others don't +spy into your =.bash_history= and alike. You can customize this +behavior using variable =crdt-comint-share-input-history=. ** What if we don't have a public IP? There're various workaround. -- You can use [[https://gitlab.com/gjedeer/tuntox][tuntox]] to proxy your connection over the [[https://tox.chat][Tox]] protocol. - =crdt.el= has experimental built-in integration for =tuntox=. - To enable it, you need to install =tuntox=, - set up the custom variable =crdt-tuntox-executable= accordingly (the path to your =tuntox= binary), - and set the custom variable =crdt-use-tuntox=. - Setting it to =t= make =crdt.el= always create =tuntox= proxy for new server sessions, - and setting it to ='confirm= make =crdt.el= ask you every time when creating new sessions. - After starting a session with =tuntox= proxy, - you can =M-x crdt-copy-url= to copy a URL recognizable by =M-x crdt-connect= and share it to your friends. - Be aware that according to my experience, =tuntox= takes significant time to establish a connection (sometimes up to half a minute), +- You can use [[https://gitlab.com/gjedeer/tuntox][tuntox]] to proxy + your connection over the [[https://tox.chat][Tox]] protocol. + =crdt.el= has experimental built-in integration for =tuntox=. To + enable it, you need to install =tuntox=, set up the custom variable + =crdt-tuntox-executable= accordingly (the path to your =tuntox= + binary), and set the custom variable =crdt-use-tuntox=. Setting it + to =t= make =crdt.el= always create =tuntox= proxy for new server + sessions, and setting it to ='confirm= make =crdt.el= ask you every + time when creating new sessions. After starting a session with + =tuntox= proxy, you can =M-x crdt-copy-url= to copy a URL + recognizable by =M-x crdt-connect= and share it to your friends. Be + aware that according to my experience, =tuntox= takes significant + time to establish a connection (sometimes up to half a minute), however it gets much faster after the connection is established. -- You can use Teredo to get a public routable IPv6 address. - One free software implementation is Miredo. Get it from your - favorite package manager or from [[https://www.remlab.net/miredo/][their website]]. +- You can use Teredo to get a public routable IPv6 address. One free + software implementation is Miredo. Get it from your favorite package + manager or from [[https://www.remlab.net/miredo/][their website]]. A typical usage is (run as root) #+BEGIN_SRC # /usr/local/sbin/miredo # ifconfig teredo #+END_SRC - The =ifconfig= command should print the information of your IPv6 address. - Now your traffic go through IPv6, and once you start a =crdt.el= session, - your friends should be able to join using the IPv6 address. - For more information, see the user guide on the Miredo website. + The =ifconfig= command should print the information of your IPv6 + address. Now your traffic go through IPv6, and once you start a + =crdt.el= session, your friends should be able to join using the + IPv6 address. For more information, see the user guide on the + Miredo website. - You can use SSH port forwarding if you have a VPS with public IP. Example usage: #+BEGIN_SRC $ ssh -R EXAMPLE.COM:6530:127.0.0.1:6530 EXAMPLE.COM #+END_SRC - This make your =crdt.el= session on local port =6530= accessible from - =EXAMPLE.COM:6530=. + This make your =crdt.el= session on local port =6530= accessible + from =EXAMPLE.COM:6530=. - Note that you need to set the following =/etc/ssh/sshd_config= option on - your VPS + Note that you need to set the following =/etc/ssh/sshd_config= + option on your VPS #+BEGIN_SRC GatewayPorts yes #+END_SRC diff --git a/crdt.el b/crdt.el index d3768a481d..cfedcc59f3 100644 --- a/crdt.el +++ b/crdt.el @@ -370,6 +370,7 @@ Must be used inside CRDT--WITH-INSERTION-INFORMATION." next-user-id (buffer-table (make-hash-table :test 'equal)) ; maps buffer network name to buffer follow-user-id + my-location-marker ; used to store my location temporarily when crdt-goto-{next,prev}-user permissions (local-fcap-table (make-hash-table)) (remote-fcap-table (make-hash-table))) @@ -916,26 +917,78 @@ Directly return the user name under point if in the user menu." (crdt--read-user session) (signal 'quit nil))) -(defun crdt-goto-user (session user-id) - "Goto the cursor location of user with USER-ID in SESSION." +(defun crdt-goto-user (session user-id &optional noswitch) + "Goto the cursor location of user with USER-ID in SESSION. +If NOSWITCH is non-nil, don't switch window." (interactive (let ((session (crdt--read-session-maybe))) (list session (crdt--read-user-maybe session)))) (let ((crdt--session session)) (if (eq user-id (crdt--session-local-id crdt--session)) - (funcall (if (eq major-mode 'crdt-user-menu-mode) - #'switch-to-buffer-other-window - #'switch-to-buffer) - (gethash (crdt--session-focused-buffer-name crdt--session) (crdt--session-buffer-table crdt--session))) + (unless noswitch + (funcall (if (eq major-mode 'crdt-user-menu-mode) + #'switch-to-buffer-other-window + #'switch-to-buffer) + (gethash (crdt--session-focused-buffer-name crdt--session) (crdt--session-buffer-table crdt--session)))) (unless (cl-block nil (let* ((metadata (or (gethash user-id (crdt--session-contact-table crdt--session)) (cl-return))) (buffer-name (or (crdt--contact-metadata-focus metadata) (cl-return)))) (crdt--with-buffer-name-pull (buffer-name) - (switch-to-buffer-other-window (current-buffer)) + (unless noswitch + (switch-to-buffer-other-window (current-buffer))) (ignore-errors (goto-char (overlay-start (car (gethash user-id crdt--pseudo-cursor-table))))) t))) (message "Doesn't have position information for this user yet."))))) +(defun crdt--session-ensure-user-menu-buffer (session) + (unless (and (crdt--session-user-menu-buffer session) + (buffer-live-p (crdt--session-user-menu-buffer session))) + (setf (crdt--session-user-menu-buffer session) + (generate-new-buffer (format "*CRDT Users %s*" (crdt--session-urlstr session)))) + (crdt--assimilate-session (crdt--session-user-menu-buffer session)))) + +(defun crdt--cycle-user (&optional prev) + "Move point in user menu buffer and goto its cursor position. +If user menu buffer does not exist yet, create it. +If PREV is non-nil, move backward, otherwise move forward. +When moving into/out of ourselves, push/pop a global marker instead." + (unless crdt--session + (error "Not a CRDT shared buffer")) + (crdt--session-ensure-user-menu-buffer crdt--session) + (let ((mark (point-marker))) + (with-current-buffer (crdt--session-user-menu-buffer crdt--session) + (when (= (point) (point-max)) + (forward-line -1)) + (when (eq (tabulated-list-get-id) (crdt--session-local-id crdt--session)) + (setf (crdt--session-my-location-marker crdt--session) mark) + (add-to-history 'global-mark-ring mark global-mark-ring-max t)) + (if prev + (unless (= (forward-line -1) 0) + (goto-char (point-max)) + (forward-line -1)) + (forward-line) + (when (= (point) (point-max)) + (goto-char (point-min)))) + (let ((window (get-buffer-window (current-buffer) t))) + (when window (set-window-point window (point)))) + (if (eq (tabulated-list-get-id) (crdt--session-local-id crdt--session)) + (if (eq (car global-mark-ring) (crdt--session-my-location-marker crdt--session)) + (pop-global-mark) + (goto-char (crdt--session-my-location-marker crdt--session))) + (crdt-goto-user crdt--session (tabulated-list-get-id) t))))) + +(defun crdt-goto-next-user () + "Move point in user menu buffer to next user and goto its cursor position. +If user menu buffer does not exist yet, create it." + (interactive) + (crdt--cycle-user)) + +(defun crdt-goto-prev-user () + "Move point in user menu buffer to previous user and goto its cursor position. +If user menu buffer does not exist yet, create it." + (interactive) + (crdt--cycle-user t)) + (defun crdt-kill-user (session user-id) "Disconnect the user with USER-ID in SESSION. Only server can perform this action." @@ -974,10 +1027,7 @@ Only server can perform this action." (with-current-buffer (current-buffer) (unless crdt--session (error "Not a CRDT shared buffer")) - (unless (and (crdt--session-user-menu-buffer crdt--session) (buffer-live-p (crdt--session-user-menu-buffer crdt--session))) - (setf (crdt--session-user-menu-buffer crdt--session) - (generate-new-buffer (format "*CRDT Users %s*" (crdt--session-urlstr crdt--session)))) - (crdt--assimilate-session (crdt--session-user-menu-buffer crdt--session))) + (crdt--session-ensure-user-menu-buffer crdt--session) (let ((display-buffer (crdt--session-user-menu-buffer crdt--session))) (crdt-refresh-users display-buffer) (switch-to-buffer-other-window display-buffer)))))