codemirror diff-match-path不同裝置、不同裝置狀態下的對比結果不穩定

azoux發表於2024-08-05

今天遇到一個問題,在使用codemirror對兩條文字內容進行對比時,有同事反饋在它的電腦上會顯示成:前面一半是正常顯示差異內容,而後面就變成了全部是新增的。

像這樣:

預期的對比結果是這樣:

我們觀察用於對比的兩個文字,實際上上面的文字都是去掉後面括號中的內容,對比結果不應該表現成全部刪除全部新增。

於是我開始在本地嘗試復現,很不幸,有時候可以,有時候不行

接著我開始查詢codemirror使用的對比庫,diff-match-patch,這個庫的對比方法的建構函式如下:

/**
 * Class containing the diff, match and patch methods.
 * @constructor
 */
var diff_match_patch = function() {

  // Defaults.
  // Redefine these in your program to override the defaults.
  // Number of seconds to map a diff before giving up (0 for infinity).
  this.Diff_Timeout = 0.5;
  // Cost of an empty edit operation in terms of edit characters.
  this.Diff_EditCost = 4;
  // At what point is no match declared (0.0 = perfection, 1.0 = very loose).
  this.Match_Threshold = 0.5;
  // How far to search for a match (0 = exact location, 1000+ = broad match).
  // A match this many characters away from the expected location will add
  // 1.0 to the score (0.0 is a perfect match).
  this.Match_Distance = 1000;
  // When deleting a large block of text (over ~64 characters), how close do
  // the contents have to be to match the expected contents. (0.0 = perfection,
  // 1.0 = very loose).  Note that Match_Threshold controls how closely the
  // end points of a delete need to match.
  this.Patch_DeleteThreshold = 0.5;
  // Chunk size for context length.
  this.Patch_Margin = 4;

  // The number of bits in an int.
  this.Match_MaxBits = 32;
};

看到這個bug,我首先懷疑是由於閾值,所以嘗試修改 Diff_ThresholdMatch_Distance 嘗試將它們調小,看是否能夠復現。很不幸,也不行。

接著我又去查了 diff-match-patch文件

注意到了 Diff_Timeout這個引數,文件中解釋了為什麼會有這個引數,主要是為了避免對比所耗費的時間,預設值是1s,超過1s為完成對比,剩餘部分就會以新增/刪除來返回。

引用一段文件中的解釋:

儘管此函式中使用了大量最佳化,但diff的計算可能需要一段時間。Diff_Timeout屬性可用於設定任何Diff的探索階段可能需要多少秒。預設值為1.0。值為0會禁用超時,並讓diff執行直到完成。如果diff超時,返回值仍然是一個有效的差值,儘管可能不是最佳值。

所以這個bug不一定所有人的裝置都能復現,即使是同一個的裝置,在不同的裝置狀況(cpu使用率、記憶體佔用)等情況下,也會有區別。在知道原因後,透過手動修改 Diff_Timeout 的值來嘗試復現bug就能成功了。

那麼如何解決呢,根據文件,我們可以設定一個較大的超時時間,來確保diff可以完成。或者設定為 0這樣就會讓diff執行直到結束。實際程式碼可以透過對DiffMatchPatch建構函式做一層包裹來實現:

  window.diff_match_patch = function () {
    const dmp = new DiffMatchPatch()
    // https://github.com/google/diff-match-patch/wiki/API#diff_maintext1-text2--diffs
    // 設定超時時間為0,禁用超時設定,直至diff執行結束
    dmp.Diff_Timeout = 0

    return dmp
  };

相關文章