@tobq/boardclip 0.0.0-stage → 2.1.107
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.
- package/LICENSE +21 -0
- package/README.md +170 -2
- package/assets/boardclip-icon.png +0 -0
- package/bin/boardclip.js +43 -0
- package/editor-preload.js +71 -0
- package/editor.html +220 -0
- package/icon.png +0 -0
- package/icon@2x.png +0 -0
- package/iconTemplate.png +0 -0
- package/iconTemplate@2x.png +0 -0
- package/index.html +1339 -0
- package/lib/appearance.js +183 -0
- package/lib/auto-update.js +306 -0
- package/lib/backup-worker.js +46 -0
- package/lib/backup.js +275 -0
- package/lib/blob-field-store.js +275 -0
- package/lib/blob-store.js +68 -0
- package/lib/build-info.js +89 -0
- package/lib/clip-blob-store.js +161 -0
- package/lib/clip-revision.js +81 -0
- package/lib/clipboard-capture.js +95 -0
- package/lib/clipboard-follow.js +98 -0
- package/lib/clipboard-model.js +1235 -0
- package/lib/cloud-accounts.js +250 -0
- package/lib/conflict-model.js +186 -0
- package/lib/control-client.js +87 -0
- package/lib/control-server.js +149 -0
- package/lib/diagnostics.js +121 -0
- package/lib/drag-files.js +48 -0
- package/lib/ensure-directory.js +56 -0
- package/lib/fork-names.js +35 -0
- package/lib/fs-probe.js +132 -0
- package/lib/history-feed.js +48 -0
- package/lib/hmac-auth.js +78 -0
- package/lib/macos-hotkey.js +297 -0
- package/lib/macos-paste.js +112 -0
- package/lib/mcp-core.js +270 -0
- package/lib/mcp-installers.js +284 -0
- package/lib/mcp-paths.js +99 -0
- package/lib/p2p-crypto.js +54 -0
- package/lib/p2p-discovery.js +191 -0
- package/lib/quick-paste.js +187 -0
- package/lib/retention.js +43 -0
- package/lib/sync-delta.js +227 -0
- package/lib/sync-journal.js +181 -0
- package/lib/sync-paths.js +73 -0
- package/lib/tailscale.js +80 -0
- package/lib/text-blob-store.js +38 -0
- package/lib/windows-clipboard.js +213 -0
- package/lib/windows-console.js +63 -0
- package/lib/windows-dwm.js +183 -0
- package/lib/windows-hook-worker.js +236 -0
- package/lib/windows-hook.js +91 -0
- package/lib/windows-paste.js +185 -0
- package/main.js +7540 -0
- package/mcp/boardclip-mcp.js +255 -0
- package/mcp-approval-preload.js +22 -0
- package/mcp-approval.html +218 -0
- package/package.json +55 -4
- package/preload.js +106 -0
- package/site/index.html +1624 -0
- package/site/shared/clip-search.js +2330 -0
- package/site/shared/clipboard-popup.css +1046 -0
- package/site/shared/clipboard-tokens.css +326 -0
- package/site/shared/clipboard-ui-core.js +6921 -0
- package/site/shared/clipboard-window.css +36 -0
- package/site/shared/vendor/cm5/README.md +52 -0
- package/site/shared/vendor/cm5/codemirror.css +344 -0
- package/site/shared/vendor/cm5/codemirror.js +9884 -0
- package/site/shared/vendor/cm5/diff-match-patch.js +2228 -0
- package/site/shared/vendor/cm5/merge.css +119 -0
- package/site/shared/vendor/cm5/merge.js +1182 -0
- package/site/shared/vendor/fonts/README.md +19 -0
- package/site/shared/vendor/fonts/material-symbols-rounded.woff2 +0 -0
- package/viewer-preload.js +51 -0
- 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
|
+
});
|