Peritext:用於富文字協作的新型CRDT

banq發表於2021-11-24

Google Docs 等協作編輯器允許人們實時處理富文字文件,當使用者希望立即檢視彼此的更改時,這很方便。然而,有時人們更喜歡更非同步的協作方式,在這種方式下,他們可以暫時處理文件的私人副本,然後再分享他們的更新。支援 Google Docs 等服務的演算法並非旨在支援此用例。
在本文中,我們介紹了 Peritext,一種提供更大靈活性的富文字協作演算法:它允許使用者編輯文件的獨立副本,並提供一種機制,以保留使用者的儘可能的意圖。一旦版本被合併,該演算法保證所有使用者都朝著相同的合併結果收斂。
我們詳細分析了需要在協作富文字編輯器中處理的各種邊緣情況,並解釋了為什麼現有的純文字協作演算法無法正確處理它們。然後我們解釋 Peritext 如何處理這些問題,並演示我們演算法的原型實現。
。。。。
許多軟體開發人員都熟悉 Git 分支和拉取請求形式的非同步工作流。但是,Git 要求的手動合併衝突解決並不方便使用者使用,尤其是對於富文字(即帶有格式的文字)等複雜的檔案格式。
Google Docs ( Operational Transformation )使用的協作演算法假設文件具有單一的線性編輯時間線,該時間線由雲服務管理。為了支援類似 Git 的分支合併工作流的非同步協作,我們需要解決的一個關鍵問題是:我們如何將一個文件的任意兩個版本,它們已經獨立編輯過,並以一種保留的方式合併它們儘可能多地瞭解不同使用者編輯背後的意圖?
Peritext 是一種用於合併富文字文件版本的新穎演算法。它是一種無衝突複製資料型別(CRDT),保證如果兩個使用者獨立合併相同的兩個版本,他們將收斂到相同的結果。除了非同步協作之外,Peritext 還為本地優先的富文字編輯軟體提供了基礎,該軟體的優點包括允許使用者在裝置離線時繼續工作,併為使用者提供更大的隱私、所有權和對其檔案的代理權。
Peritext 不是用於非同步協作的完整系統:例如,它尚未視覺化文件版本之間的差異。此外,在本文中,我們只關注內聯格式,例如粗體、斜體、字型、文字顏色、連結和註釋,它們可能出現在單個文字段落中。在以後的文章中,我們將擴充套件我們的演算法以支援塊元素,例如標題、專案符號、塊引用和表格。儘管有這些限制,但 Peritext 是實現富文字非同步協作的重要一步。
 

合併難點:保留作者的意圖
當作者非同步協作時,演算法不可能總是完美地合併編輯。舉個例子,如果兩位編劇正在編輯一部電視節目的劇本,對一集的改動可能需要在未來的劇集中改變情節。由於演算法無法自動執行此操作,因此通常需要人工干預才能產生所需的最終結果。
但是,編寫工具仍然可以極大地幫助協作過程。當兩個使用者獨立編輯一個文件時,手動合併這些版本既乏味又容易出錯。我們需要一個書寫系統來幫助作者輕鬆且一致地執行這些合併。即使自動合併不完美,它也允許作者快速恢復彼此同步,並最大限度地減少他們花費在編寫工具上的時間。
在我們實現演算法來執行這種自動合併之前,我們首先需要為使用者意圖定義一個清晰的模型,並定義合併併發編輯時的預期結果。
  

明文插入
示例 1.讓我們從一個初步示例開始:對純文字的併發插入。想象一下 Alice 和 Bob 正在同時編輯一個文件,而不知道彼此的更改。也許 Alice 正在火車上離線編輯,或者 Bob 正在嘗試在私有分支中進行一些更改。
在任何一個使用者應用他們的更改之前,我們從這句話開始:
每個使用者在對方不知情的情況下,在句子中的某處插入一些文字:

  • 愛麗絲:The quick fox jumped.
  • 鮑勃:The fox jumped over the dog.

稍後,兩個使用者同步備份,我們需要合併他們的更改。直觀地,正確的合併結果是將它們的兩個插入保持在相同的位置:

The quick fox jumped over the dog.

Bob 在“jumped”這個詞之後插入了“␣over the dog”,所以這個詞在合併句子中的那個位置結束是有道理的。插入的字元應該保持在相對於它們被插入的上下文的相同位置;文字不應僅僅因為在文件的其他地方新增或刪除了某些詞而移動。現有的純文字協作演算法已經實現了這種行為。
 。。。
 

現有 CRDT 的侷限性

無衝突複製資料型別(CRDT) 是一種演算法,允許每個使用者編輯其本地文件副本,並確保不同使用者的副本可以乾淨地合併為一致的結果。對於純文字檔案有很多CRDT演算法,如RGA因果樹木YATAWOOTTreedocLogootLSEQ,以及各種其他。
在構建用於管理富文字的新 CRDT 之前,我們可能會問是否可以簡單地使用現有的。事實證明,雖然現有 CRDT 的一些想法對於建模格式化文字非常有用,但以天真的方式應用現有演算法並不能產生上面列出的所需行為。在本節中,我們簡要描述了使用現有 CRDT 表示富文字的三種方法,並強調了它們的問題。
。。。。
 

Peritext:富文字 CRDT
我們現在介紹我們在 Peritext 中進行富文字協作所採用的方法。我們分四部分描述我們的演算法:

  • 使用現有的純文字 CRDT 表示富文字文件的文字內容
  • 生成表示格式更改的 CRDT 操作
  • 應用這些操作來生成內部文件狀態
  • 根據內部狀態匯出適合文字編輯器的文件

。。。。。

 

原型實現
我們已經實現了 Peritext CRDT 的一個工作原型,它顯示在本文的頂部執行。它是用 TypeScript 實現的,程式碼是開源的。我們的程式碼是一個自包含的實現,它擴充套件了Automerge CRDT 庫的簡化版本,我們希望將來將我們的演算法整合回 Automerge。該實現包含對本工作中描述的許多特定場景的測試,以及確保在大量隨機編輯跟蹤中收斂的自動生成測試套件。
對於編輯器 UI,我們選擇基於ProseMirror構建,這是一個流行的庫,已在許多協作編輯上下文中使用。它的模組化設計為我們提供了必要的靈活性,可以在適當的時候干預編輯器的資料流。我們還希望我們的 CRDT 能夠與其他編輯器 UI 很好地整合,因為我們沒有專門為 ProseMirror 專門設計。
目前,我們的實現有點專門針對本文中顯示的一小部分標記:粗體、斜體、連結和註釋。但是,我們打算將它們作為一組具有代表性的格式標記,並希望它們的行為也能擴充套件到其他型別的使用者可配置標記。
 

結論
我們相信富文字的 CRDT 可以啟用新的寫作工作流,具有強大的版本控制和對同步和非同步協作的支援。在這項工作中,我們已經表明,在將編輯合併到富文字文件時,傳統的純文字 CRDT 無法保留作者的意圖。我們開發了一個支援重疊內聯格式的富文字 CRDT,並展示瞭如何有效地實現它。
Peritext 只是實現非同步協作系統的第一步:它只是允許自動合併富文字文件的兩個版本。要實現非同步協作,將需要在視覺化編輯歷史和更改、突出顯示衝突以供手動解決以及其他功能方面進行進一步的工作。由於 Peritext 的工作原理是將文件的編輯歷史記錄為操作日誌,因此它為將來實現這些進一步的功能提供了良好的基礎。
在文字中可以找到許多型別的標記,它們可以建模為跨度,它們的語義各不相同。有些標記可以共存,有些則不能。此外,使用者對標記行為的期望可能因型別而異。我們相信 Peritext 可以捕捉到這些意圖,但仔細的編輯器整合對於實現使用者期望至關重要。
內聯格式對於像 Trello 卡片描述這樣的短文字塊就足夠了,但較長的文件通常依賴於更復雜的塊元素或分層格式,例如巢狀的專案符號點,而 Peritext 目前沒有建模。需要進一步工作以確保可以合併對專案符號列表等塊結構的編輯,同時保留作者意圖。分層格式結構提出了關於意圖保留的新問題——例如,當使用者同時拆分、加入和移動塊元素時會發生什麼?
未來探索的另一個領域是在文件中移動和複製文字。如果兩個人同時將相同的文字剪下貼上到文件的不同位置,然後進一步修改貼上的文字,最明智的結果是什麼?
與我們交談的作家喜歡實時協作寫作工具的便利性,但也描述了非同步編輯的各種變通方法。例如,一位受訪者使用文件的個人副本在編輯期間保護他們的隱私。其他人缺乏版本控制工具,依靠手動加粗的文字來識別編輯器所做的更改。這些變通辦法源於缺乏系統的變更跟蹤導致的潛在技術匱乏。我們希望 Peritext 和其他用於富文字的 CRDT 將為這些使用者啟用新的工作流程,這是不可能在現有資料結構之上構建以儲存文字文件的。使用者可以嘗試自己不同的長期執行分支,並透過強大的比較檢視輕鬆地將它們合併在一起。人們可以選擇私下工作,或阻止其他人做出的令人分心的變化。與其將文件歷史視為版本的線性序列,我們還可以將其視為在細粒度更改資料庫之上的大量投影檢視。


 

相關文章