From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Qiantan Hong Newsgroups: gmane.emacs.devel Subject: Re: Question collaborative editing - Wikipedia reference Date: Fri, 9 Oct 2020 07:04:47 +0000 Message-ID: <4AB5904E-A646-4A17-B78C-B4E0271E28B4@mit.edu> References: <83eemji6e8.fsf@gnu.org> <20201001141144.GO4797@protected.rcdrun.com> <20201001160136.chwmpollk4d3qc2e@Ergus> <20201004175447.GG15516@protected.rcdrun.com> <87362tvnil.fsf@gmail.com> <20201004194815.GH15516@protected.rcdrun.com> <11EB917D-3276-4D34-8DCD-2336E8062950@mit.edu> <20201005040245.GJ15516@protected.rcdrun.com> <20201005084441.GB4453@protected.rcdrun.com> <87zh50lhda.fsf@red-bean.com> <87pn5vgqsk.fsf@red-bean.com> <79924DB1-2613-4AF6-982C-39157CF3ED9B@mit.edu> <8C2D2AED-0AA8-48AC-ADB2-826177141F2F@gmail.com> <40B06E9F-A6D6-4F9B-8E2B-7DC55D16D0F7@mit.edu> <18458910-E6A1-4867-9936-FFA50E3E72D1@gmail.com> Mime-Version: 1.0 Content-Type: multipart/signed; boundary="Apple-Mail=_2AD76598-61FA-4149-BBEB-AB82F1D269B3"; protocol="application/pkcs7-signature"; micalg=sha-256 Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="9545"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Ergus , Fermin , Jean Louis , Caio Henrique , Noam Postavsky , Emacs developers , Karl Fogel , Stefan Monnier , Eli Zaretskii To: Yuan Fu Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Fri Oct 09 09:10:53 2020 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kQmYH-0002Kc-1V for ged-emacs-devel@m.gmane-mx.org; Fri, 09 Oct 2020 09:10:53 +0200 Original-Received: from localhost ([::1]:58170 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kQmYF-0002A6-Jg for ged-emacs-devel@m.gmane-mx.org; Fri, 09 Oct 2020 03:10:51 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:59310) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kQmSi-0004Bn-Py for emacs-devel@gnu.org; Fri, 09 Oct 2020 03:05:08 -0400 Original-Received: from outgoing-exchange-3.mit.edu ([18.9.28.13]:48108) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kQmSe-0003PJ-1p; Fri, 09 Oct 2020 03:05:08 -0400 Original-Received: from w92exedge4.exchange.mit.edu (W92EXEDGE4.EXCHANGE.MIT.EDU [18.7.73.16]) by outgoing-exchange-3.mit.edu (8.14.7/8.12.4) with ESMTP id 09974XhN021386; Fri, 9 Oct 2020 03:04:51 -0400 Original-Received: from oc11expo16.exchange.mit.edu (18.9.4.47) by w92exedge4.exchange.mit.edu (18.7.73.16) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Fri, 9 Oct 2020 03:03:48 -0400 Original-Received: from oc11expo16.exchange.mit.edu (18.9.4.47) by oc11expo16.exchange.mit.edu (18.9.4.47) with Microsoft SMTP Server (TLS) id 15.0.1365.1; Fri, 9 Oct 2020 03:04:47 -0400 Original-Received: from oc11expo16.exchange.mit.edu ([18.9.4.47]) by oc11expo16.exchange.mit.edu ([18.9.4.47]) with mapi id 15.00.1365.000; Fri, 9 Oct 2020 03:04:47 -0400 Thread-Topic: Question collaborative editing - Wikipedia reference Thread-Index: AQHWnA1T4LjU6k9HIUyZcJcFcadSFqmLhM0AgANFgACAAAQRgIAACE0AgABJOQA= In-Reply-To: <18458910-E6A1-4867-9936-FFA50E3E72D1@gmail.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: yes x-ms-exchange-messagesentrepresentingtype: 1 x-ms-exchange-transport-fromentityheader: Hosted x-originating-ip: [18.18.245.17] Received-SPF: pass client-ip=18.9.28.13; envelope-from=qhong@mit.edu; helo=outgoing-exchange-3.mit.edu X-detected-operating-system: by eggs.gnu.org: First seen = 2020/10/09 03:04:57 X-ACL-Warn: Detected OS = Windows 7 (Websense crawler) X-Spam_score_int: -18 X-Spam_score: -1.9 X-Spam_bar: - X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:257252 Archived-At: --Apple-Mail=_2AD76598-61FA-4149-BBEB-AB82F1D269B3 Content-Type: multipart/mixed; boundary="Apple-Mail=_27BBE01A-7683-4B72-BA13-70496B6368FE" --Apple-Mail=_27BBE01A-7683-4B72-BA13-70496B6368FE Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 Here is the current version, I feel like a relatively complete=20 set of essential features is implemented now. Changes: - Unicode support - fixed cursor behavior - M-x crdt-list-users displays a list of active users. Press enter to go to the position of that user=E2=80=99s cursor --Apple-Mail=_27BBE01A-7683-4B72-BA13-70496B6368FE Content-Disposition: attachment; filename=crdt.el Content-Type: application/octet-stream; x-unix-mode=0644; name="crdt.el" Content-Transfer-Encoding: quoted-printable ;;;=20crdt.el=20---=20collaborative=20editing=20using=20Conflict-free=20= Replicated=20Data=20Types=0A;;=0A;;=20Copyright=20(C)=202020=20Qiantan=20= Hong=0A;;=0A;;=20Author:=20Qiantan=20Hong=20=0A;;=20= Maintainer:=20Qiantan=20Hong=20=0A;;=20Keywords:=20= collaboration=20crdt=0A;;=0A;;=20crdt.el=20is=20free=20software:=20you=20= can=20redistribute=20it=20and/or=20modify=0A;;=20it=20under=20the=20= terms=20of=20the=20GNU=20General=20Public=20License=20as=20published=20= by=0A;;=20the=20Free=20Software=20Foundation,=20either=20version=203=20= of=20the=20License,=20or=0A;;=20(at=20your=20option)=20any=20later=20= version.=0A;;=0A;;=20crdt.el=20is=20distributed=20in=20the=20hope=20that=20= it=20will=20be=20useful,=0A;;=20but=20WITHOUT=20ANY=20WARRANTY;=20= without=20even=20the=20implied=20warranty=20of=0A;;=20MERCHANTABILITY=20= or=20FITNESS=20FOR=20A=20PARTICULAR=20PURPOSE.=20=20See=20the=0A;;=20GNU=20= General=20Public=20License=20for=20more=20details.=0A;;=0A;;=20You=20= should=20have=20received=20a=20copy=20of=20the=20GNU=20General=20Public=20= License=0A;;=20along=20with=20crdt.el.=20=20If=20not,=20see=20= .=0A=0A;;;=20Commentary:=0A;;=20*=20= Algorithm=0A;;=20=20=20This=20packages=20implements=20the=20Logoot=20= split=20algorithm=0A;;=20=20=20Andr=C3=A9,=20Luc,=20et=20al.=20=20= "Supporting=20adaptable=20granularity=20of=20changes=20for=20= massive-scale=20collaborative=20editing."=209th=20IEEE=20International=20= Conference=20on=20Collaborative=20Computing:=20Networking,=20= Applications=20and=20Worksharing.=20=20IEEE,=202013.=0A;;=20*=20Protocol=0A= ;;=20=20=20Text-based=20version=0A;;=20=20=20(it=20should=20be=20easy=20= to=20migrate=20to=20a=20binary=20version.=20=20Using=20text=20for=20= better=20debugging=20for=20now)=0A;;=20=20=20Every=20message=20takes=20= the=20form=20(type=20.=20body)=0A;;=20=20=20type=20can=20be:=20insert=20= hello=20cursor=20challenge=20sync=0A;;=20=20=20-=20insert=0A;;=20=20=20=20= =20body=20takes=20the=20form=20(crdt-id=20position-hint=20content)=0A;;=20= =20=20=20=20-=20position-hint=20is=20the=20buffer=20position=20where=20= the=20operation=20happens=20at=20the=20site=0A;;=20=20=20=20=20=20=20= which=20generates=20the=20operation.=20=20Then=20we=20can=20play=20the=20= trick=20that=20start=20search=0A;;=20=20=20=20=20=20=20near=20this=20= position=20at=20other=20sites=20to=20speedup=20crdt-id=20search=0A;;=20=20= =20=20=20-=20content=20is=20the=20string=20to=20be=20inserted=0A;;=20=20=20= -=20delete=0A;;=20=20=20=20=20body=20takes=20the=20form=20(position-hint=20= (crdt-id=20.=20length)*)=0A;;=20=20=20-=20cursor=0A;;=20=20=20=20=20body=20= takes=20the=20form=0A;;=20=20=20=20=20=20=20=20=20=20(site-id=20= point-position-hint=20point-crdt-id=20mark-position-hint=20mark-crdt-id)=0A= ;;=20=20=20=20=20*-crdt-id=20can=20be=20either=20a=20CRDT=20ID,=20or=0A= ;;=20=20=20=20=20=20=20-=20nil,=20which=20means=20clear=20the=20= point/mark=0A;;=20=20=20-=20contact=0A;;=20=20=20=20=20body=20takes=20= the=20form=0A;;=20=20=20=20=20=20=20=20=20=20(site-id=20name=20address)=0A= ;;=20=20=20=20=20when=20name=20is=20nil,=20clear=20the=20contact=20for=20= this=20site-id=0A;;=20=20=20-=20hello=0A;;=20=20=20=20=20This=20message=20= is=20sent=20from=20client=20to=20server,=20when=20a=20client=20connect=20= to=20the=20server.=0A;;=20=20=20=20=20body=20takes=20the=20form=20= (client-name=20&optional=20response)=0A;;=20=20=20-=20challenge=0A;;=20=20= =20=20=20body=20takes=20the=20form=20(salt)=0A;;=20=20=20-=20sync=0A;;=20= =20=20=20=20This=20message=20is=20sent=20from=20server=20to=20client=20= to=20get=20it=20sync=20to=20the=20state=20on=20the=20server.=0A;;=20=20=20= =20=20It's=20always=20sent=20after=20server=20receives=20a=20hello=20= message.=0A;;=20=20=20=20=20Might=20be=20used=20for=20error=20recovery=20= or=20other=20optimization=20in=20the=20future.=0A;;=20=20=20=20=20One=20= optimization=20I=20have=20in=20mind=20is=20let=20server=20try=20to=20= merge=20all=20CRDT=20item=20into=20a=20single=0A;;=20=20=20=20=20one=20= and=20try=20to=20synchronize=20this=20state=20to=20clients=20at=20best=20= effort.=0A;;=20=20=20=20=20body=20takes=20the=20form=20(site-id=20= major-mode=20content=20.=20crdt-id-list)=0A;;=20=20=20=20=20-=20site-id=20= is=20the=20site=20ID=20the=20server=20assigned=20to=20the=20client=0A;;=20= =20=20=20=20-=20major-mode=20is=20the=20major=20mode=20used=20at=20the=20= server=20site=0A;;=20=20=20=20=20-=20content=20is=20the=20string=20in=20= the=20buffer=0A;;=20=20=20=20=20-=20crdt-id-list=20is=20generated=20from=20= CRDT--DUMP-IDS=0A=0A=0A;;;=20Code:=0A=0A(defgroup=20crdt=20nil=0A=20=20= "Collaborative=20editing=20using=20Conflict-free=20Replicated=20Data=20= Types."=0A=20=20:prefix=20"crdt-"=0A=20=20:group=20'applications)=0A= (defcustom=20crdt-ask-for-name=20t=0A=20=20"Ask=20for=20display=20name=20= everytime=20a=20CRDT=20session=20is=20to=20be=20started."=0A=20=20:type=20= 'boolean)=0A(defcustom=20crdt-default-name=20""=0A=20=20"Default=20= display=20name."=0A=20=20:type=20'string)=0A(defcustom=20= crdt-ask-for-password=20t=0A=20=20"Ask=20for=20server=20password=20= everytime=20a=20CRDT=20server=20is=20to=20be=20started."=0A=20=20:type=20= 'boolean)=0A=0A(require=20'cl-lib)=0A=0A(require=20'color)=0A(defvar=20= crdt-cursor-region-colors=0A=20=20(let=20((n=2010))=0A=20=20=20=20= (cl-loop=20for=20i=20below=20n=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= for=20hue=20by=20(/=201.0=20n)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= collect=20(cons=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20(apply=20#'color-rgb-to-hex=0A=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(color-hsl-to-rgb=20= hue=200.5=200.5))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20(apply=20#'color-rgb-to-hex=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(color-hsl-to-rgb=20= hue=200.2=200.5))))))=0A=0A(defun=20crdt--get-cursor-color=20(site-id)=0A= =20=20"Get=20cursor=20color=20for=20SITE-ID."=0A=20=20(car=20(nth=20(mod=20= site-id=20(length=20crdt-cursor-region-colors))=20= crdt-cursor-region-colors)))=0A(defun=20crdt--get-region-color=20= (site-id)=0A=20=20"Get=20region=20color=20for=20SITE-ID."=0A=20=20(cdr=20= (nth=20(mod=20site-id=20(length=20crdt-cursor-region-colors))=20= crdt-cursor-region-colors)))=0A(defun=20crdt--move-cursor=20(ov=20pos)=0A= =20=20"Move=20pseudo=20cursor=20overlay=20OV=20to=20POS."=0A=20=20;;=20= Hax!=0A=20=20(let*=20((eof=20(eq=20pos=20(point-max)))=0A=20=20=20=20=20=20= =20=20=20(eol=20(unless=20eof=20(eq=20(char-after=20pos)=20?\n)))=0A=20=20= =20=20=20=20=20=20=20(end=20(if=20eof=20pos=20(1+=20pos)))=0A=20=20=20=20= =20=20=20=20=20(display-string=0A=20=20=20=20=20=20=20=20=20=20(when=20= eof=0A=20=20=20=20=20=20=20=20=20=20=20=20(unless=20(or=20(eq=20(point)=20= (point-max))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20(cl-some=20(lambda=20(ov)=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (and=20(eq=20(overlay-get=20ov=20'category)=20'crdt-pseudo-cursor)=0A=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20(overlay-get=20ov=20= 'before-string)))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(overlays-in=20(point-max)=20= (point-max))))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20(propertize=20= "=20"=20'face=20(overlay-get=20ov=20'face))))))=0A=20=20=20=20= (move-overlay=20ov=20pos=20end)=0A=20=20=20=20(overlay-put=20ov=20= 'before-string=20display-string)))=0A(defun=20crdt--move-region=20(ov=20= pos=20mark)=0A=20=20"Move=20pseudo=20marked=20region=20overlay=20OV=20to=20= mark=20between=20POS=20and=20MARK."=0A=20=20(move-overlay=20ov=20(min=20= pos=20mark)=20(max=20pos=20mark)))=0A=0A=0A;;=20CRDT=20IDs=20are=20= represented=20by=20unibyte=20strings=20(for=20efficient=20comparison)=0A= ;;=20Every=20two=20bytes=20represent=20a=20big=20endian=20encoded=20= integer=0A;;=20For=20base=20IDs,=20last=20two=20bytes=20are=20always=20= representing=20site=20ID=0A;;=20Stored=20strings=20are=20= BASE-ID:OFFSETs.=20So=20the=20last=20two=20bytes=20represent=20offset,=0A= ;;=20and=20second=20last=20two=20bytes=20represent=20site=20ID=0A= (defconst=20crdt--max-value=20(lsh=201=2016))=0A;;=20(defconst=20= crdt--max-value=204)=0A;;=20for=20debug=0A(defconst=20= crdt--low-byte-mask=20255)=0A(defsubst=20crdt--get-two-bytes=20(string=20= index)=0A=20=20"Get=20the=20big-endian=20encoded=20integer=20from=20= STRING=20starting=20from=20INDEX.=0AINDEX=20is=20counted=20by=20bytes."=0A= =20=20(logior=20(lsh=20(elt=20string=20index)=208)=0A=20=20=20=20=20=20=20= =20=20=20(elt=20string=20(1+=20index))))=0A(defsubst=20= crdt--get-two-bytes-with-offset=20(string=20offset=20index=20default)=0A=20= =20"Helper=20function=20for=20CRDT--GENERATE-ID.=0AGet=20the=20= big-endian=20encoded=20integer=20from=20STRING=20starting=20from=20= INDEX,=0Abut=20with=20last=20two-bytes=20of=20STRING=20(the=20offset=20= portion)=20replaced=20by=20OFFSET,=0Aand=20padded=20infintely=20by=20= DEFAULT=20to=20the=20right."=0A=20=20(cond=20((=3D=20index=20(-=20= (string-bytes=20string)=202))=0A=20=20=20=20=20=20=20=20=20offset)=0A=20=20= =20=20=20=20=20=20((<=20(1+=20index)=20(string-bytes=20string))=0A=20=20=20= =20=20=20=20=20=20(logior=20(lsh=20(elt=20string=20index)=208)=0A=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20(elt=20string=20(1+=20= index))))=0A=20=20=20=20=20=20=20=20(t=20default)))=0A=0A(defsubst=20= crdt--id-offset=20(id)=0A=20=20"Get=20the=20literal=20offset=20integer=20= from=20ID.=0ANote=20that=20it=20might=20deviate=20from=20real=20offset=20= for=20a=20character=0Ain=20the=20middle=20of=20a=20block."=0A=20=20= (crdt--get-two-bytes=20id=20(-=20(string-bytes=20id)=202)))=0A(defsubst=20= crdt--set-id-offset=20(id=20offset)=0A=20=20"Set=20the=20OFFSET=20= portion=20of=20ID=20destructively."=0A=20=20(let=20((length=20= (string-bytes=20id)))=0A=20=20=20=20(aset=20id=20(-=20length=202)=20(lsh=20= offset=20-8))=0A=20=20=20=20(aset=20id=20(-=20length=201)=20(logand=20= offset=20crdt--low-byte-mask))))=0A(defsubst=20crdt--id-replace-offset=20= (id=20offset)=0A=20=20"Create=20and=20return=20a=20new=20id=20string=20= by=20replacing=20the=20OFFSET=20portion=20from=20ID."=0A=20=20(let=20= ((new-id=20(substring=20id)))=0A=20=20=20=20(crdt--set-id-offset=20= new-id=20offset)=0A=20=20=20=20new-id))=0A(defsubst=20crdt--id-site=20= (id)=0A=20=20"Get=20the=20site=20id=20from=20ID."=0A=20=20= (crdt--get-two-bytes=20id=20(-=20(string-bytes=20id)=204)))=0A(defsubst=20= crdt--generate-id=20(low-id=20low-offset=20high-id=20high-offset=20= site-id)=0A=20=20"Generate=20a=20new=20ID=20between=20LOW-ID=20and=20= HIGH-ID.=0AThe=20generating=20site=20is=20marked=20as=20SITE-ID.=0A= Offset=20parts=20of=20LOW-ID=20and=20HIGH-ID=20are=20overriden=20by=20= LOW-OFFSET=0Aand=20HIGH-OFFSET.=20=20(to=20save=20two=20copying=20from=20= using=20CRDT--ID-REPLACE-OFFSET)"=0A=20=20(let*=20((l=20= (crdt--get-two-bytes-with-offset=20low-id=20low-offset=200=200))=0A=20=20= =20=20=20=20=20=20=20(h=20(crdt--get-two-bytes-with-offset=20high-id=20= high-offset=200=20crdt--max-value))=0A=20=20=20=20=20=20=20=20=20(bytes=20= (cl-loop=20for=20pos=20from=202=20by=202=0A=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20while=20(<=20(-=20h=20l)=20= 2)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20append=20(list=20(lsh=20l=20-8)=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20(logand=20l=20crdt--low-byte-mask))=0A=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20do=20(setq=20l=20= (crdt--get-two-bytes-with-offset=20low-id=20low-offset=20pos=200))=0A=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20do=20= (setq=20h=20(crdt--get-two-bytes-with-offset=20high-id=20high-offset=20= pos=20crdt--max-value))))=0A=20=20=20=20=20=20=20=20=20(m=20(+=20l=201=20= (random=20(-=20h=20l=201)))))=0A=20=20=20=20(apply=20#'unibyte-string=0A=20= =20=20=20=20=20=20=20=20=20=20(append=20bytes=20(list=20(lsh=20m=20-8)=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20(logand=20m=20crdt--low-byte-mask)=0A=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (lsh=20site-id=20-8)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20(logand=20site-id=20= crdt--low-byte-mask)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=200=0A=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=200)))))=0A= =0A;;=20CRDT-ID=20text=20property=20actually=20stores=20a=20cons=20of=20= (ID-STRING=20.=20END-OF-BLOCK-P)=0A(defsubst=20crdt--get-crdt-id-pair=20= (pos=20&optional=20obj)=0A=20=20"Get=20the=20(CRDT-ID=20.=20= END-OF-BLOCK-P)=20pair=20at=20POS=20in=20OBJ."=0A=20=20= (get-text-property=20pos=20'crdt-id=20obj))=0A(defsubst=20= crdt--get-starting-id=20(pos=20&optional=20obj)=0A=20=20"Get=20the=20= CRDT-ID=20at=20POS=20in=20OBJ."=0A=20=20(car=20(crdt--get-crdt-id-pair=20= pos=20obj)))=0A(defsubst=20crdt--end-of-block-p=20(pos=20&optional=20= obj)=0A=20=20"Get=20the=20END-OF-BLOCK-P=20at=20POS=20in=20OBJ."=0A=20=20= (cdr=20(crdt--get-crdt-id-pair=20pos=20obj)))=0A(defsubst=20= crdt--get-starting-id-maybe=20(pos=20&optional=20obj=20limit)=0A=20=20= "Get=20the=20CRDT-ID=20at=20POS=20in=20OBJ=20if=20POS=20is=20no=20= smaller=20than=20LIMIT.=0AReturn=20NIL=20otherwise."=0A=20=20(unless=20= (<=20pos=20(or=20limit=20(point-min)))=0A=20=20=20=20(car=20= (get-text-property=20pos=20'crdt-id=20obj))))=0A(defsubst=20= crdt--get-id-offset=20(starting-id=20pos=20&optional=20obj=20limit)=0A=20= =20"Get=20the=20real=20offset=20integer=20for=20a=20character=20at=20= POS.=0AAssume=20the=20stored=20literal=20ID=20is=20STARTING-ID."=0A=20=20= (let*=20((start-pos=20(previous-single-property-change=20(1+=20pos)=20= 'crdt-id=20obj=20(or=20limit=20(point-min)))))=0A=20=20=20=20(+=20(-=20= pos=20start-pos)=20(crdt--id-offset=20starting-id))))=0A(defsubst=20= crdt--get-id=20(pos=20&optional=20obj=20limit)=0A=20=20"Get=20the=20real=20= CRDT=20ID=20at=20POS."=0A=20=20(let=20((limit=20(or=20limit=20= (point-min))))=0A=20=20=20=20(if=20(<=20pos=20limit)=20""=0A=20=20=20=20=20= =20(let*=20((starting-id=20(crdt--get-starting-id=20pos=20obj))=0A=20=20=20= =20=20=20=20=20=20=20=20=20=20(left-offset=20(crdt--get-id-offset=20= starting-id=20pos=20obj=20limit)))=0A=20=20=20=20=20=20=20=20= (crdt--id-replace-offset=20starting-id=20left-offset)))))=0A=0A(defsubst=20= crdt--set-id=20(pos=20id=20&optional=20end-of-block-p=20obj=20limit)=0A=20= =20"Set=20the=20crdt=20ID=20and=20END-OF-BLOCK-P=20at=20POS=20in=20OBJ.=0A= Any=20characters=20after=20POS=20but=20before=20LIMIT=20that=20used=20to=0A= have=20the=20same=20(CRDT-ID=20.=20END-OF-BLOCK-P)=20pair=20are=20also=20= updated=0Awith=20ID=20and=20END-OF-BLOCK-P."=0A=20=20(put-text-property=20= pos=20(next-single-property-change=20pos=20'crdt-id=20obj=20(or=20limit=20= (point-max)))=20'crdt-id=20(cons=20id=20end-of-block-p)=20obj))=0A=0A= (cl-defmacro=20crdt--with-insertion-information=0A=20=20=20=20((beg=20= end=20&optional=20beg-obj=20end-obj=20beg-limit=20end-limit)=20&body=20= body)=0A=20=20"Helper=20macro=20to=20setup=20some=20useful=20variables."=0A= =20=20`(let*=20((not-begin=20(>=20,beg=20,(or=20beg-limit=20= '(point-min))))=20;=20if=20it's=20nil,=20we're=20at=20the=20beginning=20= of=20buffer=0A=20=20=20=20=20=20=20=20=20=20(left-pos=20(1-=20,beg))=0A=20= =20=20=20=20=20=20=20=20=20(starting-id-pair=20(when=20not-begin=20= (crdt--get-crdt-id-pair=20left-pos=20,beg-obj)))=0A=20=20=20=20=20=20=20=20= =20=20(starting-id=20(if=20not-begin=20(car=20starting-id-pair)=20""))=0A= =20=20=20=20=20=20=20=20=20=20(left-offset=20(if=20not-begin=20= (crdt--get-id-offset=20starting-id=20left-pos=20,beg-obj=20,beg-limit)=20= 0))=0A=20=20=20=20=20=20=20=20=20=20(not-end=20(<=20,end=20,(or=20= end-limit=20'(point-max))))=0A=20=20=20=20=20=20=20=20=20=20(ending-id=20= (if=20not-end=20(crdt--get-starting-id=20,end=20,end-obj)=20""))=0A=20=20= =20=20=20=20=20=20=20=20(right-offset=20(if=20not-end=20(crdt--id-offset=20= ending-id)=200))=0A=20=20=20=20=20=20=20=20=20=20(beg=20,beg)=0A=20=20=20= =20=20=20=20=20=20=20(end=20,end)=0A=20=20=20=20=20=20=20=20=20=20= (beg-obj=20,beg-obj)=0A=20=20=20=20=20=20=20=20=20=20(end-obj=20= ,end-obj)=0A=20=20=20=20=20=20=20=20=20=20(beg-limit=20,beg-limit)=0A=20=20= =20=20=20=20=20=20=20=20(end-limit=20,end-limit))=0A=20=20=20=20=20= ,@body))=0A(defmacro=20crdt--split-maybe=20()=0A=20=20'(when=20(and=20= not-end=20(eq=20starting-id=20(crdt--get-starting-id=20end=20end-obj)))=0A= =20=20=20=20=20;;=20need=20to=20split=20id=20block=0A=20=20=20=20=20= (crdt--set-id=20end=20(crdt--id-replace-offset=20starting-id=20(1+=20= left-offset))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (crdt--end-of-block-p=20left-pos=20beg-obj)=20end-obj=20end-limit)=0A=20=20= =20=20=20(rplacd=20(get-text-property=20left-pos=20'crdt-id=20beg-obj)=20= nil)=20;;=20clear=20end-of-block=20flag=0A=20=20=20=20=20t))=0A=0A= (defsubst=20crdt--same-base-p=20(a=20b)=0A=20=20(let*=20((a-length=20= (string-bytes=20a))=0A=20=20=20=20=20=20=20=20=20(b-length=20= (string-bytes=20b)))=0A=20=20=20=20(and=20(eq=20a-length=20b-length)=0A=20= =20=20=20=20=20=20=20=20(let=20((base-length=20(-=20a-length=202)))=0A=20= =20=20=20=20=20=20=20=20=20=20(eq=20t=20(compare-strings=20a=200=20= base-length=20b=200=20base-length))))))=0A=0A(defmacro=20= crdt--defvar-permanent-local=20(name=20&optional=20val=20docstring)=0A=20= =20`(progn=0A=20=20=20=20=20(defvar-local=20,name=20,val=20,docstring)=0A= =20=20=20=20=20(put=20',name=20'permanent-local=20t)))=0A= (crdt--defvar-permanent-local=20crdt--local-id=20nil=20"Local=20= site-id.")=0A(crdt--defvar-permanent-local=20crdt--inhibit-update=20nil=20= "When=20set,=20don't=20call=20CRDT--LOCAL-*=20on=20change.=0AThis=20is=20= useful=20for=20functions=20that=20apply=20remote=20change=20to=20local=20= buffer,=0Ato=20avoid=20recusive=20calling=20of=20CRDT=20synchronization=20= functions.")=0A(crdt--defvar-permanent-local=20crdt--changed-string=20= nil)=0A(crdt--defvar-permanent-local=20crdt--last-point=20nil)=0A= (crdt--defvar-permanent-local=20crdt--last-mark=20nil)=0A= (crdt--defvar-permanent-local=20crdt--overlay-table=20nil=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= "A=20hash=20table=20that=20maps=20SITE-ID=20to=20CONSes=20of=20the=20= form=20(CURSOR-OVERLAY=20.=20REGION-OVERLAY).")=0A= (crdt--defvar-permanent-local=20crdt--contact-table=20nil=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= "A=20hash=20table=20that=20maps=20SITE-ID=20to=20LISTs=20of=20the=20form=20= (DISPLAY-NAME=20ADDRESS).")=0A(crdt--defvar-permanent-local=20= crdt--local-name=20nil)=0A(crdt--defvar-permanent-local=20= crdt--user-list-buffer=20nil)=0A=0A(defvar=20crdt-user-list-mode-map=0A=20= =20(let=20((map=20(make-sparse-keymap)))=0A=20=20=20=20(define-key=20map=20= (kbd=20"RET")=20#'crdt-user-list-goto)=0A=20=20=20=20map))=0A= (define-derived-mode=20crdt-user-list-mode=20tabulated-list-mode=0A=20=20= "CRDT=20User=20List"=0A=20=20(setq=20tabulated-list-format=20[("Display=20= name"=2015=20t)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20("Address"=2015=20t)=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20("Port"=207=20t)]))=0A(defun=20crdt-user-list-goto=20()=0A=20=20= (interactive)=0A=20=20(let=20((site-id=20(tabulated-list-get-id)))=0A=20=20= =20=20(switch-to-buffer-other-window=20crdt--user-list-parent)=0A=20=20=20= =20(when=20site-id=0A=20=20=20=20=20=20(goto-char=20(overlay-start=20= (car=20(gethash=20site-id=20crdt--overlay-table)))))))=0A= (crdt--defvar-permanent-local=20crdt--user-list-parent=20nil=20"Set=20to=20= the=20CRDT=20shared=20buffer,=20local=20in=20a=20CRDT-USER-LIST=20= buffer.")=0A(defun=20crdt-list-users=20(&optional=20crdt-buffer=20= display-buffer)=0A=20=20"Display=20a=20list=20of=20active=20users=20= working=20on=20a=20CRDT-shared=20buffer=20CRDT-BUFFER.=0AIf=20= DISPLAY-BUFFER=20is=20provided,=20display=20the=20output=20there.=20= Otherwise=20use=20a=20dedicated=0Abuffer=20for=20displaying=20active=20= users=20on=20CRDT-BUFFER."=0A=20=20(interactive)=0A=20=20= (with-current-buffer=20(or=20crdt-buffer=20(current-buffer))=0A=20=20=20=20= (unless=20crdt-mode=0A=20=20=20=20=20=20(error=20"Not=20a=20CRDT=20= shared=20buffer."))=0A=20=20=20=20(unless=20display-buffer=0A=20=20=20=20= =20=20(unless=20(and=20crdt--user-list-buffer=20(buffer-live-p=20= crdt--user-list-buffer))=0A=20=20=20=20=20=20=20=20(let=20((crdt-buffer=20= (current-buffer)))=0A=20=20=20=20=20=20=20=20=20=20(setq=20= crdt--user-list-buffer=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (generate-new-buffer=20(concat=20(buffer-name=20(current-buffer))=0A=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20"=20users")))=0A=20= =20=20=20=20=20=20=20=20=20(with-current-buffer=20crdt--user-list-buffer=0A= =20=20=20=20=20=20=20=20=20=20=20=20(setq=20crdt--user-list-parent=20= crdt-buffer))))=0A=20=20=20=20=20=20(setq=20display-buffer=20= crdt--user-list-buffer))=0A=20=20=20=20(crdt-refresh-users=20= display-buffer)=0A=20=20=20=20(switch-to-buffer-other-window=20= display-buffer)))=0A(defun=20crdt-refresh-users=20(display-buffer)=0A=20=20= (let=20((table=20crdt--contact-table)=0A=20=20=20=20=20=20=20=20= (local-name=20crdt--local-name))=0A=20=20=20=20(with-current-buffer=20= display-buffer=0A=20=20=20=20=20=20(crdt-user-list-mode)=0A=20=20=20=20=20= =20(setq=20tabulated-list-entries=20nil)=0A=20=20=20=20=20=20(push=20= (list=20crdt--local-id=20(vector=20local-name=20"*myself*"=20"--"))=20= tabulated-list-entries)=0A=20=20=20=20=20=20(maphash=20(lambda=20(k=20v)=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(push=20(list=20k=20= (cl-destructuring-bind=20(name=20contact)=20v=0A=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (let=20((colored-name=20(concat=20name=20"=20")))=0A=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20(put-text-property=200=20(1-=20(length=20colored-name))=0A=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20'face=20`(:background=20,(crdt--get-region-color=20k))=0A=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= colored-name)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(put-text-property=20(1-=20= (length=20colored-name))=20(length=20colored-name)=0A=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20'face=20= `(:background=20,(crdt--get-cursor-color=20k))=0A=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= colored-name)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(vector=20colored-name=20= (car=20contact)=20(format=20"%s"=20(cadr=20contact))))))=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= tabulated-list-entries))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= table)=0A=20=20=20=20=20=20(tabulated-list-init-header)=0A=20=20=20=20=20= =20(tabulated-list-print))))=0A(defsubst=20crdt--refresh-users-maybe=20= ()=0A=20=20(when=20(and=20crdt--user-list-buffer=20(buffer-live-p=20= crdt--user-list-buffer))=0A=20=20=20=20(crdt-refresh-users=20= crdt--user-list-buffer)))=0A=0A(defun=20crdt--local-insert=20(beg=20end)=0A= =20=20"To=20be=20called=20after=20a=20local=20insert=20happened=20in=20= current=20buffer=20from=20BEG=20to=20END.=0AReturns=20a=20list=20of=20= (insert=20type)=20messages=20to=20be=20sent."=0A=20=20(let=20= (resulting-commands)=0A=20=20=20=20(crdt--with-insertion-information=0A=20= =20=20=20=20(beg=20end)=0A=20=20=20=20=20(unless=20(crdt--split-maybe)=0A= =20=20=20=20=20=20=20(when=20(and=20not-begin=0A=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20(eq=20(crdt--id-site=20starting-id)=20= crdt--local-id)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (crdt--end-of-block-p=20left-pos))=0A=20=20=20=20=20=20=20=20=20;;=20= merge=20crdt=20id=20block=0A=20=20=20=20=20=20=20=20=20(let*=20= ((max-offset=20crdt--max-value)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20(merge-end=20(min=20end=20(+=20(-=20max-offset=20left-offset=20= 1)=20beg))))=0A=20=20=20=20=20=20=20=20=20=20=20(unless=20(=3D=20= merge-end=20beg)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= (put-text-property=20beg=20merge-end=20'crdt-id=20starting-id-pair)=0A=20= =20=20=20=20=20=20=20=20=20=20=20=20(let=20((virtual-id=20(substring=20= starting-id)))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (crdt--set-id-offset=20virtual-id=20(1+=20left-offset))=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20(push=20`(insert=20,(base64-encode-string=20= virtual-id)=20,beg=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20,(buffer-substring-no-properties=20= beg=20merge-end))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20resulting-commands))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= (setq=20beg=20merge-end)))))=0A=20=20=20=20=20(while=20(<=20beg=20end)=0A= =20=20=20=20=20=20=20(let=20((block-end=20(min=20end=20(+=20= crdt--max-value=20beg))))=0A=20=20=20=20=20=20=20=20=20(let=20((new-id=20= (crdt--generate-id=20starting-id=20left-offset=20ending-id=20= right-offset=20crdt--local-id)))=0A=20=20=20=20=20=20=20=20=20=20=20= (put-text-property=20beg=20block-end=20'crdt-id=20(cons=20new-id=20t))=0A= =20=20=20=20=20=20=20=20=20=20=20(push=20`(insert=20= ,(base64-encode-string=20new-id)=20,beg=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= ,(buffer-substring-no-properties=20beg=20block-end))=0A=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20resulting-commands)=0A=20=20=20=20=20=20= =20=20=20=20=20(setq=20beg=20block-end)=0A=20=20=20=20=20=20=20=20=20=20=20= (setq=20left-offset=20(1-=20crdt--max-value))=20;=20this=20is=20always=20= true=20when=20we=20need=20to=20continue=0A=20=20=20=20=20=20=20=20=20=20=20= (setq=20starting-id=20new-id)))))=0A=20=20=20=20;;=20= (crdt--verify-buffer)=0A=20=20=20=20(nreverse=20resulting-commands)))=0A=0A= (defun=20crdt--find-id=20(id=20pos)=0A=20=20"Find=20the=20first=20= position=20*after*=20ID.=20=20Start=20the=20search=20from=20POS."=0A=20=20= (let*=20((left-pos=20(previous-single-property-change=20(if=20(<=20pos=20= (point-max))=20(1+=20pos)=20pos)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20'crdt-id=20nil=20(point-min)))=0A= =20=20=20=20=20=20=20=20=20(left-id=20(crdt--get-starting-id=20= left-pos))=0A=20=20=20=20=20=20=20=20=20(right-pos=20= (next-single-property-change=20pos=20'crdt-id=20nil=20(point-max)))=0A=20= =20=20=20=20=20=20=20=20(right-id=20(crdt--get-starting-id=20= right-pos)))=0A=20=20=20=20(cl-block=20nil=0A=20=20=20=20=20=20(while=20= t=0A=20=20=20=20=20=20=20=20(cond=20((<=3D=20right-pos=20(point-min))=0A=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20(cl-return=20(point-min)))=0A=20= =20=20=20=20=20=20=20=20=20=20=20=20=20((>=3D=20left-pos=20(point-max))=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(cl-return=20(point-max)))=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20((and=20right-id=20(not=20= (string<=20id=20right-id)))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (setq=20left-pos=20right-pos)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20(setq=20left-id=20right-id)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20(setq=20right-pos=20(next-single-property-change=20right-pos=20= 'crdt-id=20nil=20(point-max)))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20(setq=20right-id=20(crdt--get-starting-id=20right-pos)))=0A=20=20=20=20= =20=20=20=20=20=20=20=20=20=20((string<=20id=20left-id)=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20(setq=20right-pos=20left-pos)=0A=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20(setq=20right-id=20left-id)=0A=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20(setq=20left-pos=20= (previous-single-property-change=20left-pos=20'crdt-id=20nil=20= (point-min)))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(setq=20= left-id=20(crdt--get-starting-id=20left-pos)))=0A=20=20=20=20=20=20=20=20= =20=20=20=20=20=20(t=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20;;=20= will=20unibyte=20to=20multibyte=20conversion=20cause=20any=20problem?=0A=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20(cl-return=0A=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20(if=20(eq=20t=20(compare-strings=20left-id=20= 0=20(-=20(string-bytes=20left-id)=202)=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20id=200=20(-=20(string-bytes=20left-id)=202)))=0A=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(min=20right-pos=20= (+=20left-pos=201=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(-=20= (crdt--get-two-bytes=20id=20(-=20(string-bytes=20left-id)=202))=0A=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20(crdt--id-offset=20left-id))))=0A=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20right-pos))))))))=0A= (defun=20crdt--remote-insert=20(message)=0A=20=20(let=20= ((crdt--inhibit-update=20t))=0A=20=20=20=20(cl-destructuring-bind=20= (id-base64=20position-hint=20content)=20message=0A=20=20=20=20=20=20= (let*=20((id=20(base64-decode-string=20id-base64))=0A=20=20=20=20=20=20=20= =20=20=20=20=20=20(beg=20(crdt--find-id=20id=20position-hint))=20end)=0A=20= =20=20=20=20=20=20=20(goto-char=20beg)=0A=20=20=20=20=20=20=20=20(insert=20= content)=0A=20=20=20=20=20=20=20=20(setq=20end=20(point))=0A=20=20=20=20=20= =20=20=20(with-silent-modifications=0A=20=20=20=20=20=20=20=20=20=20= (crdt--with-insertion-information=0A=20=20=20=20=20=20=20=20=20=20=20= (beg=20end)=0A=20=20=20=20=20=20=20=20=20=20=20(let=20((base-length=20(-=20= (string-bytes=20starting-id)=202)))=0A=20=20=20=20=20=20=20=20=20=20=20=20= =20(if=20(and=20(eq=20(string-bytes=20id)=20(string-bytes=20= starting-id))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20(eq=20t=20(compare-strings=20starting-id=200=20base-length=0A=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20id=200=20= base-length))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20(eq=20(1+=20left-offset)=20(crdt--id-offset=20id)))=0A=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20(put-text-property=20beg=20end=20= 'crdt-id=20starting-id-pair)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20(put-text-property=20beg=20end=20'crdt-id=20(cons=20id=20t))))=0A=20=20= =20=20=20=20=20=20=20=20=20(crdt--split-maybe))))))=0A=20=20;;=20= (crdt--verify-buffer)=0A=20=20)=0A=0A(defun=20crdt--local-delete=20(beg=20= end)=0A=20=20(let=20((outer-end=20end))=0A=20=20=20=20= (crdt--with-insertion-information=0A=20=20=20=20=20(beg=200=20nil=20= crdt--changed-string=20nil=20(length=20crdt--changed-string))=0A=20=20=20= =20=20(if=20(crdt--split-maybe)=0A=20=20=20=20=20=20=20=20=20(let*=20= ((not-end=20(<=20outer-end=20(point-max)))=0A=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20(ending-id=20(when=20not-end=20(crdt--get-starting-id=20= outer-end))))=0A=20=20=20=20=20=20=20=20=20=20=20(when=20(and=20not-end=20= (eq=20starting-id=20(crdt--get-starting-id=20outer-end)))=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20(crdt--set-id=20outer-end=20= (crdt--id-replace-offset=20starting-id=20(+=201=20left-offset=20(length=20= crdt--changed-string))))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20t))=0A=20= =20=20=20=20=20=20(crdt--with-insertion-information=0A=20=20=20=20=20=20=20= =20((length=20crdt--changed-string)=20outer-end=20crdt--changed-string=20= nil=200=20nil)=0A=20=20=20=20=20=20=20=20(crdt--split-maybe)))))=0A=20=20= ;;=20(crdt--verify-buffer)=0A=20=20`(delete=20,beg=20,@=20= (crdt--dump-ids=200=20(length=20crdt--changed-string)=20= crdt--changed-string=20t)))=0A(defun=20crdt--remote-delete=20(message)=0A= =20=20(cl-destructuring-bind=20(position-hint=20.=20id-pairs)=20message=0A= =20=20=20=20(dolist=20(id-pair=20id-pairs)=0A=20=20=20=20=20=20= (cl-destructuring-bind=20(length=20.=20id-base64)=20id-pair=0A=20=20=20=20= =20=20=20=20(let=20((id=20(base64-decode-string=20id-base64)))=0A=20=20=20= =20=20=20=20=20=20=20(while=20(>=20length=200)=0A=20=20=20=20=20=20=20=20= =20=20=20=20(goto-char=20(1-=20(crdt--find-id=20id=20position-hint)))=0A=20= =20=20=20=20=20=20=20=20=20=20=20(let*=20((end-of-block=20= (next-single-property-change=20(point)=20'crdt-id=20nil=20(point-max)))=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(block-length=20= (-=20end-of-block=20(point))))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (cl-case=20(cl-signum=20(-=20length=20block-length))=0A=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20((1)=20(delete-char=20block-length)=0A=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(cl-decf=20length=20= block-length)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (crdt--set-id-offset=20id=20(+=20(crdt--id-offset=20id)=20= block-length)))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20((0)=20= (delete-char=20length)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20(setq=20length=200))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= ((-1)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(let*=20= ((starting-id=20(crdt--get-starting-id=20(point)))=0A=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(left-offset=20= (crdt--get-id-offset=20starting-id=20(point))))=0A=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20(delete-char=20length)=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20(crdt--set-id=20(point)=20= (crdt--id-replace-offset=20starting-id=20(+=20left-offset=20length))))=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(setq=20length=20= 0))))))=0A=20=20=20=20=20=20=20=20;;=20(crdt--verify-buffer)=0A=20=20=20=20= =20=20=20=20))))=0A=0A(defun=20crdt--before-change=20(beg=20end)=0A=20=20= (unless=20crdt--inhibit-update=0A=20=20=20=20(setq=20= crdt--changed-string=20(buffer-substring=20beg=20end))))=0A=0A(defun=20= crdt--after-change=20(beg=20end=20length)=0A=20=20(mapc=20(lambda=20(ov)=0A= =20=20=20=20=20=20=20=20=20=20(when=20(eq=20(overlay-get=20ov=20= 'category)=20'crdt-pseudo-cursor)=0A=20=20=20=20=20=20=20=20=20=20=20=20= (crdt--move-cursor=20ov=20beg)))=0A=20=20=20=20=20=20=20=20(overlays-in=20= beg=20(min=20(point-max)=20(1+=20beg))))=0A=20=20(when=20crdt--local-id=20= ;=20CRDT--LOCAL-ID=20is=20NIL=20when=20a=20client=20haven't=20received=20= the=20first=20sync=20message=0A=20=20=20=20(unless=20= crdt--inhibit-update=0A=20=20=20=20=20=20(let=20((crdt--inhibit-update=20= t))=0A=20=20=20=20=20=20=20=20;;=20we're=20only=20interested=20in=20text=20= change=0A=20=20=20=20=20=20=20=20;;=20ignore=20property=20only=20changes=0A= =20=20=20=20=20=20=20=20(save-excursion=0A=20=20=20=20=20=20=20=20=20=20= (goto-char=20beg)=0A=20=20=20=20=20=20=20=20=20=20(unless=20(and=20(=3D=20= length=20(-=20end=20beg))=20(looking-at=20(regexp-quote=20= crdt--changed-string)))=0A=20=20=20=20=20=20=20=20=20=20=20=20(widen)=0A=20= =20=20=20=20=20=20=20=20=20=20=20(with-silent-modifications=0A=20=20=20=20= =20=20=20=20=20=20=20=20=20=20(unless=20(=3D=20length=200)=0A=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20(crdt--broadcast-maybe=0A=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20(format=20"%S"=20= (crdt--local-delete=20beg=20end))))=0A=20=20=20=20=20=20=20=20=20=20=20=20= =20=20(unless=20(=3D=20beg=20end)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20(dolist=20(message=20(crdt--local-insert=20beg=20end))=0A=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(crdt--broadcast-maybe=0A=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(format=20"%S"=20= message)))))))))))=0A=0A(defun=20crdt--remote-cursor=20(message)=0A=20=20= (cl-destructuring-bind=0A=20=20=20=20=20=20(site-id=20= point-position-hint=20point-crdt-id-base64=20mark-position-hint=20= mark-crdt-id-base64)=20message=0A=20=20=20=20(let=20((ov-pair=20(gethash=20= site-id=20crdt--overlay-table)))=0A=20=20=20=20=20=20(if=20= point-crdt-id-base64=0A=20=20=20=20=20=20=20=20=20=20(let*=20((point=20= (crdt--find-id=20(base64-decode-string=20point-crdt-id-base64)=20= point-position-hint))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (mark=20(if=20mark-crdt-id-base64=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20(crdt--find-id=20= (base64-decode-string=20mark-crdt-id-base64)=20mark-position-hint)=0A=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= point)))=0A=20=20=20=20=20=20=20=20=20=20=20=20(unless=20ov-pair=0A=20=20= =20=20=20=20=20=20=20=20=20=20=20=20(let=20((new-cursor=20(make-overlay=20= 1=201))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (new-region=20(make-overlay=201=201)))=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20(overlay-put=20new-cursor=20'face=20`(:background=20= ,(crdt--get-cursor-color=20site-id)))=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20(overlay-put=20new-cursor=20'category=20= 'crdt-pseudo-cursor)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (overlay-put=20new-region=20'face=20`(:background=20= ,(crdt--get-region-color=20site-id)=20:extend=20t))=0A=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20(setq=20ov-pair=20(puthash=20site-id=20(cons=20= new-cursor=20new-region)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= crdt--overlay-table))))=0A=20=20=20=20=20=20=20=20=20=20=20=20= (crdt--move-cursor=20(car=20ov-pair)=20point)=0A=20=20=20=20=20=20=20=20=20= =20=20=20(crdt--move-region=20(cdr=20ov-pair)=20point=20mark))=0A=20=20=20= =20=20=20=20=20(when=20ov-pair=0A=20=20=20=20=20=20=20=20=20=20(remhash=20= site-id=20crdt--overlay-table)=0A=20=20=20=20=20=20=20=20=20=20= (delete-overlay=20(car=20ov-pair))=0A=20=20=20=20=20=20=20=20=20=20= (delete-overlay=20(cdr=20ov-pair)))))))=0A=0A(cl-defun=20= crdt--local-cursor=20(&optional=20(lazy=20t))=0A=20=20(let=20((point=20= (point))=0A=20=20=20=20=20=20=20=20(mark=20(when=20(use-region-p)=20= (mark))))=0A=20=20=20=20(unless=20(and=20lazy=0A=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20(eq=20point=20crdt--last-point)=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20(eq=20mark=20crdt--last-mark))=0A=20=20= =20=20=20=20(when=20(or=20(eq=20point=20(point-max))=20(eq=20= crdt--last-point=20(point-max)))=0A=20=20=20=20=20=20=20=20(mapc=20= (lambda=20(ov)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(when=20= (eq=20(overlay-get=20ov=20'category)=20'crdt-pseudo-cursor)=0A=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20(crdt--move-cursor=20ov=20= (point-max))))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20(overlays-in=20= (point-max)=20(point-max))))=0A=20=20=20=20=20=20(setq=20= crdt--last-point=20point)=0A=20=20=20=20=20=20(setq=20crdt--last-mark=20= mark)=0A=20=20=20=20=20=20(let=20((point-id-base64=20= (base64-encode-string=20(crdt--get-id=20(1-=20point))))=0A=20=20=20=20=20= =20=20=20=20=20=20=20(mark-id-base64=20(when=20mark=20= (base64-encode-string=20(crdt--get-id=20(1-=20mark))))))=0A=20=20=20=20=20= =20=20=20`(cursor=20,crdt--local-id=0A=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20,point=20,point-id-base64=20,mark=20,mark-id-base64)))))=0A= (defun=20crdt--post-command=20()=0A=20=20(let=20((cursor-message=20= (crdt--local-cursor)))=0A=20=20=20=20(when=20cursor-message=0A=20=20=20=20= =20=20(crdt--broadcast-maybe=20(format=20"%S"=20cursor-message)))))=0A=0A= =0A(defun=20crdt--dump-ids=20(beg=20end=20object=20&optional=20= omit-end-of-block-p)=0A=20=20"Serialize=20all=20CRDT=20ids=20in=20OBJECT=20= from=20BEG=20to=20END=20into=20a=20list.=0AThe=20list=20contains=20= CONSes=20of=20the=20form=20(LENGTH=20CRDT-ID-BASE64=20.=20= END-OF-BLOCK-P),=0Aor=20(LENGTH=20.=20CRDT-ID-BASE64)=20if=20= OMIT-END-OF-BLOCK-P=20is=20non-NIL.=0Ain=20the=20order=20that=20they=20= appears=20in=20the=20document"=0A=20=20(let=20(ids=20(pos=20end))=0A=20=20= =20=20(while=20(>=20pos=20beg)=0A=20=20=20=20=20=20(let=20((prev-pos=20= (previous-single-property-change=20pos=20'crdt-id=20object=20beg)))=0A=20= =20=20=20=20=20=20=20(push=20(cons=20(-=20pos=20prev-pos)=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(cl-destructuring-bind=20= (id=20.=20eob)=20(crdt--get-crdt-id-pair=20prev-pos=20object)=0A=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(let=20= ((id-base64=20(base64-encode-string=20id)))=0A=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(if=20omit-end-of-block-p=20= id-base64=20(cons=20id-base64=20eob)))))=0A=20=20=20=20=20=20=20=20=20=20= =20=20=20=20ids)=0A=20=20=20=20=20=20=20=20(setq=20pos=20prev-pos)))=0A=20= =20=20=20ids))=0A(defun=20crdt--load-ids=20(ids)=0A=20=20"Load=20the=20= CRDT=20ids=20in=20IDS=20(generated=20by=20CRDT--DUMP-IDS)=0Ainto=20= current=20buffer."=0A=20=20(let=20((pos=20(point-min)))=0A=20=20=20=20= (dolist=20(id-pair=20ids)=0A=20=20=20=20=20=20(let=20((next-pos=20(+=20= pos=20(car=20id-pair))))=0A=20=20=20=20=20=20=20=20(put-text-property=20= pos=20next-pos=20'crdt-id=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20(cons=20(base64-decode-string=20= (cadr=20id-pair))=20(cddr=20id-pair)))=0A=20=20=20=20=20=20=20=20(setq=20= pos=20next-pos)))))=0A(defun=20crdt--verify-buffer=20()=0A=20=20"Debug=20= helper=20function.=0AVerify=20that=20CRDT=20IDs=20in=20a=20document=20= follows=20ascending=20order."=0A=20=20(let*=20((pos=20(point-min))=0A=20=20= =20=20=20=20=20=20=20(id=20(crdt--get-starting-id=20pos)))=0A=20=20=20=20= (cl-block=0A=20=20=20=20=20=20=20=20(while=20t=0A=20=20=20=20=20=20=20=20= =20=20(let*=20((next-pos=20(next-single-property-change=20pos=20= 'crdt-id))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(next-id=20= (if=20(<=20next-pos=20(point-max))=0A=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (crdt--get-starting-id=20next-pos)=0A=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(cl-return)))=0A=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20(prev-id=20(substring=20id)))=0A= =20=20=20=20=20=20=20=20=20=20=20=20(crdt--set-id-offset=20id=20(+=20(-=20= next-pos=20pos)=20(crdt--id-offset=20id)))=0A=20=20=20=20=20=20=20=20=20=20= =20=20(unless=20(string<=20prev-id=20next-id)=0A=20=20=20=20=20=20=20=20=20= =20=20=20=20=20(error=20"Not=20monotonic!"))=0A=20=20=20=20=20=20=20=20=20= =20=20=20(setq=20pos=20next-pos)=0A=20=20=20=20=20=20=20=20=20=20=20=20= (setq=20id=20next-id))))))=0A=0A(crdt--defvar-permanent-local=20= crdt--network-process=20nil)=0A(crdt--defvar-permanent-local=20= crdt--network-clients=20nil)=0A(crdt--defvar-permanent-local=20= crdt--next-client-id=20nil)=0A(cl-defun=20crdt--broadcast-maybe=20= (message-string=20&optional=20(without=20t))=0A=20=20"Broadcast=20or=20= send=20MESSAGE-STRING.=0AIf=20CRDT--NETWORK-PROCESS=20is=20a=20server=20= process,=20broadcast=20MESSAGE-STRING=0Ato=20clients=20except=20the=20= one=20of=20which=20CLIENT-ID=20property=20is=20EQ=20to=20WITHOUT.=0AIf=20= CRDT--NETWORK-PROCESS=20is=20a=20server=20process,=20send=20= MESSAGE-STRING=0Ato=20server=20unless=20WITHOUT=20is=20NIL."=0A=20=20;;=20= (message=20message-string)=0A=20=20(if=20(process-contact=20= crdt--network-process=20:server)=0A=20=20=20=20=20=20(dolist=20(client=20= crdt--network-clients)=0A=20=20=20=20=20=20=20=20(when=20(and=20(eq=20= (process-status=20client)=20'open)=0A=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20(not=20(eq=20(process-get=20client=20'client-id)=20= without)))=0A=20=20=20=20=20=20=20=20=20=20(process-send-string=20client=20= message-string)=0A=20=20=20=20=20=20=20=20=20=20;;=20(run-at-time=201=20= nil=20#'process-send-string=20client=20message-string)=0A=20=20=20=20=20=20= =20=20=20=20;;=20^=20quick=20dirty=20way=20to=20simulate=20network=20= latency,=20for=20debugging=0A=20=20=20=20=20=20=20=20=20=20))=0A=20=20=20= =20(when=20without=0A=20=20=20=20=20=20(process-send-string=20= crdt--network-process=20message-string)=0A=20=20=20=20=20=20;;=20= (run-at-time=201=20nil=20#'process-send-string=20crdt--network-process=20= message-string)=0A=20=20=20=20=20=20)))=0A(defun=20= crdt--generate-challenge=20()=0A=20=20(apply=20#'unibyte-string=20= (cl-loop=20for=20i=20below=2032=20collect=20(random=20256))))=0A(defun=20= crdt--greet-client=20(process)=0A=20=20(cl-pushnew=20process=20= crdt--network-clients)=0A=20=20(let=20((client-id=20(process-get=20= process=20'client-id)))=0A=20=20=20=20(unless=20client-id=0A=20=20=20=20=20= =20(unless=20(<=20crdt--next-client-id=20crdt--max-value)=0A=20=20=20=20=20= =20=20=20(error=20"Used=20up=20client=20IDs.=20Need=20to=20implement=20= allocation=20algorithm."))=0A=20=20=20=20=20=20(process-put=20process=20= 'client-id=20crdt--next-client-id)=0A=20=20=20=20=20=20(setq=20client-id=20= crdt--next-client-id)=0A=20=20=20=20=20=20(cl-incf=20= crdt--next-client-id))=0A=20=20=20=20(process-send-string=20process=20= (format=20"%S"=20`(sync=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20,client-id=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20,major-mode=0A=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20,(buffer-substring-no-properties=20= (point-min)=20(point-max))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20,@=20(crdt--dump-ids=20(point-min)=20(point-max)=20= nil))))=0A=20=20=20=20(maphash=20(lambda=20(site-id=20ov-pair)=0A=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20(cl-destructuring-bind=20(cursor-ov=20= .=20region-ov)=20ov-pair=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20(let*=20((point=20(overlay-start=20cursor-ov))=0A=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(region-beg=20= (overlay-start=20region-ov))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20(region-end=20(overlay-end=20region-ov))=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (mark=20(if=20(eq=20point=20region-beg)=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (unless=20(eq=20point=20region-end)=20region-end)=0A=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= region-beg))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20(point-id-base64=20(base64-encode-string=20(crdt--get-id=20= (1-=20point))))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20(mark-id-base64=20(when=20mark=20(base64-encode-string=20= (crdt--get-id=20(1-=20mark))))))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20(process-send-string=20process=0A=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20(format=20"%S"=0A=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20`(cursor=20,site-id=0A=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20,point=20,point-id-base64=20,mark=20,mark-id-base64))))))=0A=20=20=20=20= =20=20=20=20=20=20=20=20=20crdt--overlay-table)=0A=20=20=20=20= (process-send-string=20process=20(format=20"%S"=20(crdt--local-cursor=20= nil)))=0A=20=20=20=20(maphash=20(lambda=20(k=20v)=0A=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20(process-send-string=20process=20(format=20"%S"=20= `(contact=20,k=20,@v))))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= crdt--contact-table)=0A=20=20=20=20(process-send-string=20process=0A=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (format=20"%S"=20`(contact=20,crdt--local-id=0A=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20,crdt--local-name=20nil)))=0A=20= =20=20=20(let=20((contact-message=20`(contact=20,client-id=20= ,(process-get=20process=20'client-name)=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20,(process-contact=20process))))=0A=20=20=20=20=20=20= (crdt--broadcast-maybe=20(format=20"%S"=20contact-message)=20client-id)=0A= =20=20=20=20=20=20(crdt-process-message=20contact-message=20nil))))=0A=0A= (cl-defgeneric=20crdt-process-message=20(message=20process))=0A= (cl-defmethod=20crdt-process-message=20((message=20(head=20insert))=20= process)=0A=20=20(crdt--remote-insert=20(cdr=20message))=0A=20=20= (crdt--broadcast-maybe=20(format=20"%S"=20message)=20(process-get=20= process=20'client-id)))=0A(cl-defmethod=20crdt-process-message=20= ((message=20(head=20delete))=20process)=0A=20=20(crdt--remote-delete=20= (cdr=20message))=0A=20=20(crdt--broadcast-maybe=20(format=20"%S"=20= message)=20(process-get=20process=20'client-id)))=0A(cl-defmethod=20= crdt-process-message=20((message=20(head=20cursor))=20process)=0A=20=20= (crdt--remote-cursor=20(cdr=20message))=0A=20=20(crdt--broadcast-maybe=20= (format=20"%S"=20message)=20(process-get=20process=20'client-id)))=0A= (cl-defmethod=20crdt-process-message=20((message=20(head=20sync))=20= process)=0A=20=20(unless=20(crdt--server-p)=20;=20server=20shouldn't=20= receive=20this=0A=20=20=20=20(erase-buffer)=0A=20=20=20=20= (cl-destructuring-bind=20(id=20mode=20content=20.=20ids)=20(cdr=20= message)=0A=20=20=20=20=20=20(if=20(fboundp=20mode)=0A=20=20=20=20=20=20=20= =20=20=20(unless=20(eq=20major-mode=20mode)=0A=20=20=20=20=20=20=20=20=20= =20=20=20(funcall=20mode)=20;=20trust=20your=20server...=0A=20=20=20=20=20= =20=20=20=20=20=20=20(crdt-mode))=0A=20=20=20=20=20=20=20=20(message=20= "Server=20uses=20%s,=20but=20not=20available=20locally."=20mode))=0A=20=20= =20=20=20=20(insert=20content)=0A=20=20=20=20=20=20(setq=20= crdt--local-id=20id)=0A=20=20=20=20=20=20(crdt--load-ids=20ids)=0A=20=20=20= =20=20=20(puthash=200=20(list=20nil=20(process-contact=20process))=20= crdt--contact-table))))=0A(cl-defmethod=20crdt-process-message=20= ((message=20(head=20challenge))=20process)=0A=20=20(unless=20= (crdt--server-p)=20;=20server=20shouldn't=20receive=20this=0A=20=20=20=20= (message=20nil)=0A=20=20=20=20(let=20((password=20(read-passwd=0A=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(format=20= "Password=20for=20%s:%s:=20"=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(process-contact=20= crdt--network-process=20:host)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(process-contact=20= crdt--network-process=20:service)))))=0A=20=20=20=20=20=20= (crdt--broadcast-maybe=20(format=20"%S"=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20`(hello=20nil=20,(gnutls-hash-mac=20'SHA1=20password=20(cadr=20= message))))))))=0A(cl-defmethod=20crdt-process-message=20((message=20= (head=20contact))=20process)=0A=20=20(cl-destructuring-bind=20(site-id=20= display-name=20address)=20(cdr=20message)=0A=20=20=20=20(if=20= display-name=0A=20=20=20=20=20=20=20=20(puthash=20site-id=20(list=20= display-name=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20(or=20address=20(cadr=20(gethash=20= site-id=20crdt--contact-table))))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20crdt--contact-table)=0A=20=20=20=20=20=20(remhash=20site-id=20= crdt--contact-table))=0A=20=20=20=20(crdt--refresh-users-maybe)))=0A=0A= (defsubst=20crdt--server-p=20()=0A=20=20(process-contact=20= crdt--network-process=20:server))=0A=0A(defun=20crdt--network-filter=20= (process=20string)=0A=20=20(unless=20(process-buffer=20process)=0A=20=20=20= =20(set-process-buffer=20process=20(generate-new-buffer=20= "*crdt-server*"))=0A=20=20=20=20(set-marker=20(process-mark=20process)=20= 1))=0A=20=20(when=20(buffer-live-p=20(process-buffer=20process))=0A=20=20= =20=20(with-current-buffer=20(process-buffer=20process)=0A=20=20=20=20=20= =20(save-excursion=0A=20=20=20=20=20=20=20=20(goto-char=20(process-mark=20= process))=0A=20=20=20=20=20=20=20=20(insert=20string)=0A=20=20=20=20=20=20= =20=20(set-marker=20(process-mark=20process)=20(point))=0A=20=20=20=20=20= =20=20=20(goto-char=20(point-min))=0A=20=20=20=20=20=20=20=20(let=20= (message)=0A=20=20=20=20=20=20=20=20=20=20(while=20(setq=20message=20= (ignore-errors=20(read=20(current-buffer))))=0A=20=20=20=20=20=20=20=20=20= =20=20=20;;=20(print=20message)=0A=20=20=20=20=20=20=20=20=20=20=20=20= (with-current-buffer=20(process-get=20process=20'crdt-buffer)=0A=20=20=20= =20=20=20=20=20=20=20=20=20=20=20(save-excursion=0A=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20(widen)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20(if=20(or=20(not=20(crdt--server-p))=20(process-get=20process=20= 'authenticated))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20(let=20((crdt--inhibit-update=20t))=0A=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20(crdt-process-message=20message=20= process))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (cl-block=20nil=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20(when=20(eq=20(car=20message)=20'hello)=0A=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20(cl-destructuring-bind=20(name=20= &optional=20response)=20(cdr=20message)=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20(when=20(or=20(not=20(process-get=20= process=20'password))=20;=20server=20password=20is=20empty=0A=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20(and=20response=20(string-equal=20response=20(process-get=20= process=20'challenge))))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20(process-put=20process=20'authenticated=20= t)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20(process-put=20process=20'client-name=20name)=0A=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (crdt--greet-client=20process)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20(cl-return))))=0A=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20(let=20((challenge=20= (crdt--generate-challenge)))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20(process-put=20process=20'challenge=0A=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20(gnutls-hash-mac=20'SHA1=20(substring=20(process-get=20= process=20'password))=20challenge))=0A=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20(process-send-string=20process=20(format=20= "%S"=20`(challenge=20,challenge))))))))=0A=20=20=20=20=20=20=20=20=20=20=20= =20(delete-region=20(point-min)=20(point))=0A=20=20=20=20=20=20=20=20=20=20= =20=20(goto-char=20(point-min))))))))=0A(defun=20= crdt--server-process-sentinel=20(client=20message)=0A=20=20= (with-current-buffer=20(process-get=20client=20'crdt-buffer)=0A=20=20=20=20= (unless=20(eq=20(process-status=20client)=20'open)=0A=20=20=20=20=20=20= ;;=20client=20disconnected=0A=20=20=20=20=20=20(setq=20= crdt--network-clients=20(delete=20client=20crdt--network-clients))=0A=20=20= =20=20=20=20;;=20generate=20a=20clear=20cursor=20message=20and=20a=20= clear=20contact=20message=0A=20=20=20=20=20=20(let*=20((client-id=20= (process-get=20client=20'client-id))=0A=20=20=20=20=20=20=20=20=20=20=20=20= =20(clear-cursor-message=20`(cursor=20,client-id=201=20nil=201=20nil))=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20(clear-contact-message=20= `(contact=20,client-id=20nil=20nil)))=0A=20=20=20=20=20=20=20=20= (crdt-process-message=20clear-cursor-message=20client)=0A=20=20=20=20=20=20= =20=20(crdt-process-message=20clear-contact-message=20client)=0A=20=20=20= =20=20=20=20=20(crdt-refresh-users-maybe)))))=0A(defun=20= crdt--client-process-sentinel=20(process=20message)=0A=20=20= (with-current-buffer=20(process-get=20process=20'crdt-buffer)=0A=20=20=20= =20(unless=20(eq=20(process-status=20process)=20'open)=0A=20=20=20=20=20=20= (crdt-stop-client))))=0A(defun=20crdt-serve-buffer=20(port=20&optional=20= password=20name)=0A=20=20"Share=20the=20current=20buffer=20on=20PORT."=0A= =20=20(interactive=20"nPort:=20")=0A=20=20(crdt-mode)=0A=20=20(setq=20= crdt--local-id=200)=0A=20=20(setq=20crdt--network-clients=20nil)=0A=20=20= (setq=20crdt--local-clock=200)=0A=20=20(setq=20crdt--next-client-id=201)=0A= =20=20(save-excursion=0A=20=20=20=20(widen)=0A=20=20=20=20(let=20= ((crdt--inhibit-update=20t))=0A=20=20=20=20=20=20= (with-silent-modifications=0A=20=20=20=20=20=20=20=20(crdt--local-insert=20= (point-min)=20(point-max)))))=0A=20=20(add-hook=20'kill-buffer-hook=20= #'crdt-stop-serve-buffer=20nil=20t)=0A=20=20(unless=20password=0A=20=20=20= =20(setq=20password=0A=20=20=20=20=20=20=20=20=20=20(when=20= crdt-ask-for-password=0A=20=20=20=20=20=20=20=20=20=20=20=20= (read-from-minibuffer=20"Set=20password=20(empty=20for=20no=20= authentication):=20"))))=0A=20=20(unless=20name=0A=20=20=20=20(when=20= crdt-ask-for-name=0A=20=20=20=20=20=20(setq=20name=20= (read-from-minibuffer=20"Display=20name:=20"))))=0A=20=20(setq=20= crdt--local-name=20name)=0A=20=20(setq=20crdt--network-process=0A=20=20=20= =20=20=20=20=20(make-network-process=0A=20=20=20=20=20=20=20=20=20:name=20= "CRDT=20Server"=0A=20=20=20=20=20=20=20=20=20:server=20t=0A=20=20=20=20=20= =20=20=20=20:family=20'ipv4=0A=20=20=20=20=20=20=20=20=20:host=20= "0.0.0.0"=0A=20=20=20=20=20=20=20=20=20:service=20port=0A=20=20=20=20=20=20= =20=20=20:filter=20#'crdt--network-filter=0A=20=20=20=20=20=20=20=20=20= :sentinel=20#'crdt--server-process-sentinel=0A=20=20=20=20=20=20=20=20=20= :plist=20`(crdt-buffer=20,(current-buffer)=20password=0A=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= ,(when=20(and=20password=20(>=20(length=20password)=200))=20= password)))))=0A(defsubst=20crdt--clear-overlay-table=20()=0A=20=20(when=20= crdt--overlay-table=0A=20=20=20=20(maphash=20(lambda=20(key=20pair)=0A=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20(delete-overlay=20(car=20= pair))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(delete-overlay=20= (cdr=20pair)))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= crdt--overlay-table)=0A=20=20=20=20(setq=20crdt--overlay-table=20nil)))=0A= (defun=20crdt-stop-serve-buffer=20()=0A=20=20"Stop=20sharing=20the=20= current=20buffer."=0A=20=20(interactive)=0A=20=20(if=20(or=20(not=20= crdt--network-process)=0A=20=20=20=20=20=20=20=20=20=20(not=20= (process-contact=20crdt--network-process=20:server)))=0A=20=20=20=20=20=20= (message=20"No=20CRDT=20server=20running=20on=20current=20buffer.")=0A=20= =20=20=20(when=20(process-buffer=20crdt--network-process)=0A=20=20=20=20=20= =20(kill-buffer=20(process-buffer=20crdt--network-process)))=0A=20=20=20=20= (delete-process=20crdt--network-process)=0A=20=20=20=20(dolist=20(client=20= crdt--network-clients)=0A=20=20=20=20=20=20(when=20(process-live-p=20= client)=0A=20=20=20=20=20=20=20=20(delete-process=20client))=0A=20=20=20=20= =20=20(when=20(process-buffer=20client)=0A=20=20=20=20=20=20=20=20= (kill-buffer=20(process-buffer=20client))))=0A=20=20=20=20(setq=20= crdt--network-process=20nil)=0A=20=20=20=20(setq=20crdt--network-clients=20= nil)=0A=20=20=20=20(crdt--clear-overlay-table)=0A=20=20=20=20(setq=20= crdt--local-id=20nil)=0A=20=20=20=20(setq=20crdt--contact-table=20nil))=0A= =20=20(crdt-mode=200))=0A(defun=20crdt-stop-client=20()=0A=20=20"Stop=20= the=20CRDT=20client=20running=20on=20current=20buffer=20if=20any.=0A= Leave=20the=20buffer=20open."=0A=20=20(interactive)=0A=20=20(if=20(or=20= (not=20crdt--network-process)=20(process-contact=20crdt--network-process=20= :server))=0A=20=20=20=20=20=20(message=20"No=20CRDT=20client=20running=20= on=20current=20buffer.")=0A=20=20=20=20(when=20(process-buffer=20= crdt--network-process)=0A=20=20=20=20=20=20(kill-buffer=20= (process-buffer=20crdt--network-process)))=0A=20=20=20=20(delete-process=20= crdt--network-process)=0A=20=20=20=20(setq=20crdt--network-process=20= nil)=0A=20=20=20=20(crdt--clear-overlay-table)=0A=20=20=20=20(setq=20= crdt--local-id=20nil)=0A=20=20=20=20(setq=20crdt--contact-table=20nil)=0A= =20=20=20=20(message=20"Disconnected=20from=20server."))=0A=20=20= (crdt-mode=200))=0A(defun=20crdt-connect=20(address=20port=20&optional=20= name)=0A=20=20"Connect=20to=20a=20CRDT=20server=20running=20at=20= ADDRESS:PORT.=0AOpen=20a=20new=20buffer=20to=20display=20the=20shared=20= content."=0A=20=20(interactive=20"MAddress:=20\nnPort:=20")=0A=20=20= (switch-to-buffer=20(generate-new-buffer=20"CRDT=20Client"))=0A=20=20= (unless=20name=0A=20=20=20=20(when=20crdt-ask-for-name=0A=20=20=20=20=20=20= (setq=20name=20(read-from-minibuffer=20"Display=20name:=20"))))=0A=20=20= (setq=20crdt--local-name=20name)=0A=20=20(setq=20crdt--network-process=0A= =20=20=20=20=20=20=20=20(make-network-process=0A=20=20=20=20=20=20=20=20=20= :name=20"CRDT=20Client"=0A=20=20=20=20=20=20=20=20=20:buffer=20= (generate-new-buffer=20"*crdt-client*")=0A=20=20=20=20=20=20=20=20=20= :host=20address=0A=20=20=20=20=20=20=20=20=20:family=20'ipv4=0A=20=20=20=20= =20=20=20=20=20:service=20port=0A=20=20=20=20=20=20=20=20=20:filter=20= #'crdt--network-filter=0A=20=20=20=20=20=20=20=20=20:sentinel=20= #'crdt--client-process-sentinel=0A=20=20=20=20=20=20=20=20=20:plist=20= `(crdt-buffer=20,(current-buffer))))=0A=20=20(crdt-mode)=0A=20=20= (add-hook=20'kill-buffer-hook=20#'crdt-stop-client=20nil=20t)=0A=20=20= (process-send-string=20crdt--network-process=0A=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20(format=20"%S"=20`(hello=20= ,name)))=0A=20=20(insert=20(format=20"Connected=20to=20server=20%s:%s,=20= synchronizing..."=20address=20port)))=0A(defun=20crdt-test-client=20()=0A= =20=20(interactive)=0A=20=20(crdt-connect=20"127.0.0.1"=201333))=0A= (defun=20crdt-test-server=20()=0A=20=20(interactive)=0A=20=20= (crdt-serve-buffer=201333))=0A(defun=20crdt--install-hooks=20()=0A=20=20= (add-hook=20'after-change-functions=20#'crdt--after-change=20nil=20t)=0A=20= =20(add-hook=20'before-change-functions=20#'crdt--before-change=20nil=20= t)=0A=20=20(add-hook=20'post-command-hook=20#'crdt--post-command=20nil=20= t))=0A(defun=20crdt--uninstall-hooks=20()=0A=20=20(remove-hook=20= 'after-change-functions=20#'crdt--after-change=20t)=0A=20=20(remove-hook=20= 'before-change-functions=20#'crdt--before-change=20t)=0A=20=20= (remove-hook=20'post-command-hook=20#'crdt--post-command=20t))=0A= (define-minor-mode=20crdt-mode=0A=20=20"CRDT=20mode"=20nil=20"=20CRDT"=20= nil=0A=20=20(if=20crdt-mode=0A=20=20=20=20=20=20(progn=0A=20=20=20=20=20=20= =20=20(setq=20crdt--overlay-table=20(make-hash-table))=0A=20=20=20=20=20=20= =20=20(setq=20crdt--contact-table=20(make-hash-table))=0A=20=20=20=20=20=20= =20=20(crdt--install-hooks))=0A=20=20=20=20(crdt--uninstall-hooks)=0A=20=20= =20=20(when=20crdt--user-list-buffer=0A=20=20=20=20=20=20(kill-buffer=20= crdt--user-list-buffer)=0A=20=20=20=20=20=20(setq=20= crdt--user-list-buffer=20nil))))=0A=0A(provide=20'crdt)=0A= --Apple-Mail=_27BBE01A-7683-4B72-BA13-70496B6368FE Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 It seems that locally I didn=E2=80=99t reproduce yanking issue and = multiple=20 crdt-test-client issue =E2=80=94 it just open another same document = normally, which is not the best behavior arguably but I didn=E2=80=99t get any = error. Also in terms of further development, yes a repo will be very helpful if anyone else is interested in contributing. I used GitHub before but I=E2=80=99m not sure if it feels comfortable for everyone =E2=80=94 = using it only as a file hosting service makes me feel better but it does run whatever software MS wants to run under the hood=E2=80=A6 > On Oct 8, 2020, at 10:42 PM, Yuan Fu wrote: >=20 >=20 >=20 >> On Oct 8, 2020, at 10:12 PM, Qiantan Hong wrote: >>=20 >>> Another question: According to the algorithm, what happens when both = cursor are at the same place and one inserts text? Is the other cursor = pushed forward or not? >> Currently the other cursor will not be pushed forward. Any suggestion = for =E2=80=9Cbetter=E2=80=9D behavior? >> Also note that the cursor-treating part of version on this mailing = list is not very polished. >> I=E2=80=99ve made some fixes and improvements on my local version, = will post shortly. >> The new version will also contain Unicode support and active user = list. >=20 > Pushing forward is more intuitive IMO. I asked because I see some = small problems with the cursor overlay but I=E2=80=99m sure you=E2=80=99ve= fixed them now. >=20 >>=20 >>> Very cool! If I try to run crdt-test-client when already connected, = Emacs creates a new buffer and compains "Error in post-command-hook = (crdt--post-command): (wrong-type-argument stringp nil)=E2=80=9D. Any = idea why? >> Thanks for the report! I haven=E2=80=99t consider this case. What do = you think is a reasonable >> behavior? Should it make a new connection? >=20 > It probably doesn=E2=80=99t make much sense to have multiple = connections on a single Emacs instance to the same document. I=E2=80=99d = just stop and complain to the user. >=20 > Yuan --Apple-Mail=_27BBE01A-7683-4B72-BA13-70496B6368FE-- --Apple-Mail=_2AD76598-61FA-4149-BBEB-AB82F1D269B3 Content-Disposition: attachment; filename="smime.p7s" Content-Type: application/pkcs7-signature; name="smime.p7s" Content-Transfer-Encoding: base64 MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAKCCA70w ggO5MIIDIqADAgECAhAaql39NsO1qLVjkS2hl517MA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNVBAYT AlVTMRYwFAYDVQQIEw1NYXNzYWNodXNldHRzMS4wLAYDVQQKEyVNYXNzYWNodXNldHRzIEluc3Rp dHV0ZSBvZiBUZWNobm9sb2d5MRUwEwYDVQQLEwxDbGllbnQgQ0EgdjEwHhcNMjAwODAzMDEyNDIz WhcNMjEwODAxMDEyNDIzWjCBoTELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMx LjAsBgNVBAoTJU1hc3NhY2h1c2V0dHMgSW5zdGl0dXRlIG9mIFRlY2hub2xvZ3kxFTATBgNVBAsT DENsaWVudCBDQSB2MTEVMBMGA1UEAxMMUWlhbnRhbiBIb25nMRwwGgYJKoZIhvcNAQkBFg1xaG9u Z0BNSVQuRURVMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAylUlEQdK4BSXKzoGh6As CKN/TpLmC0kjhPdxUKMj1/86Xl6GDCla4h95uISDOWVAKdu3cIlA8m9zRLT2jNEIkt1DVpXP6c9h y8RRyfJm0qlrvr6tsHi5AmO4Li6s2dEGaTxbakPL6vEn7ZYr86t5orq56nubki77Z8ZvRv9/fWdF bF/YBNGDayLNk0NbXIEQdCHiz1l+bxfw+GHHRmdOge3MKWSg463+GGMdxtLQ61AbtR2vm47FIJBt c0X6ptcInWUg4Nf/9vSNGl6KvREvfbEWKCT6TfL5ncIFlitf6ZWKue2PZ4ULFfIQ3/7EsEk03xxr S7sTOy7e2dbPboe/WwIDAQABo4GhMIGeMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMB0G A1UdJQQWMBQGCCsGAQUFBwMEBggrBgEFBQcDAjALBgNVHQ8EBAMCBeAwHQYDVR0OBBYEFDeb9Jlj XSm+y0CD872IhzRDIGv1MDMGA1UdHwQsMCowKKAmoCSGImh0dHA6Ly9jYS5taXQuZWR1L2NhL21p dGNsaWVudC5jcmwwDQYJKoZIhvcNAQELBQADgYEApBTx4tBbD5rQ+bNGd/Z3OBV07qFsm5QHNg0+ 6lxJ3j7q5zMMq35o6y5cBIhcFG6t+MFqJIdERZ3EprDturyqozQsIBMHFnqh+iZcMg0uQyssEqKZ hrzIdw8GuY4Z6jNewdGy5mwwG9yjpEbzWWgdofSM5rnezZz7EvCQu9ilt1sxggNDMIIDPwIBATCB gDBsMQswCQYDVQQGEwJVUzEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czEuMCwGA1UEChMlTWFzc2Fj aHVzZXR0cyBJbnN0aXR1dGUgb2YgVGVjaG5vbG9neTEVMBMGA1UECxMMQ2xpZW50IENBIHYxAhAa ql39NsO1qLVjkS2hl517MA0GCWCGSAFlAwQCAQUAoIIBkzAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN AQcBMBwGCSqGSIb3DQEJBTEPFw0yMDEwMDkwNzA0NDdaMC8GCSqGSIb3DQEJBDEiBCBjfKIZDyQn Kg76T6XvZNBjXsVdIYHEzF7jKlfJIo3IdTCBkQYJKwYBBAGCNxAEMYGDMIGAMGwxCzAJBgNVBAYT AlVTMRYwFAYDVQQIEw1NYXNzYWNodXNldHRzMS4wLAYDVQQKEyVNYXNzYWNodXNldHRzIEluc3Rp dHV0ZSBvZiBUZWNobm9sb2d5MRUwEwYDVQQLEwxDbGllbnQgQ0EgdjECEBqqXf02w7WotWORLaGX nXswgZMGCyqGSIb3DQEJEAILMYGDoIGAMGwxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNo dXNldHRzMS4wLAYDVQQKEyVNYXNzYWNodXNldHRzIEluc3RpdHV0ZSBvZiBUZWNobm9sb2d5MRUw EwYDVQQLEwxDbGllbnQgQ0EgdjECEBqqXf02w7WotWORLaGXnXswDQYJKoZIhvcNAQEBBQAEggEA J+ep+0lweXT5Dt0Cmir5GSA7cuZ+9kQ6MgyEM3Z+inIpxNrJwM1HMyYUa7LOP16LS5fFow928F3m knhGc5wiuA2cODd9KlGAXGxW2/rB6xTJrULv4lf+Z6YY/dv9cbd3vrOi59iy7h59lwOLuSYg95NO g5YNdeuMlSg6HV1+j56YmZU4W76XoepFU2cBS0f2M8+RnBpEaQy8fasiYx6kB1iR9+dCi/zF/tE1 2Z7Q2LgITC8HKETo2Pov656Ums3lSu5DwoiD4MEBr0wg5VSLgJ2AXW/INQ5OwoJOXn3N5iQUXnSv h2njWwQeK91xfsRq5wukpwUa7skhujpNSwFtlgAAAAAAAA== --Apple-Mail=_2AD76598-61FA-4149-BBEB-AB82F1D269B3--