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: Sun, 4 Oct 2020 23:59:33 +0000 Message-ID: <11EB917D-3276-4D34-8DCD-2336E8062950@mit.edu> References: <20200929215849.zg4wzytbrwx2b7ih@Ergus> <84B86B7C-81F0-42DF-894C-BF577E4B3D6E@mit.edu> <83a6x7js6y.fsf@gnu.org> <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> Mime-Version: 1.0 Content-Type: multipart/signed; boundary="Apple-Mail=_C86FFFC5-E98E-4EDE-BA77-D364BA4C6AA7"; protocol="application/pkcs7-signature"; micalg=sha-256 Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="32316"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Ergus , Fermin , Caio Henrique , Noam Postavsky , Emacs developers , Karl Fogel , Stefan Monnier , Eli Zaretskii To: Jean Louis Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Mon Oct 05 02:01:25 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 1kPDwS-0008KZ-5p for ged-emacs-devel@m.gmane-mx.org; Mon, 05 Oct 2020 02:01:24 +0200 Original-Received: from localhost ([::1]:53926 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kPDwR-0003ol-6Z for ged-emacs-devel@m.gmane-mx.org; Sun, 04 Oct 2020 20:01:23 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:39004) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kPDuu-0003HA-VH for emacs-devel@gnu.org; Sun, 04 Oct 2020 19:59:49 -0400 Original-Received: from outgoing-exchange-1.mit.edu ([18.9.28.15]:53420) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kPDur-0004mM-97; Sun, 04 Oct 2020 19:59:48 -0400 Original-Received: from w92exedge4.exchange.mit.edu (W92EXEDGE4.EXCHANGE.MIT.EDU [18.7.73.16]) by outgoing-exchange-1.mit.edu (8.14.7/8.12.4) with ESMTP id 094NxTrU013025; Sun, 4 Oct 2020 19:59:35 -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; Sun, 4 Oct 2020 19:58:43 -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; Sun, 4 Oct 2020 19:59:33 -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; Sun, 4 Oct 2020 19:59:33 -0400 Thread-Topic: Question collaborative editing - Wikipedia reference Thread-Index: AQHWmn564LjU6k9HIUyZcJcFcadSFqmIHKqAgABGNoA= In-Reply-To: <20201004194815.GH15516@protected.rcdrun.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.15; envelope-from=qhong@mit.edu; helo=outgoing-exchange-1.mit.edu X-detected-operating-system: by eggs.gnu.org: First seen = 2020/10/04 19:59:40 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_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H2=-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:257057 Archived-At: --Apple-Mail=_C86FFFC5-E98E-4EDE-BA77-D364BA4C6AA7 Content-Type: multipart/mixed; boundary="Apple-Mail=_4C0BF963-9CB7-4D7F-9C17-42FC5BCCB3F2" --Apple-Mail=_4C0BF963-9CB7-4D7F-9C17-42FC5BCCB3F2 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 I=E2=80=99ve finished the initial work for a CRDT based collaborative editing Elisp package. The .el is attached. The focus is on implementing the core algorithm and evaluating performance, so no cursor indicator, username display etc was implemented. However, they should be easy to add and not relevant to performance concern. I=E2=80=99ve tested it with my friends and the performance seems=20 reasonable. Using LogootSplit algorithm, the number of text property changes are relatively small. M-x crdt-test-server starts a local server at port 1333, and M-x crdt-test-client connects to 127.0.0.1:1333. Or use crdt-serve-buffer and crdt-connect for arbitrary address/port. --Apple-Mail=_4C0BF963-9CB7-4D7F-9C17-42FC5BCCB3F2 Content-Disposition: attachment; filename=crdt.el Content-Type: application/octet-stream; x-unix-mode=0644; name="crdt.el" Content-Transfer-Encoding: quoted-printable (require=20'cl-lib)=0A=0A;;=20Loogoot=20split=20algorithm=0A;;=20Andr=C3=A9= ,=20Luc,=20et=20al.=20"Supporting=20adaptable=20granularity=20of=20= changes=20for=20massive-scale=20collaborative=20editing."=209th=20IEEE=20= International=20Conference=20on=20Collaborative=20Computing:=20= Networking,=20Applications=20and=20Worksharing.=20IEEE,=202013.=0A= (defvar=20crdt--local-clock=200)=0A(defvar=20crdt--local-id)=0A(defvar=20= crdt--inhibit-update=20nil=20"When=20set,=20don't=20call=20CRDT--LOCAL-*=20= on=20change.=0AThis=20is=20useful=20for=20functions=20that=20apply=20= remote=20change=20to=20local=20buffer,=0Ato=20avoid=20recusive=20calling=20= of=20CRDT=20synchronization=20functions.")=0A=0A;;=20CRDT=20IDs=20are=20= represented=20by=20unitbyte=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(defconst=20crdt--max-value=20(lsh=201=2016))=0A= ;;=20(defconst=20crdt--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(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(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+=20index))))=0A=20=20=20=20=20=20=20=20(t=20= default)))=0A=0A(defsubst=20crdt--id-offset=20(id)=0A=20=20"Get=20the=20= literal=20offset=20integer=20from=20ID.=0ANote=20that=20it=20might=20= deviate=20from=20real=20offset=20for=20a=20character=0Ain=20the=20middle=20= of=20a=20block."=0A=20=20(crdt--get-two-bytes=20id=20(-=20(string-bytes=20= id)=202)))=0A(defsubst=20crdt--set-id-offset=20(id=20offset)=0A=20=20= (let=20((length=20(string-bytes=20id)))=0A=20=20=20=20(aset=20id=20(-=20= length=202)=20(lsh=20offset=20-8))=0A=20=20=20=20(aset=20id=20(-=20= length=201)=20(logand=20offset=20crdt--low-byte-mask))))=0A(defsubst=20= crdt--id-site=20(id)=0A=20=20(crdt--get-two-bytes=20id=20(-=20= (string-bytes=20id)=204)))=0A(defsubst=20crdt--generate-id=20(low-id=20= low-offset=20high-id=20high-offset=20site-id)=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-text-property=20pos=20'crdt-id=20= obj))=0A(defsubst=20crdt--get-starting-id=20(pos=20&optional=20obj)=0A=20= =20(car=20(crdt--get-crdt-id-pair=20pos=20obj)))=0A(defsubst=20= crdt--end-of-block-p=20(pos=20&optional=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= (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,=0Aassuming=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=0A(defsubst=20= crdt--set-id=20(pos=20id=20&optional=20end-of-block=20obj=20limit)=0A=20=20= (put-text-property=20pos=20(next-single-property-change=20pos=20'crdt-id=20= obj=20(or=20limit=20(point-max)))=20'crdt-id=20(cons=20id=20= end-of-block)=20obj))=0A=0A(defsubst=20crdt--id-replace-offset=20(id=20= offset)=0A=20=20(let=20((new-id=20(substring=20id)))=0A=20=20=20=20= (crdt--set-id-offset=20new-id=20offset)=0A=20=20=20=20new-id))=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`(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;;=20= The=20protocol=0A;;=20Text-based=20version=0A;;=20(it=20should=20be=20= easy=20to=20migrate=20to=20a=20binary=20version.=20Using=20text=20for=20= better=20debugging=20for=20now)=0A;;=20Every=20message=20takes=20the=20= form=20(type=20.=20body)=0A;;=20type=20can=20be:=20insert=20hello=20sync=0A= ;;=20-=20insert=0A;;=20=20=20body=20takes=20the=20form=20(crdt-id=20= position-hint=20content)=0A;;=20=20=20-=20position-hint=20is=20the=20= buffer=20position=20where=20the=20operation=20happens=20at=20the=20site=0A= ;;=20=20=20=20=20which=20generates=20the=20operation.=20Then=20we=20can=20= play=20the=20trick=20that=20start=20search=0A;;=20=20=20=20=20near=20= this=20position=20at=20other=20sites=20to=20speedup=20crdt-id=20search=0A= ;;=20=20=20-=20content=20is=20the=20string=20to=20be=20inserted=0A;;=20-=20= delete=0A;;=20=20=20body=20takes=20the=20form=20(position-hint=20= (crdt-id=20.=20length)*)=0A;;=20-=20cursor=0A;;=20=20=20body=20takes=20= the=20form=20(site-id=20point-position-hint=20point-crdt-id=20= mark-position-hint=20mark-crdt-id)=0A;;=20=20=20*-crdt-id=20can=20be=20= either=20a=20CRDT=20ID=20string,=20or=0A;;=20=20=20=20=20-=20nil,=20= which=20means=20clear=20the=20cursor/mark=0A;;=20=20=20=20=20-=20t,=20= which=20means=20end=20of=20buffer=0A;;=20-=20hello=0A;;=20=20=20This=20= message=20is=20sent=20from=20client=20to=20server,=20when=20a=20client=20= connect=20to=20the=20server.=0A;;=20=20=20body=20is=20currently=20nil=0A= ;;=20-=20sync=0A;;=20=20=20This=20message=20is=20sent=20from=20server=20= to=20client=20to=20get=20it=20sync=20to=20the=20state=20on=20the=20= server.=0A;;=20=20=20It's=20always=20sent=20after=20server=20receives=20= a=20hello=20message.=0A;;=20=20=20Might=20be=20used=20for=20error=20= recovery=20or=20other=20optimization=20in=20the=20future.=0A;;=20=20=20= One=20optimization=20I=20have=20in=20mind=20is=20let=20server=20try=20to=20= merge=20all=20CRDT=20item=20into=20a=20single=0A;;=20=20=20one=20and=20= try=20to=20synchronize=20this=20state=20to=20clients=20at=20best=20= effort.=0A;;=20=20=20body=20takes=20the=20form=20(site-id=20content=20.=20= crdt-id-list)=0A;;=20=20=20-=20site-id=20is=20the=20site=20ID=20the=20= server=20assigned=20to=20the=20client=0A;;=20=20=20-=20content=20is=20= the=20string=20in=20the=20buffer=0A;;=20=20=20-=20crdt-id-list=20is=20= generated=20from=20CRDT--DUMP-IDS=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=20= a-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=20base-length=20b=200=20base-length))))))=0A= (defun=20crdt--local-insert=20(beg=20end)=0A=20=20"To=20be=20called=20= after=20a=20local=20insert=20happened=20in=20current=20buffer,=20from=20= BEG=20to=20END.=0AReturns=20a=20list=20of=20(insert=20type)=20messages=20= to=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=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(crdt--verify-buffer)=0A=20= =20=20=20(nreverse=20resulting-commands)))=0A=0A(defun=20crdt--find-id=20= (id=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=20= nil=20(point-min)))=0A=20=20=20=20=20=20=20=20=20(left-id=20= (crdt--get-starting-id=20left-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=20right-pos)))=0A=20=20=20=20(print=20(list=20= left-pos=20left-id=20right-pos=20right-id))=0A=20=20=20=20(cl-block=20= nil=0A=20=20=20=20=20=20(while=20t=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=20= right-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=20= left-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=20left-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;;=20will=20unibyte=20to=20multibyte=20conversion=20cause=20= any=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=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=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=20position-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(when=20beg=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(insert=20content)=0A=20= =20=20=20=20=20=20=20=20=20(setq=20end=20(point))=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= (crdt--verify-buffer))=0A=0A(defun=20crdt--local-delete=20(beg=20end)=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= (crdt--verify-buffer)=0A=20=20`(delete=20,beg=20,@=20(crdt--dump-ids=200=20= (length=20crdt--changed-string)=20crdt--changed-string=20t)))=0A(defun=20= crdt--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(>=20= length=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= (crdt--verify-buffer)))))=0A=0A(defvar=20crdt--changed-string=20nil)=0A= (defun=20crdt--before-change=20(beg=20end)=0A=20=20(unless=20= crdt--inhibit-update=0A=20=20=20=20(setq=20crdt--changed-string=20= (buffer-substring=20beg=20end))))=0A=0A(defun=20crdt--after-change=20= (beg=20end=20length)=0A=20=20(unless=20crdt--inhibit-update=0A=20=20=20=20= (let=20((crdt--inhibit-update=20t))=0A=20=20=20=20=20=20;;=20we're=20= only=20interested=20in=20text=20change=0A=20=20=20=20=20=20;;=20ignore=20= property=20only=20changes=0A=20=20=20=20=20=20(save-excursion=0A=20=20=20= =20=20=20=20=20(goto-char=20beg)=0A=20=20=20=20=20=20=20=20(unless=20= (and=20(=3D=20length=20(-=20end=20beg))=20(looking-at=20(regexp-quote=20= crdt--changed-string)))=0A=20=20=20=20=20=20=20=20=20=20(widen)=0A=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(crdt--broadcast-maybe=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20(format=20"%S"=20(let=20((m=20(crdt--local-delete=20beg=20end)))=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(print=20m)=20m))))=0A=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(dolist=20= (message=20(crdt--local-insert=20beg=20end))=0A=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(format=20"%S"=20(progn=20(print=20message)=20= message))))))))))=0A=0A(defun=20crdt--dump-ids=20(beg=20end=20object=20= &optional=20omit-end-of-block-p)=0A=20=20"Serialize=20all=20CRDT=20ids=20= in=20OBJECT=20from=20BEG=20to=20END=20into=20a=20list=20of=0ACONSes=20of=20= the=20form=20(LENGTH=20CRDT-ID=20.=20END-OF-BLOCK-P),=0Aor=20(LENGTH=20.=20= CRDT-ID)=20if=20OMIT-END-OF-BLOCK-P=20is=20non-NIL.=0Ain=20the=20order=20= that=20they=20appears=20in=20the=20document"=0A=20=20(let=20(ids=20(pos=20= end))=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=20= beg)))=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(if=20= omit-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=20object)=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=20to=20verify=20that=20CRDT=20IDs=20in=20a=20= document=20follows=0Aascending=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(defvar=20crdt--network-process)=0A(defvar=20= crdt--network-clients)=0A(defvar=20crdt--next-client-id)=0A(cl-defun=20= crdt--broadcast-maybe=20(message-string=20&optional=20(without=20t))=0A=20= =20"Broadcast=20or=20send=20MESSAGE-STRING=20depends=20on=20whether=20= CRDT--NETWORK-PROCESS=0Ais=20a=20server=20process.=0AIf=20= CRDT--NETWORK-PROCESS=20is=20a=20server=20process,=20broadcast=20= MESSAGE-STRING=0Ato=20clients=20except=20the=20one=20of=20which=20= CLIENT-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(if=20= (process-contact=20crdt--network-process=20:server)=0A=20=20=20=20=20=20= (dolist=20(client=20crdt--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=20= client=20'client-id)=20without)))=0A=20=20=20=20=20=20=20=20=20=20= (process-send-string=20client=20message-string)))=0A=20=20=20=20(when=20= without=0A=20=20=20=20=20=20(process-send-string=20crdt--network-process=20= message-string))))=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)=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(print=20(list=20= 'received=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(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(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(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((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(unless=20(eq=20(crdt--id-site=20(car=20= body))=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(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= (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((delete)=20(crdt--remote-delete=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= (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((hello)=20(cl-pushnew=20process=20= crdt--network-clients)=0A=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= `(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=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= ,crdt--next-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=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=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)=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(process-put=20process=20'client-id=20= crdt--next-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(cl-incf=20crdt--next-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((sync)=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= (cl-destructuring-bind=20(id=20content=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(insert=20= content)=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(setq=20crdt--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(crdt--load-ids=20= ids))))))))=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=20crdt-serve-buffer=20(port)=0A=20=20""=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(crdt--local-insert=20= (point-min)=20(point-max))))=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= :plist=20`(crdt-buffer=20,(current-buffer)))))=0A(defun=20= crdt-stop-serve-buffer=20()=0A=20=20(interactive)=0A=20=20= (delete-process=20crdt--network-process)=0A=20=20(dolist=20(client=20= crdt--network-clients)=0A=20=20=20=20(when=20(process-live-p=20client)=0A= =20=20=20=20=20=20(delete-process=20client))=0A=20=20=20=20(when=20= (process-buffer=20client)=0A=20=20=20=20=20=20(kill-buffer=20= (process-buffer=20client))))=0A=20=20(setq=20crdt--network-process=20= nil)=0A=20=20(setq=20crdt--network-clients=20nil)=0A=20=20(crdt-mode=20= 0))=0A(defun=20crdt-connect=20(address=20port)=0A=20=20""=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(crdt-mode)=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=20= Client"=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=20= port=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:plist=20`(crdt-buffer=20,(current-buffer))))=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))))=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=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(add-hook=20'after-change-functions=20#'crdt--after-change=20nil=20= t)=0A=20=20=20=20=20=20=20=20(add-hook=20'before-change-functions=20= #'crdt--before-change=20nil=20t))=0A=20=20=20=20(remove-hook=20= 'after-change-functions=20#'crdt--after-change=20t)=0A=20=20=20=20= (remove-hook=20'before-change-functions=20#'crdt--before-change=20t)))=0A= --Apple-Mail=_4C0BF963-9CB7-4D7F-9C17-42FC5BCCB3F2-- --Apple-Mail=_C86FFFC5-E98E-4EDE-BA77-D364BA4C6AA7 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 AQcBMBwGCSqGSIb3DQEJBTEPFw0yMDEwMDQyMzU5MzNaMC8GCSqGSIb3DQEJBDEiBCCnlbsQXhVg WbnC8UomW3VVum8es5AdHhTLzLEb+G/ONDCBkQYJKwYBBAGCNxAEMYGDMIGAMGwxCzAJBgNVBAYT AlVTMRYwFAYDVQQIEw1NYXNzYWNodXNldHRzMS4wLAYDVQQKEyVNYXNzYWNodXNldHRzIEluc3Rp dHV0ZSBvZiBUZWNobm9sb2d5MRUwEwYDVQQLEwxDbGllbnQgQ0EgdjECEBqqXf02w7WotWORLaGX nXswgZMGCyqGSIb3DQEJEAILMYGDoIGAMGwxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNo dXNldHRzMS4wLAYDVQQKEyVNYXNzYWNodXNldHRzIEluc3RpdHV0ZSBvZiBUZWNobm9sb2d5MRUw EwYDVQQLEwxDbGllbnQgQ0EgdjECEBqqXf02w7WotWORLaGXnXswDQYJKoZIhvcNAQEBBQAEggEA IPNlYJao5bgeZKmQDFhmKYB2OVPJU9HgeDJVGvRMx3XahnKLAs5pSZezF1IEHrBHmVBF1OpWAyRz zzHgVnT+X2XvG4oRYuIsH6/puHJaPY5WFBEZXIk4ryNcNWmkNgWsOJ08GMsCvsbyRPAhuY3VxfBu boP+5RYZNCtY6Dl/K/BOUi3mLjmhVb2QcrMbquActOqNp4jrvUPviQgJ0pqagGFttstmeyJ2uFSf MrfJxdeIweNRDIDp4tg4Clc+H9vaVbgeNKPhO4MdHXNnwxN3GPg6O3S2l5nvFxYwgCVzoLReQs8w /Mf/GZ6BDRvUNfdig8SRqF+SNvAlw1PvjtyoNwAAAAAAAA== --Apple-Mail=_C86FFFC5-E98E-4EDE-BA77-D364BA4C6AA7--