Mercurial
comparison .cms/lib/codemirror/addon/merge/merge.js @ 0:78edf6b517a0 draft
24.10
author | Coffee CMS <info@coffee-cms.ru> |
---|---|
date | Fri, 11 Oct 2024 22:40:23 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:78edf6b517a0 |
---|---|
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 updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes); | |
102 updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes); | |
103 } | |
104 | |
105 if (dv.mv.options.connect == "align") | |
106 alignChunks(dv); | |
107 makeConnections(dv); | |
108 if (dv.needsScrollSync != null) syncScroll(dv, dv.needsScrollSync) | |
109 | |
110 updating = false; | |
111 } | |
112 function setDealign(fast) { | |
113 if (updating) return; | |
114 dv.dealigned = true; | |
115 set(fast); | |
116 } | |
117 function set(fast) { | |
118 if (updating || updatingFast) return; | |
119 clearTimeout(debounceChange); | |
120 if (fast === true) updatingFast = true; | |
121 debounceChange = setTimeout(update, fast === true ? 20 : 250); | |
122 } | |
123 function change(_cm, change) { | |
124 if (!dv.diffOutOfDate) { | |
125 dv.diffOutOfDate = true; | |
126 edit.from = edit.to = orig.from = orig.to = 0; | |
127 } | |
128 // Update faster when a line was added/removed | |
129 setDealign(change.text.length - 1 != change.to.line - change.from.line); | |
130 } | |
131 function swapDoc() { | |
132 dv.diffOutOfDate = true; | |
133 dv.dealigned = true; | |
134 update("full"); | |
135 } | |
136 dv.edit.on("change", change); | |
137 dv.orig.on("change", change); | |
138 dv.edit.on("swapDoc", swapDoc); | |
139 dv.orig.on("swapDoc", swapDoc); | |
140 if (dv.mv.options.connect == "align") { | |
141 CodeMirror.on(dv.edit.state.trackAlignable, "realign", setDealign) | |
142 CodeMirror.on(dv.orig.state.trackAlignable, "realign", setDealign) | |
143 } | |
144 dv.edit.on("viewportChange", function() { set(false); }); | |
145 dv.orig.on("viewportChange", function() { set(false); }); | |
146 update(); | |
147 return update; | |
148 } | |
149 | |
150 function registerScroll(dv, otherDv) { | |
151 dv.edit.on("scroll", function() { | |
152 syncScroll(dv, true) && makeConnections(dv); | |
153 }); | |
154 dv.orig.on("scroll", function() { | |
155 syncScroll(dv, false) && makeConnections(dv); | |
156 if (otherDv) syncScroll(otherDv, true) && makeConnections(otherDv); | |
157 }); | |
158 } | |
159 | |
160 function syncScroll(dv, toOrig) { | |
161 // Change handler will do a refresh after a timeout when diff is out of date | |
162 if (dv.diffOutOfDate) { | |
163 if (dv.lockScroll && dv.needsScrollSync == null) dv.needsScrollSync = toOrig | |
164 return false | |
165 } | |
166 dv.needsScrollSync = null | |
167 if (!dv.lockScroll) return true; | |
168 var editor, other, now = +new Date; | |
169 if (toOrig) { editor = dv.edit; other = dv.orig; } | |
170 else { editor = dv.orig; other = dv.edit; } | |
171 // Don't take action if the position of this editor was recently set | |
172 // (to prevent feedback loops) | |
173 if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 250 > now) return false; | |
174 | |
175 var sInfo = editor.getScrollInfo(); | |
176 if (dv.mv.options.connect == "align") { | |
177 targetPos = sInfo.top; | |
178 } else { | |
179 var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen; | |
180 var mid = editor.lineAtHeight(midY, "local"); | |
181 var around = chunkBoundariesAround(dv.chunks, mid, toOrig); | |
182 var off = getOffsets(editor, toOrig ? around.edit : around.orig); | |
183 var offOther = getOffsets(other, toOrig ? around.orig : around.edit); | |
184 var ratio = (midY - off.top) / (off.bot - off.top); | |
185 var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top); | |
186 | |
187 var botDist, mix; | |
188 // Some careful tweaking to make sure no space is left out of view | |
189 // when scrolling to top or bottom. | |
190 if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) { | |
191 targetPos = targetPos * mix + sInfo.top * (1 - mix); | |
192 } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) { | |
193 var otherInfo = other.getScrollInfo(); | |
194 var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos; | |
195 if (botDistOther > botDist && (mix = botDist / halfScreen) < 1) | |
196 targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix); | |
197 } | |
198 } | |
199 | |
200 other.scrollTo(sInfo.left, targetPos); | |
201 other.state.scrollSetAt = now; | |
202 other.state.scrollSetBy = dv; | |
203 return true; | |
204 } | |
205 | |
206 function getOffsets(editor, around) { | |
207 var bot = around.after; | |
208 if (bot == null) bot = editor.lastLine() + 1; | |
209 return {top: editor.heightAtLine(around.before || 0, "local"), | |
210 bot: editor.heightAtLine(bot, "local")}; | |
211 } | |
212 | |
213 function setScrollLock(dv, val, action) { | |
214 dv.lockScroll = val; | |
215 if (val && action != false) syncScroll(dv, DIFF_INSERT) && makeConnections(dv); | |
216 (val ? CodeMirror.addClass : CodeMirror.rmClass)(dv.lockButton, "CodeMirror-merge-scrolllock-enabled"); | |
217 } | |
218 | |
219 // Updating the marks for editor content | |
220 | |
221 function removeClass(editor, line, classes) { | |
222 var locs = classes.classLocation | |
223 for (var i = 0; i < locs.length; i++) { | |
224 editor.removeLineClass(line, locs[i], classes.chunk); | |
225 editor.removeLineClass(line, locs[i], classes.start); | |
226 editor.removeLineClass(line, locs[i], classes.end); | |
227 } | |
228 } | |
229 | |
230 function clearMarks(editor, arr, classes) { | |
231 for (var i = 0; i < arr.length; ++i) { | |
232 var mark = arr[i]; | |
233 if (mark instanceof CodeMirror.TextMarker) | |
234 mark.clear(); | |
235 else if (mark.parent) | |
236 removeClass(editor, mark, classes); | |
237 } | |
238 arr.length = 0; | |
239 } | |
240 | |
241 // FIXME maybe add a margin around viewport to prevent too many updates | |
242 function updateMarks(editor, diff, state, type, classes) { | |
243 var vp = editor.getViewport(); | |
244 editor.operation(function() { | |
245 if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) { | |
246 clearMarks(editor, state.marked, classes); | |
247 markChanges(editor, diff, type, state.marked, vp.from, vp.to, classes); | |
248 state.from = vp.from; state.to = vp.to; | |
249 } else { | |
250 if (vp.from < state.from) { | |
251 markChanges(editor, diff, type, state.marked, vp.from, state.from, classes); | |
252 state.from = vp.from; | |
253 } | |
254 if (vp.to > state.to) { | |
255 markChanges(editor, diff, type, state.marked, state.to, vp.to, classes); | |
256 state.to = vp.to; | |
257 } | |
258 } | |
259 }); | |
260 } | |
261 | |
262 function addClass(editor, lineNr, classes, main, start, end) { | |
263 var locs = classes.classLocation, line = editor.getLineHandle(lineNr); | |
264 for (var i = 0; i < locs.length; i++) { | |
265 if (main) editor.addLineClass(line, locs[i], classes.chunk); | |
266 if (start) editor.addLineClass(line, locs[i], classes.start); | |
267 if (end) editor.addLineClass(line, locs[i], classes.end); | |
268 } | |
269 return line; | |
270 } | |
271 | |
272 function markChanges(editor, diff, type, marks, from, to, classes) { | |
273 var pos = Pos(0, 0); | |
274 var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1)); | |
275 var cls = type == DIFF_DELETE ? classes.del : classes.insert; | |
276 function markChunk(start, end) { | |
277 var bfrom = Math.max(from, start), bto = Math.min(to, end); | |
278 for (var i = bfrom; i < bto; ++i) | |
279 marks.push(addClass(editor, i, classes, true, i == start, i == end - 1)); | |
280 // When the chunk is empty, make sure a horizontal line shows up | |
281 if (start == end && bfrom == end && bto == end) { | |
282 if (bfrom) | |
283 marks.push(addClass(editor, bfrom - 1, classes, false, false, true)); | |
284 else | |
285 marks.push(addClass(editor, bfrom, classes, false, true, false)); | |
286 } | |
287 } | |
288 | |
289 var chunkStart = 0, pending = false; | |
290 for (var i = 0; i < diff.length; ++i) { | |
291 var part = diff[i], tp = part[0], str = part[1]; | |
292 if (tp == DIFF_EQUAL) { | |
293 var cleanFrom = pos.line + (startOfLineClean(diff, i) ? 0 : 1); | |
294 moveOver(pos, str); | |
295 var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0); | |
296 if (cleanTo > cleanFrom) { | |
297 if (pending) { markChunk(chunkStart, cleanFrom); pending = false } | |
298 chunkStart = cleanTo; | |
299 } | |
300 } else { | |
301 pending = true | |
302 if (tp == type) { | |
303 var end = moveOver(pos, str, true); | |
304 var a = posMax(top, pos), b = posMin(bot, end); | |
305 if (!posEq(a, b)) | |
306 marks.push(editor.markText(a, b, {className: cls})); | |
307 pos = end; | |
308 } | |
309 } | |
310 } | |
311 if (pending) markChunk(chunkStart, pos.line + 1); | |
312 } | |
313 | |
314 // Updating the gap between editor and original | |
315 | |
316 function makeConnections(dv) { | |
317 if (!dv.showDifferences) return; | |
318 | |
319 if (dv.svg) { | |
320 clear(dv.svg); | |
321 var w = dv.gap.offsetWidth; | |
322 attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight); | |
323 } | |
324 if (dv.copyButtons) clear(dv.copyButtons); | |
325 | |
326 var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport(); | |
327 var outerTop = dv.mv.wrap.getBoundingClientRect().top | |
328 var sTopEdit = outerTop - dv.edit.getScrollerElement().getBoundingClientRect().top + dv.edit.getScrollInfo().top | |
329 var sTopOrig = outerTop - dv.orig.getScrollerElement().getBoundingClientRect().top + dv.orig.getScrollInfo().top; | |
330 for (var i = 0; i < dv.chunks.length; i++) { | |
331 var ch = dv.chunks[i]; | |
332 if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from && | |
333 ch.origFrom <= vpOrig.to && ch.origTo >= vpOrig.from) | |
334 drawConnectorsForChunk(dv, ch, sTopOrig, sTopEdit, w); | |
335 } | |
336 } | |
337 | |
338 function getMatchingOrigLine(editLine, chunks) { | |
339 var editStart = 0, origStart = 0; | |
340 for (var i = 0; i < chunks.length; i++) { | |
341 var chunk = chunks[i]; | |
342 if (chunk.editTo > editLine && chunk.editFrom <= editLine) return null; | |
343 if (chunk.editFrom > editLine) break; | |
344 editStart = chunk.editTo; | |
345 origStart = chunk.origTo; | |
346 } | |
347 return origStart + (editLine - editStart); | |
348 } | |
349 | |
350 // Combines information about chunks and widgets/markers to return | |
351 // an array of lines, in a single editor, that probably need to be | |
352 // aligned with their counterparts in the editor next to it. | |
353 function alignableFor(cm, chunks, isOrig) { | |
354 var tracker = cm.state.trackAlignable | |
355 var start = cm.firstLine(), trackI = 0 | |
356 var result = [] | |
357 for (var i = 0;; i++) { | |
358 var chunk = chunks[i] | |
359 var chunkStart = !chunk ? 1e9 : isOrig ? chunk.origFrom : chunk.editFrom | |
360 for (; trackI < tracker.alignable.length; trackI += 2) { | |
361 var n = tracker.alignable[trackI] + 1 | |
362 if (n <= start) continue | |
363 if (n <= chunkStart) result.push(n) | |
364 else break | |
365 } | |
366 if (!chunk) break | |
367 result.push(start = isOrig ? chunk.origTo : chunk.editTo) | |
368 } | |
369 return result | |
370 } | |
371 | |
372 // Given information about alignable lines in two editors, fill in | |
373 // the result (an array of three-element arrays) to reflect the | |
374 // lines that need to be aligned with each other. | |
375 function mergeAlignable(result, origAlignable, chunks, setIndex) { | |
376 var rI = 0, origI = 0, chunkI = 0, diff = 0 | |
377 outer: for (;; rI++) { | |
378 var nextR = result[rI], nextO = origAlignable[origI] | |
379 if (!nextR && nextO == null) break | |
380 | |
381 var rLine = nextR ? nextR[0] : 1e9, oLine = nextO == null ? 1e9 : nextO | |
382 while (chunkI < chunks.length) { | |
383 var chunk = chunks[chunkI] | |
384 if (chunk.origFrom <= oLine && chunk.origTo > oLine) { | |
385 origI++ | |
386 rI-- | |
387 continue outer; | |
388 } | |
389 if (chunk.editTo > rLine) { | |
390 if (chunk.editFrom <= rLine) continue outer; | |
391 break | |
392 } | |
393 diff += (chunk.origTo - chunk.origFrom) - (chunk.editTo - chunk.editFrom) | |
394 chunkI++ | |
395 } | |
396 if (rLine == oLine - diff) { | |
397 nextR[setIndex] = oLine | |
398 origI++ | |
399 } else if (rLine < oLine - diff) { | |
400 nextR[setIndex] = rLine + diff | |
401 } else { | |
402 var record = [oLine - diff, null, null] | |
403 record[setIndex] = oLine | |
404 result.splice(rI, 0, record) | |
405 origI++ | |
406 } | |
407 } | |
408 } | |
409 | |
410 function findAlignedLines(dv, other) { | |
411 var alignable = alignableFor(dv.edit, dv.chunks, false), result = [] | |
412 if (other) for (var i = 0, j = 0; i < other.chunks.length; i++) { | |
413 var n = other.chunks[i].editTo | |
414 while (j < alignable.length && alignable[j] < n) j++ | |
415 if (j == alignable.length || alignable[j] != n) alignable.splice(j++, 0, n) | |
416 } | |
417 for (var i = 0; i < alignable.length; i++) | |
418 result.push([alignable[i], null, null]) | |
419 | |
420 mergeAlignable(result, alignableFor(dv.orig, dv.chunks, true), dv.chunks, 1) | |
421 if (other) | |
422 mergeAlignable(result, alignableFor(other.orig, other.chunks, true), other.chunks, 2) | |
423 | |
424 return result | |
425 } | |
426 | |
427 function alignChunks(dv, force) { | |
428 if (!dv.dealigned && !force) return; | |
429 if (!dv.orig.curOp) return dv.orig.operation(function() { | |
430 alignChunks(dv, force); | |
431 }); | |
432 | |
433 dv.dealigned = false; | |
434 var other = dv.mv.left == dv ? dv.mv.right : dv.mv.left; | |
435 if (other) { | |
436 ensureDiff(other); | |
437 other.dealigned = false; | |
438 } | |
439 var linesToAlign = findAlignedLines(dv, other); | |
440 | |
441 // Clear old aligners | |
442 var aligners = dv.mv.aligners; | |
443 for (var i = 0; i < aligners.length; i++) | |
444 aligners[i].clear(); | |
445 aligners.length = 0; | |
446 | |
447 var cm = [dv.edit, dv.orig], scroll = [], offset = [] | |
448 if (other) cm.push(other.orig); | |
449 for (var i = 0; i < cm.length; i++) { | |
450 scroll.push(cm[i].getScrollInfo().top); | |
451 offset.push(-cm[i].getScrollerElement().getBoundingClientRect().top) | |
452 } | |
453 | |
454 if (offset[0] != offset[1] || cm.length == 3 && offset[1] != offset[2]) | |
455 alignLines(cm, offset, [0, 0, 0], aligners) | |
456 for (var ln = 0; ln < linesToAlign.length; ln++) | |
457 alignLines(cm, offset, linesToAlign[ln], aligners); | |
458 | |
459 for (var i = 0; i < cm.length; i++) | |
460 cm[i].scrollTo(null, scroll[i]); | |
461 } | |
462 | |
463 function alignLines(cm, cmOffset, lines, aligners) { | |
464 var maxOffset = -1e8, offset = []; | |
465 for (var i = 0; i < cm.length; i++) if (lines[i] != null) { | |
466 var off = cm[i].heightAtLine(lines[i], "local") - cmOffset[i]; | |
467 offset[i] = off; | |
468 maxOffset = Math.max(maxOffset, off); | |
469 } | |
470 for (var i = 0; i < cm.length; i++) if (lines[i] != null) { | |
471 var diff = maxOffset - offset[i]; | |
472 if (diff > 1) | |
473 aligners.push(padAbove(cm[i], lines[i], diff)); | |
474 } | |
475 } | |
476 | |
477 function padAbove(cm, line, size) { | |
478 var above = true; | |
479 if (line > cm.lastLine()) { | |
480 line--; | |
481 above = false; | |
482 } | |
483 var elt = document.createElement("div"); | |
484 elt.className = "CodeMirror-merge-spacer"; | |
485 elt.style.height = size + "px"; elt.style.minWidth = "1px"; | |
486 return cm.addLineWidget(line, elt, {height: size, above: above, mergeSpacer: true, handleMouseEvents: true}); | |
487 } | |
488 | |
489 function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) { | |
490 var flip = dv.type == "left"; | |
491 var top = dv.orig.heightAtLine(chunk.origFrom, "local", true) - sTopOrig; | |
492 if (dv.svg) { | |
493 var topLpx = top; | |
494 var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local", true) - sTopEdit; | |
495 if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; } | |
496 var botLpx = dv.orig.heightAtLine(chunk.origTo, "local", true) - sTopOrig; | |
497 var botRpx = dv.edit.heightAtLine(chunk.editTo, "local", true) - sTopEdit; | |
498 if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; } | |
499 var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx; | |
500 var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx; | |
501 attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")), | |
502 "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z", | |
503 "class", dv.classes.connect); | |
504 } | |
505 if (dv.copyButtons) { | |
506 var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", | |
507 "CodeMirror-merge-copy")); | |
508 var editOriginals = dv.mv.options.allowEditingOriginals; | |
509 copy.title = dv.edit.phrase(editOriginals ? "Push to left" : "Revert chunk"); | |
510 copy.chunk = chunk; | |
511 copy.style.top = (chunk.origTo > chunk.origFrom ? top : dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit) + "px"; | |
512 copy.setAttribute("role", "button"); | |
513 copy.setAttribute("tabindex", "0"); | |
514 copy.setAttribute("aria-label", copy.title); | |
515 | |
516 if (editOriginals) { | |
517 var topReverse = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit; | |
518 var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc", | |
519 "CodeMirror-merge-copy-reverse")); | |
520 copyReverse.title = "Push to right"; | |
521 copyReverse.chunk = {editFrom: chunk.origFrom, editTo: chunk.origTo, | |
522 origFrom: chunk.editFrom, origTo: chunk.editTo}; | |
523 copyReverse.style.top = topReverse + "px"; | |
524 dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px"; | |
525 copyReverse.setAttribute("role", "button"); | |
526 copyReverse.setAttribute("tabindex", "0"); | |
527 copyReverse.setAttribute("aria-label", copyReverse.title); | |
528 } | |
529 } | |
530 } | |
531 | |
532 function copyChunk(dv, to, from, chunk) { | |
533 if (dv.diffOutOfDate) return; | |
534 var origStart = chunk.origTo > from.lastLine() ? Pos(chunk.origFrom - 1) : Pos(chunk.origFrom, 0) | |
535 var origEnd = Pos(chunk.origTo, 0) | |
536 var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0) | |
537 var editEnd = Pos(chunk.editTo, 0) | |
538 var handler = dv.mv.options.revertChunk | |
539 if (handler) | |
540 handler(dv.mv, from, origStart, origEnd, to, editStart, editEnd) | |
541 else | |
542 to.replaceRange(from.getRange(origStart, origEnd), editStart, editEnd) | |
543 } | |
544 | |
545 // Merge view, containing 0, 1, or 2 diff views. | |
546 | |
547 var MergeView = CodeMirror.MergeView = function(node, options) { | |
548 if (!(this instanceof MergeView)) return new MergeView(node, options); | |
549 | |
550 this.options = options; | |
551 var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight; | |
552 | |
553 var hasLeft = origLeft != null, hasRight = origRight != null; | |
554 var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0); | |
555 var wrap = [], left = this.left = null, right = this.right = null; | |
556 var self = this; | |
557 | |
558 if (hasLeft) { | |
559 left = this.left = new DiffView(this, "left"); | |
560 var leftPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-left"); | |
561 wrap.push(leftPane); | |
562 wrap.push(buildGap(left)); | |
563 } | |
564 | |
565 var editPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-editor"); | |
566 wrap.push(editPane); | |
567 | |
568 if (hasRight) { | |
569 right = this.right = new DiffView(this, "right"); | |
570 wrap.push(buildGap(right)); | |
571 var rightPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-right"); | |
572 wrap.push(rightPane); | |
573 } | |
574 | |
575 (hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost"; | |
576 | |
577 wrap.push(elt("div", null, null, "height: 0; clear: both;")); | |
578 | |
579 var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane")); | |
580 this.edit = CodeMirror(editPane, copyObj(options)); | |
581 | |
582 if (left) left.init(leftPane, origLeft, options); | |
583 if (right) right.init(rightPane, origRight, options); | |
584 if (options.collapseIdentical) | |
585 this.editor().operation(function() { | |
586 collapseIdenticalStretches(self, options.collapseIdentical); | |
587 }); | |
588 if (options.connect == "align") { | |
589 this.aligners = []; | |
590 alignChunks(this.left || this.right, true); | |
591 } | |
592 if (left) left.registerEvents(right) | |
593 if (right) right.registerEvents(left) | |
594 | |
595 | |
596 var onResize = function() { | |
597 if (left) makeConnections(left); | |
598 if (right) makeConnections(right); | |
599 }; | |
600 CodeMirror.on(window, "resize", onResize); | |
601 var resizeInterval = setInterval(function() { | |
602 for (var p = wrapElt.parentNode; p && p != document.body; p = p.parentNode) {} | |
603 if (!p) { clearInterval(resizeInterval); CodeMirror.off(window, "resize", onResize); } | |
604 }, 5000); | |
605 }; | |
606 | |
607 function buildGap(dv) { | |
608 var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock"); | |
609 lock.setAttribute("role", "button"); | |
610 lock.setAttribute("tabindex", "0"); | |
611 var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap"); | |
612 CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); }); | |
613 CodeMirror.on(lock, "keyup", function(e) { (e.key === "Enter" || e.code === "Space") && setScrollLock(dv, !dv.lockScroll); }); | |
614 var gapElts = [lockWrap]; | |
615 if (dv.mv.options.revertButtons !== false) { | |
616 dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type); | |
617 var copyButtons = function(e) { | |
618 var node = e.target || e.srcElement; | |
619 if (!node.chunk) return; | |
620 if (node.className == "CodeMirror-merge-copy-reverse") { | |
621 copyChunk(dv, dv.orig, dv.edit, node.chunk); | |
622 return; | |
623 } | |
624 copyChunk(dv, dv.edit, dv.orig, node.chunk); | |
625 } | |
626 CodeMirror.on(dv.copyButtons, "click", copyButtons); | |
627 CodeMirror.on(dv.copyButtons, "keyup", function(e) { (e.key === "Enter" || e.code === "Space") && copyButtons(e); }); | |
628 gapElts.unshift(dv.copyButtons); | |
629 } | |
630 if (dv.mv.options.connect != "align") { | |
631 var svg = document.createElementNS && document.createElementNS(svgNS, "svg"); | |
632 if (svg && !svg.createSVGRect) svg = null; | |
633 dv.svg = svg; | |
634 if (svg) gapElts.push(svg); | |
635 } | |
636 | |
637 return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap"); | |
638 } | |
639 | |
640 MergeView.prototype = { | |
641 constructor: MergeView, | |
642 editor: function() { return this.edit; }, | |
643 rightOriginal: function() { return this.right && this.right.orig; }, | |
644 leftOriginal: function() { return this.left && this.left.orig; }, | |
645 setShowDifferences: function(val) { | |
646 if (this.right) this.right.setShowDifferences(val); | |
647 if (this.left) this.left.setShowDifferences(val); | |
648 }, | |
649 rightChunks: function() { | |
650 if (this.right) { ensureDiff(this.right); return this.right.chunks; } | |
651 }, | |
652 leftChunks: function() { | |
653 if (this.left) { ensureDiff(this.left); return this.left.chunks; } | |
654 } | |
655 }; | |
656 | |
657 function asString(obj) { | |
658 if (typeof obj == "string") return obj; | |
659 else return obj.getValue(); | |
660 } | |
661 | |
662 // Operations on diffs | |
663 var dmp; | |
664 function getDiff(a, b, ignoreWhitespace) { | |
665 if (!dmp) dmp = new diff_match_patch(); | |
666 | |
667 var diff = dmp.diff_main(a, b); | |
668 // The library sometimes leaves in empty parts, which confuse the algorithm | |
669 for (var i = 0; i < diff.length; ++i) { | |
670 var part = diff[i]; | |
671 if (ignoreWhitespace ? !/[^ \t]/.test(part[1]) : !part[1]) { | |
672 diff.splice(i--, 1); | |
673 } else if (i && diff[i - 1][0] == part[0]) { | |
674 diff.splice(i--, 1); | |
675 diff[i][1] += part[1]; | |
676 } | |
677 } | |
678 return diff; | |
679 } | |
680 | |
681 function getChunks(diff) { | |
682 var chunks = []; | |
683 if (!diff.length) return chunks; | |
684 var startEdit = 0, startOrig = 0; | |
685 var edit = Pos(0, 0), orig = Pos(0, 0); | |
686 for (var i = 0; i < diff.length; ++i) { | |
687 var part = diff[i], tp = part[0]; | |
688 if (tp == DIFF_EQUAL) { | |
689 var startOff = !startOfLineClean(diff, i) || edit.line < startEdit || orig.line < startOrig ? 1 : 0; | |
690 var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff; | |
691 moveOver(edit, part[1], null, orig); | |
692 var endOff = endOfLineClean(diff, i) ? 1 : 0; | |
693 var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff; | |
694 if (cleanToEdit > cleanFromEdit) { | |
695 if (i) chunks.push({origFrom: startOrig, origTo: cleanFromOrig, | |
696 editFrom: startEdit, editTo: cleanFromEdit}); | |
697 startEdit = cleanToEdit; startOrig = cleanToOrig; | |
698 } | |
699 } else { | |
700 moveOver(tp == DIFF_INSERT ? edit : orig, part[1]); | |
701 } | |
702 } | |
703 if (startEdit <= edit.line || startOrig <= orig.line) | |
704 chunks.push({origFrom: startOrig, origTo: orig.line + 1, | |
705 editFrom: startEdit, editTo: edit.line + 1}); | |
706 return chunks; | |
707 } | |
708 | |
709 function endOfLineClean(diff, i) { | |
710 if (i == diff.length - 1) return true; | |
711 var next = diff[i + 1][1]; | |
712 if ((next.length == 1 && i < diff.length - 2) || next.charCodeAt(0) != 10) return false; | |
713 if (i == diff.length - 2) return true; | |
714 next = diff[i + 2][1]; | |
715 return (next.length > 1 || i == diff.length - 3) && next.charCodeAt(0) == 10; | |
716 } | |
717 | |
718 function startOfLineClean(diff, i) { | |
719 if (i == 0) return true; | |
720 var last = diff[i - 1][1]; | |
721 if (last.charCodeAt(last.length - 1) != 10) return false; | |
722 if (i == 1) return true; | |
723 last = diff[i - 2][1]; | |
724 return last.charCodeAt(last.length - 1) == 10; | |
725 } | |
726 | |
727 function chunkBoundariesAround(chunks, n, nInEdit) { | |
728 var beforeE, afterE, beforeO, afterO; | |
729 for (var i = 0; i < chunks.length; i++) { | |
730 var chunk = chunks[i]; | |
731 var fromLocal = nInEdit ? chunk.editFrom : chunk.origFrom; | |
732 var toLocal = nInEdit ? chunk.editTo : chunk.origTo; | |
733 if (afterE == null) { | |
734 if (fromLocal > n) { afterE = chunk.editFrom; afterO = chunk.origFrom; } | |
735 else if (toLocal > n) { afterE = chunk.editTo; afterO = chunk.origTo; } | |
736 } | |
737 if (toLocal <= n) { beforeE = chunk.editTo; beforeO = chunk.origTo; } | |
738 else if (fromLocal <= n) { beforeE = chunk.editFrom; beforeO = chunk.origFrom; } | |
739 } | |
740 return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}}; | |
741 } | |
742 | |
743 function collapseSingle(cm, from, to) { | |
744 cm.addLineClass(from, "wrap", "CodeMirror-merge-collapsed-line"); | |
745 var widget = document.createElement("span"); | |
746 widget.className = "CodeMirror-merge-collapsed-widget"; | |
747 widget.title = cm.phrase("Identical text collapsed. Click to expand."); | |
748 var mark = cm.markText(Pos(from, 0), Pos(to - 1), { | |
749 inclusiveLeft: true, | |
750 inclusiveRight: true, | |
751 replacedWith: widget, | |
752 clearOnEnter: true | |
753 }); | |
754 function clear() { | |
755 mark.clear(); | |
756 cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line"); | |
757 } | |
758 if (mark.explicitlyCleared) clear(); | |
759 CodeMirror.on(widget, "click", clear); | |
760 mark.on("clear", clear); | |
761 CodeMirror.on(widget, "click", clear); | |
762 return {mark: mark, clear: clear}; | |
763 } | |
764 | |
765 function collapseStretch(size, editors) { | |
766 var marks = []; | |
767 function clear() { | |
768 for (var i = 0; i < marks.length; i++) marks[i].clear(); | |
769 } | |
770 for (var i = 0; i < editors.length; i++) { | |
771 var editor = editors[i]; | |
772 var mark = collapseSingle(editor.cm, editor.line, editor.line + size); | |
773 marks.push(mark); | |
774 mark.mark.on("clear", clear); | |
775 } | |
776 return marks[0].mark; | |
777 } | |
778 | |
779 function unclearNearChunks(dv, margin, off, clear) { | |
780 for (var i = 0; i < dv.chunks.length; i++) { | |
781 var chunk = dv.chunks[i]; | |
782 for (var l = chunk.editFrom - margin; l < chunk.editTo + margin; l++) { | |
783 var pos = l + off; | |
784 if (pos >= 0 && pos < clear.length) clear[pos] = false; | |
785 } | |
786 } | |
787 } | |
788 | |
789 function collapseIdenticalStretches(mv, margin) { | |
790 if (typeof margin != "number") margin = 2; | |
791 var clear = [], edit = mv.editor(), off = edit.firstLine(); | |
792 for (var l = off, e = edit.lastLine(); l <= e; l++) clear.push(true); | |
793 if (mv.left) unclearNearChunks(mv.left, margin, off, clear); | |
794 if (mv.right) unclearNearChunks(mv.right, margin, off, clear); | |
795 | |
796 for (var i = 0; i < clear.length; i++) { | |
797 if (clear[i]) { | |
798 var line = i + off; | |
799 for (var size = 1; i < clear.length - 1 && clear[i + 1]; i++, size++) {} | |
800 if (size > margin) { | |
801 var editors = [{line: line, cm: edit}]; | |
802 if (mv.left) editors.push({line: getMatchingOrigLine(line, mv.left.chunks), cm: mv.left.orig}); | |
803 if (mv.right) editors.push({line: getMatchingOrigLine(line, mv.right.chunks), cm: mv.right.orig}); | |
804 var mark = collapseStretch(size, editors); | |
805 if (mv.options.onCollapse) mv.options.onCollapse(mv, line, size, mark); | |
806 } | |
807 } | |
808 } | |
809 } | |
810 | |
811 // General utilities | |
812 | |
813 function elt(tag, content, className, style) { | |
814 var e = document.createElement(tag); | |
815 if (className) e.className = className; | |
816 if (style) e.style.cssText = style; | |
817 if (typeof content == "string") e.appendChild(document.createTextNode(content)); | |
818 else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); | |
819 return e; | |
820 } | |
821 | |
822 function clear(node) { | |
823 for (var count = node.childNodes.length; count > 0; --count) | |
824 node.removeChild(node.firstChild); | |
825 } | |
826 | |
827 function attrs(elt) { | |
828 for (var i = 1; i < arguments.length; i += 2) | |
829 elt.setAttribute(arguments[i], arguments[i+1]); | |
830 } | |
831 | |
832 function copyObj(obj, target) { | |
833 if (!target) target = {}; | |
834 for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop]; | |
835 return target; | |
836 } | |
837 | |
838 function moveOver(pos, str, copy, other) { | |
839 var out = copy ? Pos(pos.line, pos.ch) : pos, at = 0; | |
840 for (;;) { | |
841 var nl = str.indexOf("\n", at); | |
842 if (nl == -1) break; | |
843 ++out.line; | |
844 if (other) ++other.line; | |
845 at = nl + 1; | |
846 } | |
847 out.ch = (at ? 0 : out.ch) + (str.length - at); | |
848 if (other) other.ch = (at ? 0 : other.ch) + (str.length - at); | |
849 return out; | |
850 } | |
851 | |
852 // Tracks collapsed markers and line widgets, in order to be able to | |
853 // accurately align the content of two editors. | |
854 | |
855 var F_WIDGET = 1, F_WIDGET_BELOW = 2, F_MARKER = 4 | |
856 | |
857 function TrackAlignable(cm) { | |
858 this.cm = cm | |
859 this.alignable = [] | |
860 this.height = cm.doc.height | |
861 var self = this | |
862 cm.on("markerAdded", function(_, marker) { | |
863 if (!marker.collapsed) return | |
864 var found = marker.find(1) | |
865 if (found != null) self.set(found.line, F_MARKER) | |
866 }) | |
867 cm.on("markerCleared", function(_, marker, _min, max) { | |
868 if (max != null && marker.collapsed) | |
869 self.check(max, F_MARKER, self.hasMarker) | |
870 }) | |
871 cm.on("markerChanged", this.signal.bind(this)) | |
872 cm.on("lineWidgetAdded", function(_, widget, lineNo) { | |
873 if (widget.mergeSpacer) return | |
874 if (widget.above) self.set(lineNo - 1, F_WIDGET_BELOW) | |
875 else self.set(lineNo, F_WIDGET) | |
876 }) | |
877 cm.on("lineWidgetCleared", function(_, widget, lineNo) { | |
878 if (widget.mergeSpacer) return | |
879 if (widget.above) self.check(lineNo - 1, F_WIDGET_BELOW, self.hasWidgetBelow) | |
880 else self.check(lineNo, F_WIDGET, self.hasWidget) | |
881 }) | |
882 cm.on("lineWidgetChanged", this.signal.bind(this)) | |
883 cm.on("change", function(_, change) { | |
884 var start = change.from.line, nBefore = change.to.line - change.from.line | |
885 var nAfter = change.text.length - 1, end = start + nAfter | |
886 if (nBefore || nAfter) self.map(start, nBefore, nAfter) | |
887 self.check(end, F_MARKER, self.hasMarker) | |
888 if (nBefore || nAfter) self.check(change.from.line, F_MARKER, self.hasMarker) | |
889 }) | |
890 cm.on("viewportChange", function() { | |
891 if (self.cm.doc.height != self.height) self.signal() | |
892 }) | |
893 } | |
894 | |
895 TrackAlignable.prototype = { | |
896 signal: function() { | |
897 CodeMirror.signal(this, "realign") | |
898 this.height = this.cm.doc.height | |
899 }, | |
900 | |
901 set: function(n, flags) { | |
902 var pos = -1 | |
903 for (; pos < this.alignable.length; pos += 2) { | |
904 var diff = this.alignable[pos] - n | |
905 if (diff == 0) { | |
906 if ((this.alignable[pos + 1] & flags) == flags) return | |
907 this.alignable[pos + 1] |= flags | |
908 this.signal() | |
909 return | |
910 } | |
911 if (diff > 0) break | |
912 } | |
913 this.signal() | |
914 this.alignable.splice(pos, 0, n, flags) | |
915 }, | |
916 | |
917 find: function(n) { | |
918 for (var i = 0; i < this.alignable.length; i += 2) | |
919 if (this.alignable[i] == n) return i | |
920 return -1 | |
921 }, | |
922 | |
923 check: function(n, flag, pred) { | |
924 var found = this.find(n) | |
925 if (found == -1 || !(this.alignable[found + 1] & flag)) return | |
926 if (!pred.call(this, n)) { | |
927 this.signal() | |
928 var flags = this.alignable[found + 1] & ~flag | |
929 if (flags) this.alignable[found + 1] = flags | |
930 else this.alignable.splice(found, 2) | |
931 } | |
932 }, | |
933 | |
934 hasMarker: function(n) { | |
935 var handle = this.cm.getLineHandle(n) | |
936 if (handle.markedSpans) for (var i = 0; i < handle.markedSpans.length; i++) | |
937 if (handle.markedSpans[i].marker.collapsed && handle.markedSpans[i].to != null) | |
938 return true | |
939 return false | |
940 }, | |
941 | |
942 hasWidget: function(n) { | |
943 var handle = this.cm.getLineHandle(n) | |
944 if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++) | |
945 if (!handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true | |
946 return false | |
947 }, | |
948 | |
949 hasWidgetBelow: function(n) { | |
950 if (n == this.cm.lastLine()) return false | |
951 var handle = this.cm.getLineHandle(n + 1) | |
952 if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++) | |
953 if (handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true | |
954 return false | |
955 }, | |
956 | |
957 map: function(from, nBefore, nAfter) { | |
958 var diff = nAfter - nBefore, to = from + nBefore, widgetFrom = -1, widgetTo = -1 | |
959 for (var i = 0; i < this.alignable.length; i += 2) { | |
960 var n = this.alignable[i] | |
961 if (n == from && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetFrom = i | |
962 if (n == to && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetTo = i | |
963 if (n <= from) continue | |
964 else if (n < to) this.alignable.splice(i--, 2) | |
965 else this.alignable[i] += diff | |
966 } | |
967 if (widgetFrom > -1) { | |
968 var flags = this.alignable[widgetFrom + 1] | |
969 if (flags == F_WIDGET_BELOW) this.alignable.splice(widgetFrom, 2) | |
970 else this.alignable[widgetFrom + 1] = flags & ~F_WIDGET_BELOW | |
971 } | |
972 if (widgetTo > -1 && nAfter) | |
973 this.set(from + nAfter, F_WIDGET_BELOW) | |
974 } | |
975 } | |
976 | |
977 function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; } | |
978 function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; } | |
979 function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } | |
980 | |
981 function findPrevDiff(chunks, start, isOrig) { | |
982 for (var i = chunks.length - 1; i >= 0; i--) { | |
983 var chunk = chunks[i]; | |
984 var to = (isOrig ? chunk.origTo : chunk.editTo) - 1; | |
985 if (to < start) return to; | |
986 } | |
987 } | |
988 | |
989 function findNextDiff(chunks, start, isOrig) { | |
990 for (var i = 0; i < chunks.length; i++) { | |
991 var chunk = chunks[i]; | |
992 var from = (isOrig ? chunk.origFrom : chunk.editFrom); | |
993 if (from > start) return from; | |
994 } | |
995 } | |
996 | |
997 function goNearbyDiff(cm, dir) { | |
998 var found = null, views = cm.state.diffViews, line = cm.getCursor().line; | |
999 if (views) for (var i = 0; i < views.length; i++) { | |
1000 var dv = views[i], isOrig = cm == dv.orig; | |
1001 ensureDiff(dv); | |
1002 var pos = dir < 0 ? findPrevDiff(dv.chunks, line, isOrig) : findNextDiff(dv.chunks, line, isOrig); | |
1003 if (pos != null && (found == null || (dir < 0 ? pos > found : pos < found))) | |
1004 found = pos; | |
1005 } | |
1006 if (found != null) | |
1007 cm.setCursor(found, 0); | |
1008 else | |
1009 return CodeMirror.Pass; | |
1010 } | |
1011 | |
1012 CodeMirror.commands.goNextDiff = function(cm) { | |
1013 return goNearbyDiff(cm, 1); | |
1014 }; | |
1015 CodeMirror.commands.goPrevDiff = function(cm) { | |
1016 return goNearbyDiff(cm, -1); | |
1017 }; | |
1018 }); |