@tobq/boardclip 0.0.0-stage → 2.1.108

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -2
  3. package/assets/boardclip-icon.png +0 -0
  4. package/bin/boardclip.js +43 -0
  5. package/editor-preload.js +71 -0
  6. package/editor.html +220 -0
  7. package/icon.png +0 -0
  8. package/icon@2x.png +0 -0
  9. package/iconTemplate.png +0 -0
  10. package/iconTemplate@2x.png +0 -0
  11. package/index.html +1339 -0
  12. package/lib/appearance.js +183 -0
  13. package/lib/auto-update.js +306 -0
  14. package/lib/backup-worker.js +46 -0
  15. package/lib/backup.js +275 -0
  16. package/lib/blob-field-store.js +275 -0
  17. package/lib/blob-store.js +68 -0
  18. package/lib/build-info.js +89 -0
  19. package/lib/clip-blob-store.js +161 -0
  20. package/lib/clip-revision.js +81 -0
  21. package/lib/clipboard-capture.js +95 -0
  22. package/lib/clipboard-follow.js +98 -0
  23. package/lib/clipboard-model.js +1235 -0
  24. package/lib/cloud-accounts.js +250 -0
  25. package/lib/conflict-model.js +186 -0
  26. package/lib/control-client.js +87 -0
  27. package/lib/control-server.js +149 -0
  28. package/lib/diagnostics.js +121 -0
  29. package/lib/drag-files.js +48 -0
  30. package/lib/ensure-directory.js +56 -0
  31. package/lib/fork-names.js +35 -0
  32. package/lib/fs-probe.js +132 -0
  33. package/lib/history-feed.js +48 -0
  34. package/lib/hmac-auth.js +78 -0
  35. package/lib/macos-hotkey.js +297 -0
  36. package/lib/macos-paste.js +112 -0
  37. package/lib/mcp-core.js +270 -0
  38. package/lib/mcp-installers.js +284 -0
  39. package/lib/mcp-paths.js +99 -0
  40. package/lib/p2p-crypto.js +54 -0
  41. package/lib/p2p-discovery.js +191 -0
  42. package/lib/quick-paste.js +187 -0
  43. package/lib/retention.js +43 -0
  44. package/lib/sync-delta.js +227 -0
  45. package/lib/sync-journal.js +181 -0
  46. package/lib/sync-paths.js +73 -0
  47. package/lib/tailscale.js +80 -0
  48. package/lib/text-blob-store.js +38 -0
  49. package/lib/windows-clipboard.js +213 -0
  50. package/lib/windows-console.js +63 -0
  51. package/lib/windows-dwm.js +183 -0
  52. package/lib/windows-hook-worker.js +236 -0
  53. package/lib/windows-hook.js +91 -0
  54. package/lib/windows-paste.js +185 -0
  55. package/main.js +7540 -0
  56. package/mcp/boardclip-mcp.js +255 -0
  57. package/mcp-approval-preload.js +22 -0
  58. package/mcp-approval.html +218 -0
  59. package/package.json +55 -4
  60. package/preload.js +106 -0
  61. package/site/index.html +1624 -0
  62. package/site/shared/clip-search.js +2330 -0
  63. package/site/shared/clipboard-popup.css +1046 -0
  64. package/site/shared/clipboard-tokens.css +326 -0
  65. package/site/shared/clipboard-ui-core.js +6921 -0
  66. package/site/shared/clipboard-window.css +36 -0
  67. package/site/shared/vendor/cm5/README.md +52 -0
  68. package/site/shared/vendor/cm5/codemirror.css +344 -0
  69. package/site/shared/vendor/cm5/codemirror.js +9884 -0
  70. package/site/shared/vendor/cm5/diff-match-patch.js +2228 -0
  71. package/site/shared/vendor/cm5/merge.css +119 -0
  72. package/site/shared/vendor/cm5/merge.js +1182 -0
  73. package/site/shared/vendor/fonts/README.md +19 -0
  74. package/site/shared/vendor/fonts/material-symbols-rounded.woff2 +0 -0
  75. package/viewer-preload.js +51 -0
  76. package/viewer.html +129 -0
@@ -0,0 +1,1182 @@
1
+ // CodeMirror, copyright (c) by Marijn Haverbeke and others
2
+ // Distributed under an MIT license: https://codemirror.net/5/LICENSE
3
+
4
+ // declare global: diff_match_patch, DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL
5
+
6
+ (function(mod) {
7
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
8
+ mod(require("../../lib/codemirror")); // Note non-packaged dependency diff_match_patch
9
+ else if (typeof define == "function" && define.amd) // AMD
10
+ define(["../../lib/codemirror", "diff_match_patch"], mod);
11
+ else // Plain browser env
12
+ mod(CodeMirror);
13
+ })(function(CodeMirror) {
14
+ "use strict";
15
+ var Pos = CodeMirror.Pos;
16
+ var svgNS = "http://www.w3.org/2000/svg";
17
+
18
+ function DiffView(mv, type) {
19
+ this.mv = mv;
20
+ this.type = type;
21
+ this.classes = type == "left"
22
+ ? {chunk: "CodeMirror-merge-l-chunk",
23
+ start: "CodeMirror-merge-l-chunk-start",
24
+ end: "CodeMirror-merge-l-chunk-end",
25
+ insert: "CodeMirror-merge-l-inserted",
26
+ del: "CodeMirror-merge-l-deleted",
27
+ connect: "CodeMirror-merge-l-connect"}
28
+ : {chunk: "CodeMirror-merge-r-chunk",
29
+ start: "CodeMirror-merge-r-chunk-start",
30
+ end: "CodeMirror-merge-r-chunk-end",
31
+ insert: "CodeMirror-merge-r-inserted",
32
+ del: "CodeMirror-merge-r-deleted",
33
+ connect: "CodeMirror-merge-r-connect"};
34
+ }
35
+
36
+ DiffView.prototype = {
37
+ constructor: DiffView,
38
+ init: function(pane, orig, options) {
39
+ this.edit = this.mv.edit;
40
+ ;(this.edit.state.diffViews || (this.edit.state.diffViews = [])).push(this);
41
+ this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options)));
42
+ if (this.mv.options.connect == "align") {
43
+ if (!this.edit.state.trackAlignable) this.edit.state.trackAlignable = new TrackAlignable(this.edit)
44
+ this.orig.state.trackAlignable = new TrackAlignable(this.orig)
45
+ }
46
+ this.lockButton.title = this.edit.phrase("Toggle locked scrolling");
47
+ this.lockButton.setAttribute("aria-label", this.lockButton.title);
48
+
49
+ this.orig.state.diffViews = [this];
50
+ var classLocation = options.chunkClassLocation || "background";
51
+ if (Object.prototype.toString.call(classLocation) != "[object Array]") classLocation = [classLocation]
52
+ this.classes.classLocation = classLocation
53
+
54
+ this.diff = getDiff(asString(orig), asString(options.value), this.mv.options.ignoreWhitespace);
55
+ this.chunks = getChunks(this.diff);
56
+ this.diffOutOfDate = this.dealigned = false;
57
+ this.needsScrollSync = null
58
+
59
+ this.showDifferences = options.showDifferences !== false;
60
+ },
61
+ registerEvents: function(otherDv) {
62
+ this.forceUpdate = registerUpdate(this);
63
+ setScrollLock(this, true, false);
64
+ registerScroll(this, otherDv);
65
+ },
66
+ setShowDifferences: function(val) {
67
+ val = val !== false;
68
+ if (val != this.showDifferences) {
69
+ this.showDifferences = val;
70
+ this.forceUpdate("full");
71
+ }
72
+ }
73
+ };
74
+
75
+ function ensureDiff(dv) {
76
+ if (dv.diffOutOfDate) {
77
+ dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue(), dv.mv.options.ignoreWhitespace);
78
+ dv.chunks = getChunks(dv.diff);
79
+ dv.diffOutOfDate = false;
80
+ CodeMirror.signal(dv.edit, "updateDiff", dv.diff);
81
+ }
82
+ }
83
+
84
+ var updating = false;
85
+ function registerUpdate(dv) {
86
+ var edit = {from: 0, to: 0, marked: []};
87
+ var orig = {from: 0, to: 0, marked: []};
88
+ var debounceChange, updatingFast = false;
89
+ function update(mode) {
90
+ updating = true;
91
+ updatingFast = false;
92
+ if (mode == "full") {
93
+ if (dv.svg) clear(dv.svg);
94
+ if (dv.copyButtons) clear(dv.copyButtons);
95
+ clearMarks(dv.edit, edit.marked, dv.classes);
96
+ clearMarks(dv.orig, orig.marked, dv.classes);
97
+ edit.from = edit.to = orig.from = orig.to = 0;
98
+ }
99
+ ensureDiff(dv);
100
+ if (dv.showDifferences) {
101
+ // BOARDCLIP PATCH: the marks come from the word-level diff (bcMarkDiff);
102
+ // chunks keep diff_main's own (see bcWordDiff).
103
+ var markDiff = bcMarkDiff(dv);
104
+ updateMarks(dv.edit, markDiff, edit, DIFF_INSERT, dv.classes);
105
+ updateMarks(dv.orig, markDiff, orig, DIFF_DELETE, dv.classes);
106
+ }
107
+
108
+ if (dv.mv.options.connect == "align")
109
+ alignChunks(dv);
110
+ makeConnections(dv);
111
+ if (dv.needsScrollSync != null) syncScroll(dv, dv.needsScrollSync)
112
+
113
+ updating = false;
114
+ }
115
+ function setDealign(fast) {
116
+ if (updating) return;
117
+ dv.dealigned = true;
118
+ set(fast);
119
+ }
120
+ function set(fast) {
121
+ if (updating || updatingFast) return;
122
+ clearTimeout(debounceChange);
123
+ if (fast === true) updatingFast = true;
124
+ debounceChange = setTimeout(update, fast === true ? 20 : 250);
125
+ }
126
+ function change(_cm, change) {
127
+ if (!dv.diffOutOfDate) {
128
+ dv.diffOutOfDate = true;
129
+ edit.from = edit.to = orig.from = orig.to = 0;
130
+ }
131
+ // Update faster when a line was added/removed
132
+ setDealign(change.text.length - 1 != change.to.line - change.from.line);
133
+ }
134
+ function swapDoc() {
135
+ dv.diffOutOfDate = true;
136
+ dv.dealigned = true;
137
+ update("full");
138
+ }
139
+ dv.edit.on("change", change);
140
+ dv.orig.on("change", change);
141
+ dv.edit.on("swapDoc", swapDoc);
142
+ dv.orig.on("swapDoc", swapDoc);
143
+ if (dv.mv.options.connect == "align") {
144
+ CodeMirror.on(dv.edit.state.trackAlignable, "realign", setDealign)
145
+ CodeMirror.on(dv.orig.state.trackAlignable, "realign", setDealign)
146
+ }
147
+ dv.edit.on("viewportChange", function() { set(false); });
148
+ dv.orig.on("viewportChange", function() { set(false); });
149
+ update();
150
+ return update;
151
+ }
152
+
153
+ function registerScroll(dv, otherDv) {
154
+ dv.edit.on("scroll", function() {
155
+ syncScroll(dv, true) && makeConnections(dv);
156
+ });
157
+ dv.orig.on("scroll", function() {
158
+ syncScroll(dv, false) && makeConnections(dv);
159
+ if (otherDv) syncScroll(otherDv, true) && makeConnections(otherDv);
160
+ });
161
+ }
162
+
163
+ function syncScroll(dv, toOrig) {
164
+ // Change handler will do a refresh after a timeout when diff is out of date
165
+ if (dv.diffOutOfDate) {
166
+ if (dv.lockScroll && dv.needsScrollSync == null) dv.needsScrollSync = toOrig
167
+ return false
168
+ }
169
+ dv.needsScrollSync = null
170
+ if (!dv.lockScroll) return true;
171
+ var editor, other, now = +new Date;
172
+ if (toOrig) { editor = dv.edit; other = dv.orig; }
173
+ else { editor = dv.orig; other = dv.edit; }
174
+ // Don't take action if the position of this editor was recently set
175
+ // (to prevent feedback loops)
176
+ if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 250 > now) return false;
177
+
178
+ var sInfo = editor.getScrollInfo();
179
+ if (dv.mv.options.connect == "align") {
180
+ targetPos = sInfo.top;
181
+ } else {
182
+ var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen;
183
+ var mid = editor.lineAtHeight(midY, "local");
184
+ var around = chunkBoundariesAround(dv.chunks, mid, toOrig);
185
+ var off = getOffsets(editor, toOrig ? around.edit : around.orig);
186
+ var offOther = getOffsets(other, toOrig ? around.orig : around.edit);
187
+ var ratio = (midY - off.top) / (off.bot - off.top);
188
+ var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top);
189
+
190
+ var botDist, mix;
191
+ // Some careful tweaking to make sure no space is left out of view
192
+ // when scrolling to top or bottom.
193
+ if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) {
194
+ targetPos = targetPos * mix + sInfo.top * (1 - mix);
195
+ } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) {
196
+ var otherInfo = other.getScrollInfo();
197
+ var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos;
198
+ if (botDistOther > botDist && (mix = botDist / halfScreen) < 1)
199
+ targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix);
200
+ }
201
+ }
202
+
203
+ other.scrollTo(sInfo.left, targetPos);
204
+ other.state.scrollSetAt = now;
205
+ other.state.scrollSetBy = dv;
206
+ return true;
207
+ }
208
+
209
+ function getOffsets(editor, around) {
210
+ var bot = around.after;
211
+ if (bot == null) bot = editor.lastLine() + 1;
212
+ return {top: editor.heightAtLine(around.before || 0, "local"),
213
+ bot: editor.heightAtLine(bot, "local")};
214
+ }
215
+
216
+ function setScrollLock(dv, val, action) {
217
+ dv.lockScroll = val;
218
+ if (val && action != false) syncScroll(dv, DIFF_INSERT) && makeConnections(dv);
219
+ (val ? CodeMirror.addClass : CodeMirror.rmClass)(dv.lockButton, "CodeMirror-merge-scrolllock-enabled");
220
+ }
221
+
222
+ // Updating the marks for editor content
223
+
224
+ function removeClass(editor, line, classes) {
225
+ var locs = classes.classLocation
226
+ for (var i = 0; i < locs.length; i++) {
227
+ editor.removeLineClass(line, locs[i], classes.chunk);
228
+ editor.removeLineClass(line, locs[i], classes.start);
229
+ editor.removeLineClass(line, locs[i], classes.end);
230
+ }
231
+ }
232
+
233
+ function clearMarks(editor, arr, classes) {
234
+ for (var i = 0; i < arr.length; ++i) {
235
+ var mark = arr[i];
236
+ if (mark instanceof CodeMirror.TextMarker)
237
+ mark.clear();
238
+ else if (mark.parent)
239
+ removeClass(editor, mark, classes);
240
+ }
241
+ arr.length = 0;
242
+ }
243
+
244
+ // FIXME maybe add a margin around viewport to prevent too many updates
245
+ function updateMarks(editor, diff, state, type, classes) {
246
+ var vp = editor.getViewport();
247
+ editor.operation(function() {
248
+ if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
249
+ clearMarks(editor, state.marked, classes);
250
+ markChanges(editor, diff, type, state.marked, vp.from, vp.to, classes);
251
+ state.from = vp.from; state.to = vp.to;
252
+ } else {
253
+ if (vp.from < state.from) {
254
+ markChanges(editor, diff, type, state.marked, vp.from, state.from, classes);
255
+ state.from = vp.from;
256
+ }
257
+ if (vp.to > state.to) {
258
+ markChanges(editor, diff, type, state.marked, state.to, vp.to, classes);
259
+ state.to = vp.to;
260
+ }
261
+ }
262
+ });
263
+ }
264
+
265
+ function addClass(editor, lineNr, classes, main, start, end) {
266
+ var locs = classes.classLocation, line = editor.getLineHandle(lineNr);
267
+ for (var i = 0; i < locs.length; i++) {
268
+ if (main) editor.addLineClass(line, locs[i], classes.chunk);
269
+ if (start) editor.addLineClass(line, locs[i], classes.start);
270
+ if (end) editor.addLineClass(line, locs[i], classes.end);
271
+ }
272
+ return line;
273
+ }
274
+
275
+ function markChanges(editor, diff, type, marks, from, to, classes) {
276
+ var pos = Pos(0, 0);
277
+ var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1));
278
+ var cls = type == DIFF_DELETE ? classes.del : classes.insert;
279
+ function markChunk(start, end) {
280
+ var bfrom = Math.max(from, start), bto = Math.min(to, end);
281
+ for (var i = bfrom; i < bto; ++i)
282
+ marks.push(addClass(editor, i, classes, true, i == start, i == end - 1));
283
+ // When the chunk is empty, make sure a horizontal line shows up
284
+ if (start == end && bfrom == end && bto == end) {
285
+ if (bfrom)
286
+ marks.push(addClass(editor, bfrom - 1, classes, false, false, true));
287
+ else
288
+ marks.push(addClass(editor, bfrom, classes, false, true, false));
289
+ }
290
+ }
291
+
292
+ var chunkStart = 0, pending = false;
293
+ for (var i = 0; i < diff.length; ++i) {
294
+ var part = diff[i], tp = part[0], str = part[1];
295
+ if (tp == DIFF_EQUAL) {
296
+ var cleanFrom = pos.line + (startOfLineClean(diff, i) ? 0 : 1);
297
+ moveOver(pos, str);
298
+ var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0);
299
+ if (cleanTo > cleanFrom) {
300
+ if (pending) { markChunk(chunkStart, cleanFrom); pending = false }
301
+ chunkStart = cleanTo;
302
+ }
303
+ } else {
304
+ // BOARDCLIP PATCH: part[3] = a whitespace-only edit under
305
+ // ignoreWhitespace (bcWordDiff): it moves the position, nothing more.
306
+ if (!part[3]) pending = true
307
+ if (tp == type) {
308
+ var end = moveOver(pos, str, true);
309
+ var a = posMax(top, pos), b = posMin(bot, end);
310
+ if (!posEq(a, b) && !part[2] && !part[3]) // BOARDCLIP PATCH: whole-line edits get the chunk wash only
311
+ marks.push(editor.markText(a, b, {className: cls}));
312
+ pos = end;
313
+ }
314
+ }
315
+ }
316
+ if (pending) markChunk(chunkStart, pos.line + 1);
317
+ }
318
+
319
+ // Updating the gap between editor and original
320
+
321
+ function makeConnections(dv) {
322
+ if (!dv.showDifferences) return;
323
+
324
+ if (dv.svg) {
325
+ clear(dv.svg);
326
+ var w = dv.gap.offsetWidth;
327
+ attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight);
328
+ }
329
+ if (dv.copyButtons) clear(dv.copyButtons);
330
+
331
+ var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport();
332
+ var outerTop = dv.mv.wrap.getBoundingClientRect().top
333
+ var sTopEdit = outerTop - dv.edit.getScrollerElement().getBoundingClientRect().top + dv.edit.getScrollInfo().top
334
+ var sTopOrig = outerTop - dv.orig.getScrollerElement().getBoundingClientRect().top + dv.orig.getScrollInfo().top;
335
+ for (var i = 0; i < dv.chunks.length; i++) {
336
+ var ch = dv.chunks[i];
337
+ if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from &&
338
+ ch.origFrom <= vpOrig.to && ch.origTo >= vpOrig.from)
339
+ drawConnectorsForChunk(dv, ch, sTopOrig, sTopEdit, w);
340
+ }
341
+ }
342
+
343
+ function getMatchingOrigLine(editLine, chunks) {
344
+ var editStart = 0, origStart = 0;
345
+ for (var i = 0; i < chunks.length; i++) {
346
+ var chunk = chunks[i];
347
+ if (chunk.editTo > editLine && chunk.editFrom <= editLine) return null;
348
+ if (chunk.editFrom > editLine) break;
349
+ editStart = chunk.editTo;
350
+ origStart = chunk.origTo;
351
+ }
352
+ return origStart + (editLine - editStart);
353
+ }
354
+
355
+ // Combines information about chunks and widgets/markers to return
356
+ // an array of lines, in a single editor, that probably need to be
357
+ // aligned with their counterparts in the editor next to it.
358
+ function alignableFor(cm, chunks, isOrig) {
359
+ var tracker = cm.state.trackAlignable
360
+ var start = cm.firstLine(), trackI = 0
361
+ var result = []
362
+ for (var i = 0;; i++) {
363
+ var chunk = chunks[i]
364
+ var chunkStart = !chunk ? 1e9 : isOrig ? chunk.origFrom : chunk.editFrom
365
+ for (; trackI < tracker.alignable.length; trackI += 2) {
366
+ var n = tracker.alignable[trackI] + 1
367
+ if (n <= start) continue
368
+ if (n <= chunkStart) result.push(n)
369
+ else break
370
+ }
371
+ if (!chunk) break
372
+ result.push(start = isOrig ? chunk.origTo : chunk.editTo)
373
+ }
374
+ return result
375
+ }
376
+
377
+ // Given information about alignable lines in two editors, fill in
378
+ // the result (an array of three-element arrays) to reflect the
379
+ // lines that need to be aligned with each other.
380
+ function mergeAlignable(result, origAlignable, chunks, setIndex) {
381
+ var rI = 0, origI = 0, chunkI = 0, diff = 0
382
+ outer: for (;; rI++) {
383
+ var nextR = result[rI], nextO = origAlignable[origI]
384
+ if (!nextR && nextO == null) break
385
+
386
+ var rLine = nextR ? nextR[0] : 1e9, oLine = nextO == null ? 1e9 : nextO
387
+ while (chunkI < chunks.length) {
388
+ var chunk = chunks[chunkI]
389
+ if (chunk.origFrom <= oLine && chunk.origTo > oLine) {
390
+ origI++
391
+ rI--
392
+ continue outer;
393
+ }
394
+ if (chunk.editTo > rLine) {
395
+ if (chunk.editFrom <= rLine) continue outer;
396
+ break
397
+ }
398
+ diff += (chunk.origTo - chunk.origFrom) - (chunk.editTo - chunk.editFrom)
399
+ chunkI++
400
+ }
401
+ if (rLine == oLine - diff) {
402
+ nextR[setIndex] = oLine
403
+ origI++
404
+ } else if (rLine < oLine - diff) {
405
+ nextR[setIndex] = rLine + diff
406
+ } else {
407
+ var record = [oLine - diff, null, null]
408
+ record[setIndex] = oLine
409
+ result.splice(rI, 0, record)
410
+ origI++
411
+ }
412
+ }
413
+ }
414
+
415
+ function findAlignedLines(dv, other) {
416
+ var alignable = alignableFor(dv.edit, dv.chunks, false), result = []
417
+ if (other) for (var i = 0, j = 0; i < other.chunks.length; i++) {
418
+ var n = other.chunks[i].editTo
419
+ while (j < alignable.length && alignable[j] < n) j++
420
+ if (j == alignable.length || alignable[j] != n) alignable.splice(j++, 0, n)
421
+ }
422
+ for (var i = 0; i < alignable.length; i++)
423
+ result.push([alignable[i], null, null])
424
+
425
+ mergeAlignable(result, alignableFor(dv.orig, dv.chunks, true), dv.chunks, 1)
426
+ if (other)
427
+ mergeAlignable(result, alignableFor(other.orig, other.chunks, true), other.chunks, 2)
428
+
429
+ return result
430
+ }
431
+
432
+ function alignChunks(dv, force) {
433
+ if (!dv.dealigned && !force) return;
434
+ if (!dv.orig.curOp) return dv.orig.operation(function() {
435
+ alignChunks(dv, force);
436
+ });
437
+
438
+ dv.dealigned = false;
439
+ var other = dv.mv.left == dv ? dv.mv.right : dv.mv.left;
440
+ if (other) {
441
+ ensureDiff(other);
442
+ other.dealigned = false;
443
+ }
444
+ var linesToAlign = findAlignedLines(dv, other);
445
+
446
+ // Clear old aligners
447
+ var aligners = dv.mv.aligners;
448
+ for (var i = 0; i < aligners.length; i++)
449
+ aligners[i].clear();
450
+ aligners.length = 0;
451
+
452
+ var cm = [dv.edit, dv.orig], scroll = [], offset = []
453
+ if (other) cm.push(other.orig);
454
+ for (var i = 0; i < cm.length; i++) {
455
+ scroll.push(cm[i].getScrollInfo().top);
456
+ offset.push(-cm[i].getScrollerElement().getBoundingClientRect().top)
457
+ }
458
+
459
+ if (offset[0] != offset[1] || cm.length == 3 && offset[1] != offset[2])
460
+ alignLines(cm, offset, [0, 0, 0], aligners)
461
+ for (var ln = 0; ln < linesToAlign.length; ln++)
462
+ alignLines(cm, offset, linesToAlign[ln], aligners);
463
+
464
+ for (var i = 0; i < cm.length; i++)
465
+ cm[i].scrollTo(null, scroll[i]);
466
+ }
467
+
468
+ function alignLines(cm, cmOffset, lines, aligners) {
469
+ var maxOffset = -1e8, offset = [];
470
+ for (var i = 0; i < cm.length; i++) if (lines[i] != null) {
471
+ var off = cm[i].heightAtLine(lines[i], "local") - cmOffset[i];
472
+ offset[i] = off;
473
+ maxOffset = Math.max(maxOffset, off);
474
+ }
475
+ for (var i = 0; i < cm.length; i++) if (lines[i] != null) {
476
+ var diff = maxOffset - offset[i];
477
+ if (diff > 1)
478
+ aligners.push(padAbove(cm[i], lines[i], diff));
479
+ }
480
+ }
481
+
482
+ function padAbove(cm, line, size) {
483
+ var above = true;
484
+ if (line > cm.lastLine()) {
485
+ line--;
486
+ above = false;
487
+ }
488
+ var elt = document.createElement("div");
489
+ elt.className = "CodeMirror-merge-spacer";
490
+ elt.style.height = size + "px"; elt.style.minWidth = "1px";
491
+ return cm.addLineWidget(line, elt, {height: size, above: above, mergeSpacer: true, handleMouseEvents: true});
492
+ }
493
+
494
+ function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) {
495
+ var flip = dv.type == "left";
496
+ // BOARDCLIP PATCH: per-chunk state hook. The wrapper classifies a chunk as
497
+ // 'quiet' (whitespace-only difference \u2014 drawn as nothing) or 'declined'
498
+ // (dismissed by the user \u2014 dimmed connector, no buttons). Riding this
499
+ // function means positioning/redraw come from the addon's own machinery.
500
+ var bcState = dv.mv.options.chunkState ? dv.mv.options.chunkState(dv, chunk) : null;
501
+ if (bcState === "quiet") return;
502
+ var top = dv.orig.heightAtLine(chunk.origFrom, "local", true) - sTopOrig;
503
+ if (dv.svg) {
504
+ var topLpx = top;
505
+ var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local", true) - sTopEdit;
506
+ if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; }
507
+ var botLpx = dv.orig.heightAtLine(chunk.origTo, "local", true) - sTopOrig;
508
+ var botRpx = dv.edit.heightAtLine(chunk.editTo, "local", true) - sTopEdit;
509
+ if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; }
510
+ var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx;
511
+ var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx;
512
+ attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")),
513
+ "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z",
514
+ "class", dv.classes.connect + (bcState === "declined" ? " bc-declined" : bcState === "conflict" ? " bc-conflict-connect" : "")); // BOARDCLIP PATCH
515
+ }
516
+ if (dv.copyButtons && bcState !== "declined") { // BOARDCLIP PATCH: no buttons on declined chunks
517
+ // BOARDCLIP PATCH: Material Symbols glyphs (the app's one icon set, `mi`
518
+ // ligatures) instead of the unicode arrows; the arrow points into the Result.
519
+ var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "arrow_forward" : "arrow_back",
520
+ "CodeMirror-merge-copy mi"));
521
+ var editOriginals = dv.mv.options.allowEditingOriginals;
522
+ copy.title = dv.edit.phrase(editOriginals ? "Push to left" : "Revert chunk");
523
+ copy.chunk = chunk;
524
+ copy.style.top = (chunk.origTo > chunk.origFrom ? top : dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit) + "px";
525
+ copy.setAttribute("role", "button");
526
+ copy.setAttribute("tabindex", "0");
527
+ copy.setAttribute("aria-label", copy.title);
528
+ // BOARDCLIP PATCH: a decline (dismiss) button beside the apply arrow, like
529
+ // IntelliJ's per-chunk x. Routed via options.declineChunk in buildGap.
530
+ if (dv.mv.options.declineChunk) {
531
+ var dec = dv.copyButtons.appendChild(elt("div", "close", "CodeMirror-merge-copy mi bc-decline"));
532
+ dec.title = dv.edit.phrase("Dismiss this change");
533
+ dec.chunk = chunk;
534
+ dec.bcDecline = true;
535
+ dec.style.top = copy.style.top;
536
+ dec.setAttribute("role", "button");
537
+ dec.setAttribute("tabindex", "0");
538
+ dec.setAttribute("aria-label", dec.title);
539
+ }
540
+
541
+ if (editOriginals) {
542
+ var topReverse = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit;
543
+ var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc",
544
+ "CodeMirror-merge-copy-reverse"));
545
+ copyReverse.title = "Push to right";
546
+ copyReverse.chunk = {editFrom: chunk.origFrom, editTo: chunk.origTo,
547
+ origFrom: chunk.editFrom, origTo: chunk.editTo};
548
+ copyReverse.style.top = topReverse + "px";
549
+ dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px";
550
+ copyReverse.setAttribute("role", "button");
551
+ copyReverse.setAttribute("tabindex", "0");
552
+ copyReverse.setAttribute("aria-label", copyReverse.title);
553
+ }
554
+ }
555
+ }
556
+
557
+ function copyChunk(dv, to, from, chunk) {
558
+ if (dv.diffOutOfDate) return;
559
+ var origStart = chunk.origTo > from.lastLine() ? Pos(chunk.origFrom - 1) : Pos(chunk.origFrom, 0)
560
+ var origEnd = Pos(chunk.origTo, 0)
561
+ var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0)
562
+ var editEnd = Pos(chunk.editTo, 0)
563
+ var handler = dv.mv.options.revertChunk
564
+ if (handler)
565
+ handler(dv.mv, from, origStart, origEnd, to, editStart, editEnd)
566
+ else
567
+ to.replaceRange(from.getRange(origStart, origEnd), editStart, editEnd)
568
+ }
569
+
570
+ // Merge view, containing 0, 1, or 2 diff views.
571
+
572
+ var MergeView = CodeMirror.MergeView = function(node, options) {
573
+ if (!(this instanceof MergeView)) return new MergeView(node, options);
574
+
575
+ this.options = options;
576
+ var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight;
577
+
578
+ var hasLeft = origLeft != null, hasRight = origRight != null;
579
+ var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0);
580
+ var wrap = [], left = this.left = null, right = this.right = null;
581
+ var self = this;
582
+
583
+ if (hasLeft) {
584
+ left = this.left = new DiffView(this, "left");
585
+ var leftPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-left");
586
+ wrap.push(leftPane);
587
+ wrap.push(buildGap(left));
588
+ }
589
+
590
+ var editPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-editor");
591
+ wrap.push(editPane);
592
+
593
+ if (hasRight) {
594
+ right = this.right = new DiffView(this, "right");
595
+ wrap.push(buildGap(right));
596
+ var rightPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-right");
597
+ wrap.push(rightPane);
598
+ }
599
+
600
+ (hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost";
601
+
602
+ wrap.push(elt("div", null, null, "height: 0; clear: both;"));
603
+
604
+ var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane"));
605
+ this.edit = CodeMirror(editPane, copyObj(options));
606
+
607
+ if (left) left.init(leftPane, origLeft, options);
608
+ if (right) right.init(rightPane, origRight, options);
609
+ this._bcCollapseMarks = []; // BOARDCLIP PATCH: track collapse widgets for bcRecollapse
610
+ if (options.collapseIdentical)
611
+ this.editor().operation(function() {
612
+ collapseIdenticalStretches(self, options.collapseIdentical);
613
+ });
614
+ if (options.connect == "align") {
615
+ this.aligners = [];
616
+ alignChunks(this.left || this.right, true);
617
+ }
618
+ if (left) left.registerEvents(right)
619
+ if (right) right.registerEvents(left)
620
+
621
+
622
+ // BOARDCLIP PATCH: CodeMirror re-measures its lines on its own 100 ms
623
+ // debounce after a window resize, so connectors drawn at once keep the old
624
+ // line positions (an arrow ends up beside the wrong chunk). Draw again
625
+ // once it has re-measured.
626
+ var resizeRedraw = null;
627
+ var onResize = function() {
628
+ if (left) makeConnections(left);
629
+ if (right) makeConnections(right);
630
+ clearTimeout(resizeRedraw);
631
+ resizeRedraw = setTimeout(function() {
632
+ if (left) makeConnections(left);
633
+ if (right) makeConnections(right);
634
+ }, 160);
635
+ };
636
+ CodeMirror.on(window, "resize", onResize);
637
+ var resizeInterval = setInterval(function() {
638
+ for (var p = wrapElt.parentNode; p && p != document.body; p = p.parentNode) {}
639
+ if (!p) { clearInterval(resizeInterval); CodeMirror.off(window, "resize", onResize); }
640
+ }, 5000);
641
+ };
642
+
643
+ function buildGap(dv) {
644
+ var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock");
645
+ lock.setAttribute("role", "button");
646
+ lock.setAttribute("tabindex", "0");
647
+ var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap");
648
+ CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); });
649
+ CodeMirror.on(lock, "keyup", function(e) { (e.key === "Enter" || e.code === "Space") && setScrollLock(dv, !dv.lockScroll); });
650
+ var gapElts = [lockWrap];
651
+ if (dv.mv.options.revertButtons !== false) {
652
+ dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type);
653
+ var copyButtons = function(e) {
654
+ var node = e.target || e.srcElement;
655
+ if (!node.chunk) return;
656
+ // BOARDCLIP PATCH: decline (dismiss) buttons route to the wrapper hook.
657
+ if (node.bcDecline) {
658
+ if (dv.mv.options.declineChunk) dv.mv.options.declineChunk(dv, node.chunk);
659
+ return;
660
+ }
661
+ if (node.className == "CodeMirror-merge-copy-reverse") {
662
+ copyChunk(dv, dv.orig, dv.edit, node.chunk);
663
+ return;
664
+ }
665
+ copyChunk(dv, dv.edit, dv.orig, node.chunk);
666
+ }
667
+ CodeMirror.on(dv.copyButtons, "click", copyButtons);
668
+ CodeMirror.on(dv.copyButtons, "keyup", function(e) { (e.key === "Enter" || e.code === "Space") && copyButtons(e); });
669
+ gapElts.unshift(dv.copyButtons);
670
+ }
671
+ if (dv.mv.options.connect != "align") {
672
+ var svg = document.createElementNS && document.createElementNS(svgNS, "svg");
673
+ if (svg && !svg.createSVGRect) svg = null;
674
+ dv.svg = svg;
675
+ if (svg) gapElts.push(svg);
676
+ }
677
+
678
+ // BOARDCLIP PATCH: let the wrapper force a gap redraw after chunk-state
679
+ // changes (decline/quiet) without waiting for an editor change event.
680
+ dv.bcRedraw = function() { makeConnections(dv); };
681
+
682
+ return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap");
683
+ }
684
+
685
+ MergeView.prototype = {
686
+ constructor: MergeView,
687
+ editor: function() { return this.edit; },
688
+ rightOriginal: function() { return this.right && this.right.orig; },
689
+ leftOriginal: function() { return this.left && this.left.orig; },
690
+ setShowDifferences: function(val) {
691
+ if (this.right) this.right.setShowDifferences(val);
692
+ if (this.left) this.left.setShowDifferences(val);
693
+ },
694
+ rightChunks: function() {
695
+ if (this.right) { ensureDiff(this.right); return this.right.chunks; }
696
+ },
697
+ leftChunks: function() {
698
+ if (this.left) { ensureDiff(this.left); return this.left.chunks; }
699
+ },
700
+ // BOARDCLIP PATCH: re-run identical-stretch collapse after the diff changes
701
+ // (a merged/declined chunk, a whitespace-mode toggle). Clears prior collapse
702
+ // widgets first so they don't stack.
703
+ bcRecollapse: function(margin) {
704
+ if (this._bcCollapseMarks) for (var i = 0; i < this._bcCollapseMarks.length; i++) { try { this._bcCollapseMarks[i].clear(); } catch (e) {} }
705
+ this._bcCollapseMarks = [];
706
+ var m = margin != null ? margin : (this.options.collapseIdentical === true ? 2 : this.options.collapseIdentical);
707
+ if (m) collapseIdenticalStretches(this, m);
708
+ }
709
+ };
710
+
711
+ function asString(obj) {
712
+ if (typeof obj == "string") return obj;
713
+ else return obj.getValue();
714
+ }
715
+
716
+ // Operations on diffs
717
+ var dmp;
718
+ function getDiff(a, b, ignoreWhitespace) {
719
+ if (!dmp) dmp = new diff_match_patch();
720
+
721
+ // BOARDCLIP PATCH: keep the raw (lossless) diff_main result beside the
722
+ // cleaned one (diff.bcRaw) for the word-level marks (bcMarkDiff), so each
723
+ // update diffs the texts once. The loop below rewrites parts in place,
724
+ // hence the copy.
725
+ var raw = dmp.diff_main(a, b);
726
+ var diff = raw.map(function(p) { return [p[0], p[1]]; });
727
+ // The library sometimes leaves in empty parts, which confuse the algorithm
728
+ for (var i = 0; i < diff.length; ++i) {
729
+ var part = diff[i];
730
+ if (ignoreWhitespace ? !/[^ \t]/.test(part[1]) : !part[1]) {
731
+ diff.splice(i--, 1);
732
+ } else if (i && diff[i - 1][0] == part[0]) {
733
+ diff.splice(i--, 1);
734
+ diff[i][1] += part[1];
735
+ }
736
+ }
737
+ diff.bcRaw = raw;
738
+ return diff;
739
+ }
740
+
741
+ // BOARDCLIP PATCH: a word-level diff for the inline marks. diff_main's
742
+ // character diff marks coincidental letters inside rewritten words
743
+ // ("Wedne|sday" vs "Thur|sday"), a confetti of tiles. This copy of the diff
744
+ // (1) folds every equality that sits INSIDE a line (no line break) into the
745
+ // edits around it when it is no longer than the edits on both sides (the
746
+ // diff_cleanupSemantic rule, kept off line breaks), (2) widens each edit out
747
+ // to whole words, and (3) flags an edit that covers whole lines on every
748
+ // side (part[2] = true): markChanges paints those with the chunk wash only,
749
+ // no second layer; under ignoreWhitespace a whitespace-only edit is flagged
750
+ // quiet (part[3] = true): no mark, no wash. Equalities holding a line break
751
+ // are never folded, so the lines a change touches stay the same; the chunks
752
+ // (gutter buttons, connectors, the wrapper's merge logic) keep using getDiff.
753
+ var BC_WORD_END = /[0-9A-Za-z_\u00C0-\uFFFF]+$/;
754
+ var BC_WORD_START = /^[0-9A-Za-z_\u00C0-\uFFFF]+/;
755
+ var BC_WORD_CHAR = /[0-9A-Za-z_\u00C0-\uFFFF]/;
756
+ function bcWordDiff(diff, ignoreWhitespace) {
757
+ var parts = [], i, j;
758
+ for (i = 0; i < diff.length; i++) {
759
+ var tp = diff[i][0], str = diff[i][1];
760
+ if (tp == DIFF_EQUAL) { parts.push({eq: str}); continue; }
761
+ var last = parts[parts.length - 1];
762
+ if (!last || last.eq != null) { last = {del: "", ins: ""}; parts.push(last); }
763
+ if (tp == DIFF_DELETE) last.del += str; else last.ins += str;
764
+ }
765
+ var size = function(g) { return Math.max(g.del.length, g.ins.length); };
766
+ // (1) Fold short in-line equalities, smallest neighbourhoods first, until none is left.
767
+ for (var folded = true; folded;) {
768
+ folded = false;
769
+ for (j = 1; j < parts.length - 1; j++) {
770
+ var p = parts[j], a = parts[j - 1], b = parts[j + 1];
771
+ if (p.eq == null || a.eq != null || b.eq != null || p.eq.indexOf("\n") >= 0) continue;
772
+ if (p.eq.length > size(a) || p.eq.length > size(b)) continue;
773
+ parts.splice(j - 1, 3, {del: a.del + p.eq + b.del, ins: a.ins + p.eq + b.ins});
774
+ folded = true;
775
+ break;
776
+ }
777
+ }
778
+ // (2) Widen each edit to whole words: a word cut by an edit boundary moves
779
+ // into both sides of the edit (never past a space or a line break).
780
+ for (j = 0; j < parts.length; j++) {
781
+ var g = parts[j];
782
+ if (g.eq != null) continue;
783
+ var prev = parts[j - 1], next = parts[j + 1];
784
+ if (prev && prev.eq && (BC_WORD_CHAR.test(g.del.charAt(0)) || BC_WORD_CHAR.test(g.ins.charAt(0)))) {
785
+ var head = BC_WORD_END.exec(prev.eq);
786
+ if (head) { prev.eq = prev.eq.slice(0, head.index); g.del = head[0] + g.del; g.ins = head[0] + g.ins; }
787
+ }
788
+ if (next && next.eq && (BC_WORD_CHAR.test(g.del.charAt(g.del.length - 1)) || BC_WORD_CHAR.test(g.ins.charAt(g.ins.length - 1)))) {
789
+ var lead = BC_WORD_START.exec(next.eq);
790
+ if (lead) { next.eq = next.eq.slice(lead[0].length); g.del += lead[0]; g.ins += lead[0]; }
791
+ }
792
+ }
793
+ // Emptied equalities go; the edits either side of one become one edit.
794
+ var merged = [];
795
+ for (j = 0; j < parts.length; j++) {
796
+ var q = parts[j], top = merged[merged.length - 1];
797
+ if (q.eq != null) { if (q.eq) merged.push(q); continue; }
798
+ if (top && top.eq == null) { top.del += q.del; top.ins += q.ins; } else merged.push({del: q.del, ins: q.ins});
799
+ }
800
+ // (3) Back to diff form, whole-line edits flagged.
801
+ var out = [];
802
+ for (j = 0; j < merged.length; j++) {
803
+ var m = merged[j];
804
+ if (m.eq != null) { out.push([DIFF_EQUAL, m.eq]); continue; }
805
+ var before = merged[j - 1], after = merged[j + 1];
806
+ var text = m.del || m.ins;
807
+ var whole = (!before || /\n$/.test(before.eq) || (!m.del || m.del.charAt(0) == "\n") && (!m.ins || m.ins.charAt(0) == "\n"))
808
+ && (!after || after.eq.charAt(0) == "\n" || (!m.del || /\n$/.test(m.del)) && (!m.ins || /\n$/.test(m.ins)));
809
+ if (!text) continue;
810
+ var quiet = !!ignoreWhitespace && !/[^ \t]/.test(m.del) && !/[^ \t]/.test(m.ins);
811
+ if (m.del) out.push([DIFF_DELETE, m.del, whole, quiet]);
812
+ if (m.ins) out.push([DIFF_INSERT, m.ins, whole, quiet]);
813
+ }
814
+ return out;
815
+ }
816
+ // From diff_main's raw (lossless) diff, not dv.diff: getDiff's
817
+ // ignoreWhitespace drops whitespace-only parts, equalities included, so its
818
+ // positions drift (a mark ending a character early). getDiff keeps that raw
819
+ // diff on dv.diff.bcRaw (the texts are diffed once per update). Recomputed
820
+ // with dv.diff.
821
+ function bcMarkDiff(dv) {
822
+ if (dv.bcMarkSource !== dv.diff) {
823
+ var raw = dv.diff.bcRaw;
824
+ if (!raw) { if (!dmp) dmp = new diff_match_patch(); raw = dmp.diff_main(dv.orig.getValue(), dv.edit.getValue()); }
825
+ dv.bcMarkSource = dv.diff;
826
+ dv.bcMarkDiffCache = bcWordDiff(raw, dv.mv.options.ignoreWhitespace);
827
+ }
828
+ return dv.bcMarkDiffCache;
829
+ }
830
+
831
+ function getChunks(diff) {
832
+ var chunks = [];
833
+ if (!diff.length) return chunks;
834
+ var startEdit = 0, startOrig = 0;
835
+ var edit = Pos(0, 0), orig = Pos(0, 0);
836
+ for (var i = 0; i < diff.length; ++i) {
837
+ var part = diff[i], tp = part[0];
838
+ if (tp == DIFF_EQUAL) {
839
+ var startOff = !startOfLineClean(diff, i) || edit.line < startEdit || orig.line < startOrig ? 1 : 0;
840
+ var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff;
841
+ moveOver(edit, part[1], null, orig);
842
+ var endOff = endOfLineClean(diff, i) ? 1 : 0;
843
+ var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff;
844
+ if (cleanToEdit > cleanFromEdit) {
845
+ if (i) chunks.push({origFrom: startOrig, origTo: cleanFromOrig,
846
+ editFrom: startEdit, editTo: cleanFromEdit});
847
+ startEdit = cleanToEdit; startOrig = cleanToOrig;
848
+ }
849
+ } else {
850
+ moveOver(tp == DIFF_INSERT ? edit : orig, part[1]);
851
+ }
852
+ }
853
+ if (startEdit <= edit.line || startOrig <= orig.line)
854
+ chunks.push({origFrom: startOrig, origTo: orig.line + 1,
855
+ editFrom: startEdit, editTo: edit.line + 1});
856
+ return chunks;
857
+ }
858
+
859
+ function endOfLineClean(diff, i) {
860
+ if (i == diff.length - 1) return true;
861
+ var next = diff[i + 1][1];
862
+ if ((next.length == 1 && i < diff.length - 2) || next.charCodeAt(0) != 10) return false;
863
+ if (i == diff.length - 2) return true;
864
+ next = diff[i + 2][1];
865
+ return (next.length > 1 || i == diff.length - 3) && next.charCodeAt(0) == 10;
866
+ }
867
+
868
+ function startOfLineClean(diff, i) {
869
+ if (i == 0) return true;
870
+ var last = diff[i - 1][1];
871
+ if (last.charCodeAt(last.length - 1) != 10) return false;
872
+ if (i == 1) return true;
873
+ last = diff[i - 2][1];
874
+ return last.charCodeAt(last.length - 1) == 10;
875
+ }
876
+
877
+ function chunkBoundariesAround(chunks, n, nInEdit) {
878
+ var beforeE, afterE, beforeO, afterO;
879
+ for (var i = 0; i < chunks.length; i++) {
880
+ var chunk = chunks[i];
881
+ var fromLocal = nInEdit ? chunk.editFrom : chunk.origFrom;
882
+ var toLocal = nInEdit ? chunk.editTo : chunk.origTo;
883
+ if (afterE == null) {
884
+ if (fromLocal > n) { afterE = chunk.editFrom; afterO = chunk.origFrom; }
885
+ else if (toLocal > n) { afterE = chunk.editTo; afterO = chunk.origTo; }
886
+ }
887
+ if (toLocal <= n) { beforeE = chunk.editTo; beforeO = chunk.origTo; }
888
+ else if (fromLocal <= n) { beforeE = chunk.editFrom; beforeO = chunk.origFrom; }
889
+ }
890
+ return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}};
891
+ }
892
+
893
+ function collapseSingle(cm, from, to) {
894
+ cm.addLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
895
+ var widget = document.createElement("span");
896
+ widget.className = "CodeMirror-merge-collapsed-widget";
897
+ widget.title = cm.phrase("Identical text collapsed. Click to expand.");
898
+ // BOARDCLIP PATCH: say how much is folded ("12 unchanged lines").
899
+ var bcFolded = to - from;
900
+ widget.textContent = bcFolded + (bcFolded === 1 ? " unchanged line" : " unchanged lines");
901
+ var mark = cm.markText(Pos(from, 0), Pos(to - 1), {
902
+ inclusiveLeft: true,
903
+ inclusiveRight: true,
904
+ replacedWith: widget,
905
+ clearOnEnter: true
906
+ });
907
+ function clear() {
908
+ mark.clear();
909
+ cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
910
+ }
911
+ if (mark.explicitlyCleared) clear();
912
+ CodeMirror.on(widget, "click", clear);
913
+ mark.on("clear", clear);
914
+ CodeMirror.on(widget, "click", clear);
915
+ return {mark: mark, clear: clear};
916
+ }
917
+
918
+ function collapseStretch(size, editors) {
919
+ var marks = [];
920
+ function clear() {
921
+ for (var i = 0; i < marks.length; i++) marks[i].clear();
922
+ }
923
+ for (var i = 0; i < editors.length; i++) {
924
+ var editor = editors[i];
925
+ var mark = collapseSingle(editor.cm, editor.line, editor.line + size);
926
+ marks.push(mark);
927
+ mark.mark.on("clear", clear);
928
+ }
929
+ return marks[0].mark;
930
+ }
931
+
932
+ function unclearNearChunks(dv, margin, off, clear) {
933
+ for (var i = 0; i < dv.chunks.length; i++) {
934
+ var chunk = dv.chunks[i];
935
+ // BOARDCLIP PATCH: "quiet" chunks (whitespace/blank-line-only, per the
936
+ // wrapper's chunkState hook) shouldn't keep their surrounding identical
937
+ // lines expanded — collapse straight through them like every diff viewer.
938
+ if (dv.mv.options.chunkState && dv.mv.options.chunkState(dv, chunk) === "quiet") continue;
939
+ for (var l = chunk.editFrom - margin; l < chunk.editTo + margin; l++) {
940
+ var pos = l + off;
941
+ if (pos >= 0 && pos < clear.length) clear[pos] = false;
942
+ }
943
+ }
944
+ }
945
+
946
+ function collapseIdenticalStretches(mv, margin) {
947
+ if (typeof margin != "number") margin = 2;
948
+ var clear = [], edit = mv.editor(), off = edit.firstLine();
949
+ for (var l = off, e = edit.lastLine(); l <= e; l++) clear.push(true);
950
+ if (mv.left) unclearNearChunks(mv.left, margin, off, clear);
951
+ if (mv.right) unclearNearChunks(mv.right, margin, off, clear);
952
+ // BOARDCLIP PATCH: always keep `margin` context lines at the very top and
953
+ // bottom of the document. Otherwise a fully-identical doc collapses line 0
954
+ // too, and the Result editor's cursor (at 0,0) instantly clears it via the
955
+ // collapse mark's clearOnEnter — so it would never fold. This also matches
956
+ // GitHub/IntelliJ (a little context always shows at the edges).
957
+ for (var bc = 0; bc < margin && bc < clear.length; bc++) { clear[bc] = false; clear[clear.length - 1 - bc] = false; }
958
+
959
+ for (var i = 0; i < clear.length; i++) {
960
+ if (clear[i]) {
961
+ var line = i + off;
962
+ for (var size = 1; i < clear.length - 1 && clear[i + 1]; i++, size++) {}
963
+ if (size > margin) {
964
+ var editors = [{line: line, cm: edit}];
965
+ if (mv.left) editors.push({line: getMatchingOrigLine(line, mv.left.chunks), cm: mv.left.orig});
966
+ if (mv.right) editors.push({line: getMatchingOrigLine(line, mv.right.chunks), cm: mv.right.orig});
967
+ var mark = collapseStretch(size, editors);
968
+ if (mv._bcCollapseMarks) mv._bcCollapseMarks.push(mark); // BOARDCLIP PATCH: track for bcRecollapse
969
+ if (mv.options.onCollapse) mv.options.onCollapse(mv, line, size, mark);
970
+ }
971
+ }
972
+ }
973
+ }
974
+
975
+ // General utilities
976
+
977
+ function elt(tag, content, className, style) {
978
+ var e = document.createElement(tag);
979
+ if (className) e.className = className;
980
+ if (style) e.style.cssText = style;
981
+ if (typeof content == "string") e.appendChild(document.createTextNode(content));
982
+ else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
983
+ return e;
984
+ }
985
+
986
+ function clear(node) {
987
+ for (var count = node.childNodes.length; count > 0; --count)
988
+ node.removeChild(node.firstChild);
989
+ }
990
+
991
+ function attrs(elt) {
992
+ for (var i = 1; i < arguments.length; i += 2)
993
+ elt.setAttribute(arguments[i], arguments[i+1]);
994
+ }
995
+
996
+ function copyObj(obj, target) {
997
+ if (!target) target = {};
998
+ for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop];
999
+ return target;
1000
+ }
1001
+
1002
+ function moveOver(pos, str, copy, other) {
1003
+ var out = copy ? Pos(pos.line, pos.ch) : pos, at = 0;
1004
+ for (;;) {
1005
+ var nl = str.indexOf("\n", at);
1006
+ if (nl == -1) break;
1007
+ ++out.line;
1008
+ if (other) ++other.line;
1009
+ at = nl + 1;
1010
+ }
1011
+ out.ch = (at ? 0 : out.ch) + (str.length - at);
1012
+ if (other) other.ch = (at ? 0 : other.ch) + (str.length - at);
1013
+ return out;
1014
+ }
1015
+
1016
+ // Tracks collapsed markers and line widgets, in order to be able to
1017
+ // accurately align the content of two editors.
1018
+
1019
+ var F_WIDGET = 1, F_WIDGET_BELOW = 2, F_MARKER = 4
1020
+
1021
+ function TrackAlignable(cm) {
1022
+ this.cm = cm
1023
+ this.alignable = []
1024
+ this.height = cm.doc.height
1025
+ var self = this
1026
+ cm.on("markerAdded", function(_, marker) {
1027
+ if (!marker.collapsed) return
1028
+ var found = marker.find(1)
1029
+ if (found != null) self.set(found.line, F_MARKER)
1030
+ })
1031
+ cm.on("markerCleared", function(_, marker, _min, max) {
1032
+ if (max != null && marker.collapsed)
1033
+ self.check(max, F_MARKER, self.hasMarker)
1034
+ })
1035
+ cm.on("markerChanged", this.signal.bind(this))
1036
+ cm.on("lineWidgetAdded", function(_, widget, lineNo) {
1037
+ if (widget.mergeSpacer) return
1038
+ if (widget.above) self.set(lineNo - 1, F_WIDGET_BELOW)
1039
+ else self.set(lineNo, F_WIDGET)
1040
+ })
1041
+ cm.on("lineWidgetCleared", function(_, widget, lineNo) {
1042
+ if (widget.mergeSpacer) return
1043
+ if (widget.above) self.check(lineNo - 1, F_WIDGET_BELOW, self.hasWidgetBelow)
1044
+ else self.check(lineNo, F_WIDGET, self.hasWidget)
1045
+ })
1046
+ cm.on("lineWidgetChanged", this.signal.bind(this))
1047
+ cm.on("change", function(_, change) {
1048
+ var start = change.from.line, nBefore = change.to.line - change.from.line
1049
+ var nAfter = change.text.length - 1, end = start + nAfter
1050
+ if (nBefore || nAfter) self.map(start, nBefore, nAfter)
1051
+ self.check(end, F_MARKER, self.hasMarker)
1052
+ if (nBefore || nAfter) self.check(change.from.line, F_MARKER, self.hasMarker)
1053
+ })
1054
+ cm.on("viewportChange", function() {
1055
+ if (self.cm.doc.height != self.height) self.signal()
1056
+ })
1057
+ }
1058
+
1059
+ TrackAlignable.prototype = {
1060
+ signal: function() {
1061
+ CodeMirror.signal(this, "realign")
1062
+ this.height = this.cm.doc.height
1063
+ },
1064
+
1065
+ set: function(n, flags) {
1066
+ var pos = -1
1067
+ for (; pos < this.alignable.length; pos += 2) {
1068
+ var diff = this.alignable[pos] - n
1069
+ if (diff == 0) {
1070
+ if ((this.alignable[pos + 1] & flags) == flags) return
1071
+ this.alignable[pos + 1] |= flags
1072
+ this.signal()
1073
+ return
1074
+ }
1075
+ if (diff > 0) break
1076
+ }
1077
+ this.signal()
1078
+ this.alignable.splice(pos, 0, n, flags)
1079
+ },
1080
+
1081
+ find: function(n) {
1082
+ for (var i = 0; i < this.alignable.length; i += 2)
1083
+ if (this.alignable[i] == n) return i
1084
+ return -1
1085
+ },
1086
+
1087
+ check: function(n, flag, pred) {
1088
+ var found = this.find(n)
1089
+ if (found == -1 || !(this.alignable[found + 1] & flag)) return
1090
+ if (!pred.call(this, n)) {
1091
+ this.signal()
1092
+ var flags = this.alignable[found + 1] & ~flag
1093
+ if (flags) this.alignable[found + 1] = flags
1094
+ else this.alignable.splice(found, 2)
1095
+ }
1096
+ },
1097
+
1098
+ hasMarker: function(n) {
1099
+ var handle = this.cm.getLineHandle(n)
1100
+ if (handle.markedSpans) for (var i = 0; i < handle.markedSpans.length; i++)
1101
+ if (handle.markedSpans[i].marker.collapsed && handle.markedSpans[i].to != null)
1102
+ return true
1103
+ return false
1104
+ },
1105
+
1106
+ hasWidget: function(n) {
1107
+ var handle = this.cm.getLineHandle(n)
1108
+ if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++)
1109
+ if (!handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true
1110
+ return false
1111
+ },
1112
+
1113
+ hasWidgetBelow: function(n) {
1114
+ if (n == this.cm.lastLine()) return false
1115
+ var handle = this.cm.getLineHandle(n + 1)
1116
+ if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++)
1117
+ if (handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true
1118
+ return false
1119
+ },
1120
+
1121
+ map: function(from, nBefore, nAfter) {
1122
+ var diff = nAfter - nBefore, to = from + nBefore, widgetFrom = -1, widgetTo = -1
1123
+ for (var i = 0; i < this.alignable.length; i += 2) {
1124
+ var n = this.alignable[i]
1125
+ if (n == from && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetFrom = i
1126
+ if (n == to && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetTo = i
1127
+ if (n <= from) continue
1128
+ else if (n < to) this.alignable.splice(i--, 2)
1129
+ else this.alignable[i] += diff
1130
+ }
1131
+ if (widgetFrom > -1) {
1132
+ var flags = this.alignable[widgetFrom + 1]
1133
+ if (flags == F_WIDGET_BELOW) this.alignable.splice(widgetFrom, 2)
1134
+ else this.alignable[widgetFrom + 1] = flags & ~F_WIDGET_BELOW
1135
+ }
1136
+ if (widgetTo > -1 && nAfter)
1137
+ this.set(from + nAfter, F_WIDGET_BELOW)
1138
+ }
1139
+ }
1140
+
1141
+ function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; }
1142
+ function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; }
1143
+ function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }
1144
+
1145
+ function findPrevDiff(chunks, start, isOrig) {
1146
+ for (var i = chunks.length - 1; i >= 0; i--) {
1147
+ var chunk = chunks[i];
1148
+ var to = (isOrig ? chunk.origTo : chunk.editTo) - 1;
1149
+ if (to < start) return to;
1150
+ }
1151
+ }
1152
+
1153
+ function findNextDiff(chunks, start, isOrig) {
1154
+ for (var i = 0; i < chunks.length; i++) {
1155
+ var chunk = chunks[i];
1156
+ var from = (isOrig ? chunk.origFrom : chunk.editFrom);
1157
+ if (from > start) return from;
1158
+ }
1159
+ }
1160
+
1161
+ function goNearbyDiff(cm, dir) {
1162
+ var found = null, views = cm.state.diffViews, line = cm.getCursor().line;
1163
+ if (views) for (var i = 0; i < views.length; i++) {
1164
+ var dv = views[i], isOrig = cm == dv.orig;
1165
+ ensureDiff(dv);
1166
+ var pos = dir < 0 ? findPrevDiff(dv.chunks, line, isOrig) : findNextDiff(dv.chunks, line, isOrig);
1167
+ if (pos != null && (found == null || (dir < 0 ? pos > found : pos < found)))
1168
+ found = pos;
1169
+ }
1170
+ if (found != null)
1171
+ cm.setCursor(found, 0);
1172
+ else
1173
+ return CodeMirror.Pass;
1174
+ }
1175
+
1176
+ CodeMirror.commands.goNextDiff = function(cm) {
1177
+ return goNearbyDiff(cm, 1);
1178
+ };
1179
+ CodeMirror.commands.goPrevDiff = function(cm) {
1180
+ return goNearbyDiff(cm, -1);
1181
+ };
1182
+ });