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: Wed, 7 Oct 2020 00:00:54 +0000 Message-ID: <79924DB1-2613-4AF6-982C-39157CF3ED9B@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> Mime-Version: 1.0 Content-Type: multipart/signed; boundary="Apple-Mail=_60E98C5B-0FB3-4E8A-B086-2FD5858165F9"; protocol="application/pkcs7-signature"; micalg=sha-256 Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="32652"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Ergus , Fermin , Jean Louis , Caio Henrique , Noam Postavsky , Emacs developers , Stefan Monnier , Eli Zaretskii To: Karl Fogel Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Wed Oct 07 02:18:06 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 1kPx9i-0008PQ-41 for ged-emacs-devel@m.gmane-mx.org; Wed, 07 Oct 2020 02:18:06 +0200 Original-Received: from localhost ([::1]:37672 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kPx9h-00005A-2w for ged-emacs-devel@m.gmane-mx.org; Tue, 06 Oct 2020 20:18:05 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:39714) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kPwvV-0004rT-Ni for emacs-devel@gnu.org; Tue, 06 Oct 2020 20:03:27 -0400 Original-Received: from outgoing-exchange-3.mit.edu ([18.9.28.13]:42525) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kPwvS-0002sc-Qw; Tue, 06 Oct 2020 20:03:25 -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 09701CPl026898; Tue, 6 Oct 2020 20:01:16 -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; Tue, 6 Oct 2020 20:00:00 -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; Tue, 6 Oct 2020 20:00:54 -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; Tue, 6 Oct 2020 20:00:54 -0400 Thread-Topic: Question collaborative editing - Wikipedia reference Thread-Index: AQHWnA1T4LjU6k9HIUyZcJcFcadSFqmLhM0A In-Reply-To: <87pn5vgqsk.fsf@red-bean.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/06 20:03:14 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, 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:257180 Archived-At: --Apple-Mail=_60E98C5B-0FB3-4E8A-B086-2FD5858165F9 Content-Type: multipart/mixed; boundary="Apple-Mail=_BA90BA34-37D5-4673-94C8-A2BBB1968393" --Apple-Mail=_BA90BA34-37D5-4673-94C8-A2BBB1968393 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 > That's an interesting idea! >=20 > But do you intend to keep the "no server" option available too? That = is, for people whose network firewalls allow it, will it always be = possible to just run crdt.el with one of the Emacsen acting as server = itself? Sure of course, that=E2=80=99s the easy option. Also here is an a-bit-more-polished version. Now there=E2=80=99s - cursor/marked region indication - optional challenge-response authentication - I think it handles undo correctly now. Previously it was interfering with the text properties. - cleanup the dead processes etc correctly Also for the yanking issue Jeans reported, I haven=E2=80=99t=20 reproduce it successfully so it is a bug it will probably=20 still be there, but you can give a try.=20 --Apple-Mail=_BA90BA34-37D5-4673-94C8-A2BBB1968393 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=20is=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=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.=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=20sync=0A;;=20=20=20-=20insert=0A;;=20=20=20=20=20body=20takes=20= the=20form=20(crdt-id=20position-hint=20content)=0A;;=20=20=20=20=20-=20= position-hint=20is=20the=20buffer=20position=20where=20the=20operation=20= happens=20at=20the=20site=0A;;=20=20=20=20=20=20=20which=20generates=20= the=20operation.=20=20Then=20we=20can=20play=20the=20trick=20that=20= start=20search=0A;;=20=20=20=20=20=20=20near=20this=20position=20at=20= other=20sites=20to=20speedup=20crdt-id=20search=0A;;=20=20=20=20=20-=20= content=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=20takes=20= the=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=20string,=20= or=0A;;=20=20=20=20=20=20=20-=20nil,=20which=20means=20clear=20the=20= point/mark=0A;;=20=20=20-=20hello=0A;;=20=20=20=20=20This=20message=20is=20= sent=20from=20client=20to=20server,=20when=20a=20client=20connect=20to=20= the=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(require=20'cl-lib)=0A=0A(require=20= 'color)=0A(defvar=20crdt-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=20for=20hue=20by=20(/=201.0=20n)=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20collect=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=20hue=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=20hue=200.2=200.5))))))=0A=0A(defun=20= crdt--get-cursor-color=20(site-id)=0A=20=20"Get=20cursor=20color=20for=20= SITE-ID."=0A=20=20(car=20(nth=20(mod=20site-id=20(length=20= crdt-cursor-region-colors))=20crdt-cursor-region-colors)))=0A(defun=20= crdt--get-region-color=20(site-id)=0A=20=20"Get=20region=20color=20for=20= SITE-ID."=0A=20=20(cdr=20(nth=20(mod=20site-id=20(length=20= crdt-cursor-region-colors))=20crdt-cursor-region-colors)))=0A(defun=20= crdt--move-cursor=20(ov=20pos)=0A=20=20"Move=20pseudo=20cursor=20overlay=20= OV=20to=20POS."=0A=20=20(let*=20((eol=20(or=20(eq=20pos=20(point-max))=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(eq=20(char-after=20= pos)=20?\n)))=0A=20=20=20=20=20=20=20=20=20(end=20(if=20eol=20pos=20(1+=20= pos)))=0A=20=20=20=20=20=20=20=20=20(after-str=20(when=20eol=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= 'after-string=20after-str)))=0A(defun=20crdt--move-region=20(ov=20pos=20= mark)=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=0A(defun=20= crdt--local-insert=20(beg=20end)=0A=20=20"To=20be=20called=20after=20a=20= local=20insert=20happened=20in=20current=20buffer=20from=20BEG=20to=20= END.=0AReturns=20a=20list=20of=20(insert=20type)=20messages=20to=20be=20= sent."=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=20= not-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)=20crdt--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;;=20merge=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=20= left-offset=201)=20beg))))=0A=20=20=20=20=20=20=20=20=20=20=20(unless=20= (=3D=20merge-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,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=20beg=20merge-end))=0A=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= resulting-commands))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20(setq=20= beg=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(+=20crdt--max-value=20= beg))))=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,new-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=20= position-hint=20content)=20message=0A=20=20=20=20=20=20(let=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=20content)=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)=20id-pair=0A=20=20=20=20=20=20= =20=20(while=20(>=20length=200)=0A=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(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(block-length=20(-=20end-of-block=20(point))))=0A=20= =20=20=20=20=20=20=20=20=20=20=20(cl-case=20(cl-signum=20(-=20length=20= block-length))=0A=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(cl-decf=20length=20block-length)=0A=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((0)=20= (delete-char=20length)=0A=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((-1)=0A= =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(left-offset=20(crdt--get-id-offset=20= starting-id=20(point))))=0A=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(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(setq=20length=200)))))=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=20= crdt--before-change=20(beg=20end)=0A=20=20(unless=20crdt--inhibit-update=0A= =20=20=20=20(setq=20crdt--changed-string=20(buffer-substring=20beg=20= end))))=0A=0A(defun=20crdt--after-change=20(beg=20end=20length)=0A=20=20= (when=20crdt--local-id=20;=20CRDT--LOCAL-ID=20is=20NIL=20when=20a=20= client=20haven't=20received=20the=20first=20sync=20message=0A=20=20=20=20= (unless=20crdt--inhibit-update=0A=20=20=20=20=20=20(let=20= ((crdt--inhibit-update=20t))=0A=20=20=20=20=20=20=20=20;;=20we're=20only=20= interested=20in=20text=20change=0A=20=20=20=20=20=20=20=20;;=20ignore=20= property=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=20length=20(-=20end=20beg))=20(looking-at=20= (regexp-quote=20crdt--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"=20message)))))))))))=0A=0A(defun=20= crdt--remote-cursor=20(message)=0A=20=20(unless=20crdt--overlay-table=0A=20= =20=20=20(setq=20crdt--overlay-table=20(make-hash-table)))=0A=20=20= (cl-destructuring-bind=0A=20=20=20=20=20=20(site-id=20= point-position-hint=20point-crdt-id=20mark-position-hint=20mark-crdt-id)=20= message=0A=20=20=20=20(let=20((ov-pair=20(gethash=20site-id=20= crdt--overlay-table)))=0A=20=20=20=20=20=20(if=20point-crdt-id=0A=20=20=20= =20=20=20=20=20=20=20(let*=20((point=20(crdt--find-id=20point-crdt-id=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=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=20mark-crdt-id=20= mark-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=20point)))=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=201=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-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=20= site-id=20(cons=20new-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=20crdt--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(defun=20crdt--local-cursor=20= ()=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=20(eq=20= point=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(let=20= ((point-id=20(crdt--get-id=20(1-=20point)))=0A=20=20=20=20=20=20=20=20=20= =20=20=20(mark-id=20(when=20mark=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=20,mark=20,mark-id)))))=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=20.=20END-OF-BLOCK-P),=0Aor=20= (LENGTH=20.=20CRDT-ID)=20if=20OMIT-END-OF-BLOCK-P=20is=20non-NIL.=0Ain=20= the=20order=20that=20they=20appears=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(-=20= pos=20prev-pos)=0A=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=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(crdt--get-starting-id=20prev-pos=20= object)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20(crdt--get-crdt-id-pair=20prev-pos=20object)))=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=20= prev-pos)))=0A=20=20=20=20ids))=0A(defun=20crdt--load-ids=20(ids)=0A=20=20= "Load=20the=20CRDT=20ids=20in=20IDS=20(generated=20by=20CRDT--DUMP-IDS)=0A= into=20current=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=20(cdr=20id-pair))=0A=20=20=20=20=20=20=20=20= (setq=20pos=20next-pos)))))=0A(defun=20crdt--verify-buffer=20()=0A=20=20= "Debug=20helper=20function.=0AVerify=20that=20CRDT=20IDs=20in=20a=20= document=20follows=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=20= pos)))=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(-=20next-pos=20pos)=20(crdt--id-offset=20= id)))=0A=20=20=20=20=20=20=20=20=20=20=20=20(unless=20(string<=20prev-id=20= next-id)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20(error=20"Not=20= monotonic!"))=0A=20=20=20=20=20=20=20=20=20=20=20=20(setq=20pos=20= next-pos)=0A=20=20=20=20=20=20=20=20=20=20=20=20(setq=20id=20= next-id))))))=0A=0A(crdt--defvar-permanent-local=20crdt--network-process=20= nil)=0A(crdt--defvar-permanent-local=20crdt--network-clients=20nil)=0A= (crdt--defvar-permanent-local=20crdt--next-client-id=20nil)=0A(cl-defun=20= crdt--broadcast-maybe=20(message-string=20&optional=20(without=20t))=0A=20= =20"Broadcast=20or=20send=20MESSAGE-STRING.=0AIf=20CRDT--NETWORK-PROCESS=20= is=20a=20server=20process,=20broadcast=20MESSAGE-STRING=0Ato=20clients=20= except=20the=20one=20of=20which=20CLIENT-ID=20property=20is=20EQ=20to=20= WITHOUT.=0AIf=20CRDT--NETWORK-PROCESS=20is=20a=20server=20process,=20= send=20MESSAGE-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=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=20crdt--network-process=20= message-string)=0A=20=20=20=20=20=20;;=20(run-at-time=201=20nil=20= #'process-send-string=20crdt--network-process=20message-string)=0A=20=20=20= =20=20=20)))=0A(defun=20crdt--generate-challenge=20()=0A=20=20(apply=20= #'unibyte-string=20(cl-loop=20for=20i=20below=2032=20collect=20(random=20= 256))))=0A(defun=20crdt--greet-client=20(process)=0A=20=20(cl-pushnew=20= process=20crdt--network-clients)=0A=20=20(let=20((client-id=20= (process-get=20process=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=20= implement=20allocation=20algorithm."))=0A=20=20=20=20=20=20(process-put=20= process=20'client-id=20crdt--next-client-id)=0A=20=20=20=20=20=20(setq=20= client-id=20crdt--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(defun=20crdt--network-filter=20(process=20string)=0A=20=20= (unless=20(process-buffer=20process)=0A=20=20=20=20(set-process-buffer=20= process=20(generate-new-buffer=20"*crdt-server*"))=0A=20=20=20=20= (set-marker=20(process-mark=20process)=201))=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= (when=20enable-multibyte-characters=0A=20=20=20=20=20=20=20=20= (set-buffer-multibyte=20nil))=0A=20=20=20=20=20=20(save-excursion=0A=20=20= =20=20=20=20=20=20(goto-char=20(process-mark=20process))=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= (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(let=20((server-p=20(process-contact=20= crdt--network-process=20:server)))=0A=20=20=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=20=20(widen)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (cl-destructuring-bind=20(type=20.=20body)=20message=0A=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20(if=20(or=20(not=20server-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=20=20=20=20(let=20((crdt--inhibit-update=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(cl-case=20type=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((insert)=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(unless=20= (eq=20(crdt--id-site=20(car=20body))=20crdt--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= (crdt--remote-insert=20body)=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= (crdt--broadcast-maybe=20(format=20"%S"=20message)=20(process-get=20= process=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((delete)=20(crdt--remote-delete=20= body)=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(crdt--broadcast-maybe=20(format=20"%S"=20message)=20= (process-get=20process=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((cursor)=20= (crdt--remote-cursor=20body)=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(crdt--broadcast-maybe=20= (format=20"%S"=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=20=20=20=20=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-get=20process=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((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(unless=20server-p=20= ;=20server=20shouldn't=20receive=20this=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= (erase-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(cl-destructuring-bind=20(id=20mode=20= content=20.=20ids)=20body=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(if=20(fboundp=20= 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(unless=20(eq=20major-mode=20= 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(funcall=20mode)=20;=20= trust=20your=20server...=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--install-hooks))=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(message=20= "Server=20uses=20%s,=20but=20not=20available=20locally."=20mode))=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(insert=20content)=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(setq=20= crdt--local-id=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=20=20=20=20=20=20=20=20=20(crdt--load-ids=20= ids))))=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((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(unless=20server-p=20;=20= server=20shouldn't=20receive=20this=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(message=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=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=20=20=20=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"Password=20= for=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=20=20=20=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=20=20=20=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=20=20=20=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--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=20=20=20=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(car=20= body))))))))))=0A=20=20=20=20=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=20=20=20=20(cl-case=20type=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((hello)=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= (cl-destructuring-bind=20(name=20&optional=20response)=20body=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(when=20(or=20(not=20(process-get=20process=20'password))=20;=20= server=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=20=20=20=20=20= (and=20response=20(string-equal=20response=20(process-get=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(process-put=20process=20= 'authenticated=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(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=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=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=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=20=20=20=20(gnutls-hash-mac=20'SHA1=20= (substring=20(process-get=20process=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=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;;=20simulate=20a=20clear=20cursor=20message=0A=20=20=20=20=20= =20(let=20((clear-cursor-message=20`(,(process-get=20client=20= 'client-id)=201=20nil=201=20nil)))=0A=20=20=20=20=20=20=20=20= (crdt--remote-cursor=20clear-cursor-message)=0A=20=20=20=20=20=20=20=20= (crdt--broadcast-maybe=20(format=20"%S"=20(cons=20'cursor=20= clear-cursor-message)))))))=0A(defun=20crdt--client-process-sentinel=20= (process=20message)=0A=20=20(with-current-buffer=20(process-get=20= process=20'crdt-buffer)=0A=20=20=20=20(unless=20(eq=20(process-status=20= process)=20'open)=0A=20=20=20=20=20=20(crdt-stop-client))))=0A(defun=20= crdt-serve-buffer=20(port)=0A=20=20"Share=20the=20current=20buffer=20on=20= PORT."=0A=20=20(interactive=20"nPort:=20")=0A=20=20(crdt-mode)=0A=20=20= (setq=20crdt--local-id=200)=0A=20=20(setq=20crdt--network-clients=20nil)=0A= =20=20(setq=20crdt--local-clock=200)=0A=20=20(setq=20= crdt--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= (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(let=20= ((password=20(read-from-minibuffer=20"Set=20password=20(empty=20for=20no=20= authentication):=20")))=0A=20=20=20=20(setq=20crdt--network-process=0A=20= =20=20=20=20=20=20=20=20=20(make-network-process=0A=20=20=20=20=20=20=20=20= =20=20=20:name=20"CRDT=20Server"=0A=20=20=20=20=20=20=20=20=20=20=20= :server=20t=0A=20=20=20=20=20=20=20=20=20=20=20:family=20'ipv4=0A=20=20=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=20= =20:service=20port=0A=20=20=20=20=20=20=20=20=20=20=20:filter=20= #'crdt--network-filter=0A=20=20=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=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=20= =20,(when=20(and=20password=20(>=20(length=20password)=200))=20= password))))))=0A(defsubst=20crdt--clear-overlay-table=20()=0A=20=20= (when=20crdt--overlay-table=0A=20=20=20=20(maphash=20(lambda=20(key=20= pair)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(delete-overlay=20= (car=20pair))=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(when=20= crdt--network-process=0A=20=20=20=20(delete-process=20= crdt--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(crdt-mode=200))=0A(defun=20= crdt-stop-client=20()=0A=20=20"Stop=20the=20CRDT=20client=20running=20on=20= current=20buffer=20if=20any.=0ALeave=20the=20buffer=20open."=0A=20=20= (interactive)=0A=20=20(when=20crdt--network-process=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(setq=20= crdt--network-process=20nil)=0A=20=20=20=20(crdt--clear-overlay-table)=0A= =20=20=20=20(setq=20crdt--local-id=20nil)=0A=20=20=20=20(message=20= "Disconnected=20from=20server."))=0A=20=20(crdt-mode=200))=0A(defun=20= crdt-connect=20(address=20port)=0A=20=20"Connect=20to=20a=20CRDT=20= server=20running=20at=20ADDRESS:PORT.=0AOpen=20a=20new=20buffer=20to=20= display=20the=20shared=20content."=0A=20=20(interactive=20"MAddress:=20= \nnPort:=20")=0A=20=20(switch-to-buffer=20(generate-new-buffer=20"CRDT=20= Client"))=0A=20=20(crdt-mode)=0A=20=20(add-hook=20'kill-buffer-hook=20= #'crdt-stop-client=20nil=20t)=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(process-send-string=20= crdt--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=20nil)))=0A=20=20(insert=20= (format=20"Connected=20to=20server=20%s:%s,=20synchronizing..."=20= address=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=20= crdt--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=20t)=0A=20=20= (add-hook=20'post-command-hook=20#'crdt--post-command=20nil=20t))=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(crdt--install-hooks)=0A=20= =20=20=20(crdt--uninstall-hooks)))=0A= --Apple-Mail=_BA90BA34-37D5-4673-94C8-A2BBB1968393-- --Apple-Mail=_60E98C5B-0FB3-4E8A-B086-2FD5858165F9 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 AQcBMBwGCSqGSIb3DQEJBTEPFw0yMDEwMDcwMDAwNTRaMC8GCSqGSIb3DQEJBDEiBCBubt1ICgiL yedYbkupx9OSlsUfvymjZ7GPlGUp15KqdzCBkQYJKwYBBAGCNxAEMYGDMIGAMGwxCzAJBgNVBAYT AlVTMRYwFAYDVQQIEw1NYXNzYWNodXNldHRzMS4wLAYDVQQKEyVNYXNzYWNodXNldHRzIEluc3Rp dHV0ZSBvZiBUZWNobm9sb2d5MRUwEwYDVQQLEwxDbGllbnQgQ0EgdjECEBqqXf02w7WotWORLaGX nXswgZMGCyqGSIb3DQEJEAILMYGDoIGAMGwxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNo dXNldHRzMS4wLAYDVQQKEyVNYXNzYWNodXNldHRzIEluc3RpdHV0ZSBvZiBUZWNobm9sb2d5MRUw EwYDVQQLEwxDbGllbnQgQ0EgdjECEBqqXf02w7WotWORLaGXnXswDQYJKoZIhvcNAQEBBQAEggEA ee4YscA3FuvX5h4SnNezrGRgZCV7xEunJRKoLymcR8kgYpkyxZXgZEnbifYczipyVvUo8hKMa681 onWgRcODC7ycnykzN5sv9lTFUyJOqxmkk3jBQDp7kS/BGIMt8LFnnjJzBQ8YjRhhOnHkXS9jAeQj Jr0E/WeeHg3TlrbswieQO43o4q8N898UTeWA+d3k0jX2jsoB/ZxJ9doxh2gKvKqXlKhc7/aLU6rU 50cvUx84ZoPPQXrAzmzKaj2e/Gb8lIRAltAhxCjUjY/hhlm7eivVe4IV9mguWnVcrTRE88/eQcMX VW8xfEq2x5d/q1S3TkZwCh8WxUqKOjrW2Nq1DAAAAAAAAA== --Apple-Mail=_60E98C5B-0FB3-4E8A-B086-2FD5858165F9--