最近折騰了一下 Cirru Editor 的新版本: Cumulo Editor,
相關代碼和演示能夠在這些地址找到:
https://github.com/Cirru/cumu...
https://github.com/mvc-works/...
http://weibo.com/1651843872/F...
https://twitter.com/jiyinyiyo...git
網上能找到關於協同編輯的說明:
https://en.wikipedia.org/wiki...github
A collaborative editor is a form of collaborative software application that allows several people to edit a computer file using different computers, a practice called collaborative editing.算法
There are two types of collaborative editing: real-time and non-real-time. In real-time collaborative editing (RTCE), users can edit the same file simultaneously, whereas in Non-real-time collaborative editing, the users do not edit the same file at the same time (similar to revision control systems). Collaborative real-time editors generally permit both the above modes of editing in any given instance.編程
一個有名的例子是 Google Docs, 它支持多人同時編輯.
你能夠看到多我的的光標同時顯示在網頁上, 在各自的位置編輯文檔.
這是一種很高效的合做寫文檔的方式, 能夠很是快速基於對方的結果迭代.segmentfault
要作到協同編程, 咱們就要對代碼提供類似的功能, 容許多我的編輯.
首先咱們須要有服務器做爲操做順序的協調者, 用來保存和同步的狀態,
接着須要有 Diff 算法, 使得經過網絡傳輸的信息是最精簡的,
而後還要有 Patch 算法, 而且在 Patch 先後光標的行爲應當一致.
每一個環節到要打磨到必定的程度, 不然很容易影響到正常編寫代碼的過程.
實際上基於文本的代碼同步會遇到不少問題, 設置對編輯器原有功能也會形成影響.數組
Cirru 裏採用了同樣的思路, 代碼的核心形態並非文本, 而是語法樹,
並且完成了 Stack Editor 這樣一個能比較快速地編輯代碼的方案,
Stack Editor 基於 Clojure Macros 能夠從語法樹生成 Clojure 代碼,
雖然不能生成所有的 Clojure 語法, 好比特殊符號, 好比 Macro,
可是能夠覆蓋很大一部分網頁開發須要的功能, 從而提供完整方案.
因爲 Stack Editor 是基於網頁來作代碼的編輯, 也就避開了文本編輯器的慣用方案.服務器
Cirru 的語法樹看起來是 ["def" "a" ["+" "1" "2"]]
這樣一個數組的形態,
這個形態和 S 表達式對應, 也就是 Lisp 社區普遍接受的表達式方案.
對於這樣一個語法樹, 經過肯定操做的類型, 以及操做的位置, 就能進行操做,
並且用戶能夠有個明確的座標, 做爲編輯時表達和肯定編輯位置的方案.
基於這樣的結構, 能夠獲得很精簡的語法樹, 也便於在網頁上渲染.微信
除了表達式能夠被 Cirru 記法替代, 整個源碼文件結構均可以被改寫,
好比文件抽象爲命名空間, 用一個 HashMap 存儲,
而後每一個文件分紅 ns
defs
proc
三部分, 其中 proc
表示腳本代碼,
因此一個項目的源碼看上去就像是這樣:網絡
{:package "app" :files {"app.main" {:ns ["ns" "app.main"] :defs {"main!" ["defn" "main!" []]} :proc []} "app.lib" {:ns ["ns" "app.lib"] :defs {} :proc []}}}
通過這樣的思惟轉化, 協同編程的問題就被從新理解了,
再也不問怎樣同步文本, 而是說, 這棵樹怎樣進行修改的同步?
樹的同步是個相對簡單的問題, 由於操做樹的一個分支, 一般不會影響到其餘分支,
咱們只要定義好節點, 定義好操做, 而後這些操做有肯定的順序, 就能夠了.
以後經過服務器講操做同步到多個不一樣的客戶端, 就能造成同步的假想.數據結構
Cumulo 是我基於 React 設計一個經過 tree diff/patch 來跨客戶端同步信息的方案,
React 有一份 Virtual DOM, 而後能夠經過 Diff 分發而後 Patch 到不一樣的客戶端,
而 Cumulo 則是在服務端生成 Diff, 讓客戶端精確地同步須要的數據,
這個特性也就適合運用在 Cirru 語法樹的同步上面, 服務端維護好樹, 客戶端同步.
這裏不深刻講 Cumulo, 能夠看我以前的內容瞭解 Cumulo:
https://segmentfault.com/t/cu...
https://github.com/Cumulo/cum...
這裏有個 tree diff 的效率問題, 由於數組的 Diff 其實比較低效,
由於數組比較難探測子節點之間如何匹配, 須要一個 key 來輔助,
好比 React 當中就藉助一個 key 來判斷子節點, 從而提升效率.
另外一個問題是數組受到位置影響比較明顯, 好比前面插入元素, 後面的座標就改變了.
而 HashMap 相對來講是更穩定的實現, 只在祖先路徑更改時座標會改變.
因而我設計了一個新的數據結構, 原先 Cirru 表達式的格式是:
["def" "f" ["x" "y"]]
在新的格式當中存儲成這樣,
{:type :expr :data {"T" {:type :leaf, :text "def"} "j" {:type :leaf, :text "f"} "r" {:type :expr :data {"T" {:type :leaf, :text "x"} "j" {:type :leaf, :text "y"}}}}}
爲了方便理解, 能夠認爲是這樣一個結構:
{"T" "def" "j" "f" "r" {"T" "x", "j" "y"}}
能夠看到表達式的子節點變成了用 HashMap 存儲, 增長了 key,
這個 key 的字典序是和表達式當中的子節點位置對應的,
在 Cirru 中咱們會遇到任意位置可能插入新的節點, 就須要生成新的 key,
並且這個新的 key 要知足正確的字典序, 好比在兩個中間插入, 或者頭尾插入.
關於這個 key 的算法, 我實現了一個簡單的類庫來達成, 能夠了解:
http://clojure-china.org/t/bi...
總之轉化爲 HashMap 以後, 樹的 Diff 就快多了, 並且衝突也會變少,
因而一會兒就繞過了不少問題, 協同編輯也就在必定範圍內可行了.
Cumulo Editor 總體基於 Stack Editor 的功能作了從新實現,
主打的是協同編輯, 同時也支持其餘一些簡單的編碼輔助功能,
好比基於語法樹跳轉到定義, 快速在表達式之間跳轉等等.
沒有實現 Stack Editor 當中查找使用位置的功能, 之後也許會加上.
關於仍是關於協同編輯部分, 目前沒有作深度的測試,
好比我把服務器部署在阿里雲香港, 這個延時徹底是在可接受範圍內的.
表達式的操做會等待服務器響應, 符號的修改是本地實時的,
至少目前測試看來, 編輯方面的流暢度沒有明顯的問題.
可能擔憂的仍是人數和代碼裏增長以後, 編輯器是夠延時增長引起問題.
另外一個問題是協同編輯會遇到搶佔文件保存的問題,
也就是一個問保存文件, 致使另外一我的代碼沒完成就保存, 從而出錯.
我認爲這一點對實際開發的影響會不小, 甚至須要增長功能來規範.
固然如今也很難說, 我一我的比較搞不出那麼多幺蛾子來,
因此若是有人感興趣想一塊兒測試, 微信上加我, 而且註明 "Cirru",
我會試着積累一下經驗, 看下這方面究竟會有多大的問題.
因爲整個方案基於 ClojureScript, 因此須要先有一些瞭解再開始嘗試.
這東西首先很突破常規, 但比較難說是否派的上用場,畢竟協同編輯帶啦衝突的話, 可能我的的開發效率降低也未可知,可是對於未知呢, 老是不能輕易下定論的, 花時間去探索一下總能夠吧 :)