| 1 |
(function() { |
| 2 |
"use strict"; |
| 3 |
|
| 4 |
var Pos = CodeMirror.Pos; |
| 5 |
var svgNS = "http://www.w3.org/2000/svg"; |
| 6 |
|
| 7 |
function DiffView(mv, type) { |
| 8 |
this.mv = mv; |
| 9 |
this.type = type; |
| 10 |
this.classes = type == "left" |
| 11 |
? {chunk: "CodeMirror-merge-l-chunk", |
| 12 |
start: "CodeMirror-merge-l-chunk-start", |
| 13 |
end: "CodeMirror-merge-l-chunk-end", |
| 14 |
insert: "CodeMirror-merge-l-inserted", |
| 15 |
del: "CodeMirror-merge-l-deleted", |
| 16 |
connect: "CodeMirror-merge-l-connect"} |
| 17 |
: {chunk: "CodeMirror-merge-r-chunk", |
| 18 |
start: "CodeMirror-merge-r-chunk-start", |
| 19 |
end: "CodeMirror-merge-r-chunk-end", |
| 20 |
insert: "CodeMirror-merge-r-inserted", |
| 21 |
del: "CodeMirror-merge-r-deleted", |
| 22 |
connect: "CodeMirror-merge-r-connect"}; |
| 23 |
} |
| 24 |
|
| 25 |
DiffView.prototype = { |
| 26 |
constructor: DiffView, |
| 27 |
init: function(pane, orig, options) { |
| 28 |
this.edit = this.mv.edit; |
| 29 |
this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: true}, copyObj(options))); |
| 30 |
|
| 31 |
this.diff = getDiff(orig, options.value); |
| 32 |
this.diffOutOfDate = false; |
| 33 |
|
| 34 |
this.showDifferences = options.showDifferences !== false; |
| 35 |
this.forceUpdate = registerUpdate(this); |
| 36 |
setScrollLock(this, true, false); |
| 37 |
registerScroll(this); |
| 38 |
}, |
| 39 |
setShowDifferences: function(val) { |
| 40 |
val = val !== false; |
| 41 |
if (val != this.showDifferences) { |
| 42 |
this.showDifferences = val; |
| 43 |
this.forceUpdate("full"); |
| 44 |
} |
| 45 |
} |
| 46 |
}; |
| 47 |
|
| 48 |
function registerUpdate(dv) { |
| 49 |
var edit = {from: 0, to: 0, marked: []}; |
| 50 |
var orig = {from: 0, to: 0, marked: []}; |
| 51 |
var debounceChange; |
| 52 |
function update(mode) { |
| 53 |
if (mode == "full") { |
| 54 |
if (dv.svg) clear(dv.svg); |
| 55 |
clear(dv.copyButtons); |
| 56 |
clearMarks(dv.edit, edit.marked, dv.classes); |
| 57 |
clearMarks(dv.orig, orig.marked, dv.classes); |
| 58 |
edit.from = edit.to = orig.from = orig.to = 0; |
| 59 |
} |
| 60 |
if (dv.diffOutOfDate) { |
| 61 |
dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue()); |
| 62 |
dv.diffOutOfDate = false; |
| 63 |
CodeMirror.signal(dv.edit, "updateDiff", dv.diff); |
| 64 |
} |
| 65 |
if (dv.showDifferences) { |
| 66 |
updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes); |
| 67 |
updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes); |
| 68 |
} |
| 69 |
drawConnectors(dv); |
| 70 |
} |
| 71 |
function set(slow) { |
| 72 |
clearTimeout(debounceChange); |
| 73 |
debounceChange = setTimeout(update, slow == true ? 250 : 100); |
| 74 |
} |
| 75 |
function change() { |
| 76 |
if (!dv.diffOutOfDate) { |
| 77 |
dv.diffOutOfDate = true; |
| 78 |
edit.from = edit.to = orig.from = orig.to = 0; |
| 79 |
} |
| 80 |
set(true); |
| 81 |
} |
| 82 |
dv.edit.on("change", change); |
| 83 |
dv.orig.on("change", change); |
| 84 |
dv.edit.on("viewportChange", set); |
| 85 |
dv.orig.on("viewportChange", set); |
| 86 |
update(); |
| 87 |
return update; |
| 88 |
} |
| 89 |
|
| 90 |
function registerScroll(dv) { |
| 91 |
dv.edit.on("scroll", function() { |
| 92 |
syncScroll(dv, DIFF_INSERT) && drawConnectors(dv); |
| 93 |
}); |
| 94 |
dv.orig.on("scroll", function() { |
| 95 |
syncScroll(dv, DIFF_DELETE) && drawConnectors(dv); |
| 96 |
}); |
| 97 |
} |
| 98 |
|
| 99 |
function syncScroll(dv, type) { |
| 100 |
// Change handler will do a refresh after a timeout when diff is out of date |
| 101 |
if (dv.diffOutOfDate) return false; |
| 102 |
if (!dv.lockScroll) return true; |
| 103 |
var editor, other, now = +new Date; |
| 104 |
if (type == DIFF_INSERT) { editor = dv.edit; other = dv.orig; } |
| 105 |
else { editor = dv.orig; other = dv.edit; } |
| 106 |
// Don't take action if the position of this editor was recently set |
| 107 |
// (to prevent feedback loops) |
| 108 |
if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 50 > now) return false; |
| 109 |
|
| 110 |
var sInfo = editor.getScrollInfo(), halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen; |
| 111 |
var mid = editor.lineAtHeight(midY, "local"); |
| 112 |
var around = chunkBoundariesAround(dv.diff, mid, type == DIFF_INSERT); |
| 113 |
var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig); |
| 114 |
var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit); |
| 115 |
var ratio = (midY - off.top) / (off.bot - off.top); |
| 116 |
var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top); |
| 117 |
|
| 118 |
var botDist, mix; |
| 119 |
// Some careful tweaking to make sure no space is left out of view |
| 120 |
// when scrolling to top or bottom. |
| 121 |
if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) { |
| 122 |
targetPos = targetPos * mix + sInfo.top * (1 - mix); |
| 123 |
} else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) { |
| 124 |
var otherInfo = other.getScrollInfo(); |
| 125 |
var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos; |
| 126 |
if (botDistOther > botDist && (mix = botDist / halfScreen) < 1) |
| 127 |
targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix); |
| 128 |
} |
| 129 |
|
| 130 |
other.scrollTo(sInfo.left, targetPos); |
| 131 |
other.state.scrollSetAt = now; |
| 132 |
other.state.scrollSetBy = dv; |
| 133 |
return true; |
| 134 |
} |
| 135 |
|
| 136 |
function getOffsets(editor, around) { |
| 137 |
var bot = around.after; |
| 138 |
if (bot == null) bot = editor.lastLine() + 1; |
| 139 |
return {top: editor.heightAtLine(around.before || 0, "local"), |
| 140 |
bot: editor.heightAtLine(bot, "local")}; |
| 141 |
} |
| 142 |
|
| 143 |
function setScrollLock(dv, val, action) { |
| 144 |
dv.lockScroll = val; |
| 145 |
if (val && action != false) syncScroll(dv, DIFF_INSERT) && drawConnectors(dv); |
| 146 |
dv.lockButton.innerHTML = val ? "\u21db\u21da" : "\u21db \u21da"; |
| 147 |
} |
| 148 |
|
| 149 |
// Updating the marks for editor content |
| 150 |
|
| 151 |
function clearMarks(editor, arr, classes) { |
| 152 |
for (var i = 0; i < arr.length; ++i) { |
| 153 |
var mark = arr[i]; |
| 154 |
if (mark instanceof CodeMirror.TextMarker) { |
| 155 |
mark.clear(); |
| 156 |
} else { |
| 157 |
editor.removeLineClass(mark, "background", classes.chunk); |
| 158 |
editor.removeLineClass(mark, "background", classes.start); |
| 159 |
editor.removeLineClass(mark, "background", classes.end); |
| 160 |
} |
| 161 |
} |
| 162 |
arr.length = 0; |
| 163 |
} |
| 164 |
|
| 165 |
// FIXME maybe add a margin around viewport to prevent too many updates |
| 166 |
function updateMarks(editor, diff, state, type, classes) { |
| 167 |
var vp = editor.getViewport(); |
| 168 |
editor.operation(function() { |
| 169 |
if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) { |
| 170 |
clearMarks(editor, state.marked, classes); |
| 171 |
markChanges(editor, diff, type, state.marked, vp.from, vp.to, classes); |
| 172 |
state.from = vp.from; state.to = vp.to; |
| 173 |
} else { |
| 174 |
if (vp.from < state.from) { |
| 175 |
markChanges(editor, diff, type, state.marked, vp.from, state.from, classes); |
| 176 |
state.from = vp.from; |
| 177 |
} |
| 178 |
if (vp.to > state.to) { |
| 179 |
markChanges(editor, diff, type, state.marked, state.to, vp.to, classes); |
| 180 |
state.to = vp.to; |
| 181 |
} |
| 182 |
} |
| 183 |
}); |
| 184 |
} |
| 185 |
|
| 186 |
function markChanges(editor, diff, type, marks, from, to, classes) { |
| 187 |
var pos = Pos(0, 0); |
| 188 |
var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1)); |
| 189 |
var cls = type == DIFF_DELETE ? classes.del : classes.insert; |
| 190 |
function markChunk(start, end) { |
| 191 |
var bfrom = Math.max(from, start), bto = Math.min(to, end); |
| 192 |
for (var i = bfrom; i < bto; ++i) { |
| 193 |
var line = editor.addLineClass(i, "background", classes.chunk); |
| 194 |
if (i == start) editor.addLineClass(line, "background", classes.start); |
| 195 |
if (i == end - 1) editor.addLineClass(line, "background", classes.end); |
| 196 |
marks.push(line); |
| 197 |
} |
| 198 |
// When the chunk is empty, make sure a horizontal line shows up |
| 199 |
if (start == end && bfrom == end && bto == end) { |
| 200 |
if (bfrom) |
| 201 |
marks.push(editor.addLineClass(bfrom - 1, "background", classes.end)); |
| 202 |
else |
| 203 |
marks.push(editor.addLineClass(bfrom, "background", classes.start)); |
| 204 |
} |
| 205 |
} |
| 206 |
|
| 207 |
var chunkStart = 0; |
| 208 |
for (var i = 0; i < diff.length; ++i) { |
| 209 |
var part = diff[i], tp = part[0], str = part[1]; |
| 210 |
if (tp == DIFF_EQUAL) { |
| 211 |
var cleanFrom = pos.line + (startOfLineClean(diff, i) ? 0 : 1); |
| 212 |
moveOver(pos, str); |
| 213 |
var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0); |
| 214 |
if (cleanTo > cleanFrom) { |
| 215 |
if (i) markChunk(chunkStart, cleanFrom); |
| 216 |
chunkStart = cleanTo; |
| 217 |
} |
| 218 |
} else { |
| 219 |
if (tp == type) { |
| 220 |
var end = moveOver(pos, str, true); |
| 221 |
var a = posMax(top, pos), b = posMin(bot, end); |
| 222 |
if (!posEq(a, b)) |
| 223 |
marks.push(editor.markText(a, b, {className: cls})); |
| 224 |
pos = end; |
| 225 |
} |
| 226 |
} |
| 227 |
} |
| 228 |
if (chunkStart <= pos.line) markChunk(chunkStart, pos.line + 1); |
| 229 |
} |
| 230 |
|
| 231 |
// Updating the gap between editor and original |
| 232 |
|
| 233 |
function drawConnectors(dv) { |
| 234 |
if (!dv.showDifferences) return; |
| 235 |
|
| 236 |
if (dv.svg) { |
| 237 |
clear(dv.svg); |
| 238 |
var w = dv.gap.offsetWidth; |
| 239 |
attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight); |
| 240 |
} |
| 241 |
clear(dv.copyButtons); |
| 242 |
|
| 243 |
var flip = dv.type == "left"; |
| 244 |
var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport(); |
| 245 |
var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top; |
| 246 |
iterateChunks(dv.diff, function(topOrig, botOrig, topEdit, botEdit) { |
| 247 |
if (topEdit > vpEdit.to || botEdit < vpEdit.from || |
| 248 |
topOrig > vpOrig.to || botOrig < vpOrig.from) |
| 249 |
return; |
| 250 |
var topLpx = dv.orig.heightAtLine(topOrig, "local") - sTopOrig, top = topLpx; |
| 251 |
if (dv.svg) { |
| 252 |
var topRpx = dv.edit.heightAtLine(topEdit, "local") - sTopEdit; |
| 253 |
if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; } |
| 254 |
var botLpx = dv.orig.heightAtLine(botOrig, "local") - sTopOrig; |
| 255 |
var botRpx = dv.edit.heightAtLine(botEdit, "local") - sTopEdit; |
| 256 |
if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; } |
| 257 |
var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx; |
| 258 |
var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx; |
| 259 |
attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")), |
| 260 |
"d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z", |
| 261 |
"class", dv.classes.connect); |
| 262 |
} |
| 263 |
var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", |
| 264 |
"CodeMirror-merge-copy")); |
| 265 |
copy.title = "Revert chunk"; |
| 266 |
copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig}; |
| 267 |
copy.style.top = top + "px"; |
| 268 |
}); |
| 269 |
} |
| 270 |
|
| 271 |
function copyChunk(dv, chunk) { |
| 272 |
if (dv.diffOutOfDate) return; |
| 273 |
dv.edit.replaceRange(dv.orig.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)), |
| 274 |
Pos(chunk.topEdit, 0), Pos(chunk.botEdit, 0)); |
| 275 |
} |
| 276 |
|
| 277 |
// Merge view, containing 0, 1, or 2 diff views. |
| 278 |
|
| 279 |
var MergeView = CodeMirror.MergeView = function(node, options) { |
| 280 |
if (!(this instanceof MergeView)) return new MergeView(node, options); |
| 281 |
|
| 282 |
var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight; |
| 283 |
var hasLeft = origLeft != null, hasRight = origRight != null; |
| 284 |
var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0); |
| 285 |
var wrap = [], left = this.left = null, right = this.right = null; |
| 286 |
|
| 287 |
if (hasLeft) { |
| 288 |
left = this.left = new DiffView(this, "left"); |
| 289 |
var leftPane = elt("div", null, "CodeMirror-merge-pane"); |
| 290 |
wrap.push(leftPane); |
| 291 |
wrap.push(buildGap(left)); |
| 292 |
} |
| 293 |
|
| 294 |
var editPane = elt("div", null, "CodeMirror-merge-pane"); |
| 295 |
wrap.push(editPane); |
| 296 |
|
| 297 |
if (hasRight) { |
| 298 |
right = this.right = new DiffView(this, "right"); |
| 299 |
wrap.push(buildGap(right)); |
| 300 |
var rightPane = elt("div", null, "CodeMirror-merge-pane"); |
| 301 |
wrap.push(rightPane); |
| 302 |
} |
| 303 |
|
| 304 |
(hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost"; |
| 305 |
|
| 306 |
wrap.push(elt("div", null, null, "height: 0; clear: both;")); |
| 307 |
var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane")); |
| 308 |
this.edit = CodeMirror(editPane, copyObj(options)); |
| 309 |
|
| 310 |
if (left) left.init(leftPane, origLeft, options); |
| 311 |
if (right) right.init(rightPane, origRight, options); |
| 312 |
|
| 313 |
var onResize = function() { |
| 314 |
if (left) drawConnectors(left); |
| 315 |
if (right) drawConnectors(right); |
| 316 |
}; |
| 317 |
CodeMirror.on(window, "resize", onResize); |
| 318 |
var resizeInterval = setInterval(function() { |
| 319 |
for (var p = wrapElt.parentNode; p && p != document.body; p = p.parentNode) {} |
| 320 |
if (!p) { clearInterval(resizeInterval); CodeMirror.off(window, "resize", onResize); } |
| 321 |
}, 5000); |
| 322 |
}; |
| 323 |
|
| 324 |
function buildGap(dv) { |
| 325 |
var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock"); |
| 326 |
lock.title = "Toggle locked scrolling"; |
| 327 |
var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap"); |
| 328 |
CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); }); |
| 329 |
dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type); |
| 330 |
CodeMirror.on(dv.copyButtons, "click", function(e) { |
| 331 |
var node = e.target || e.srcElement; |
| 332 |
if (node.chunk) copyChunk(dv, node.chunk); |
| 333 |
}); |
| 334 |
var gapElts = [dv.copyButtons, lockWrap]; |
| 335 |
var svg = document.createElementNS && document.createElementNS(svgNS, "svg"); |
| 336 |
if (svg && !svg.createSVGRect) svg = null; |
| 337 |
dv.svg = svg; |
| 338 |
if (svg) gapElts.push(svg); |
| 339 |
|
| 340 |
return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap"); |
| 341 |
} |
| 342 |
|
| 343 |
MergeView.prototype = { |
| 344 |
constuctor: MergeView, |
| 345 |
editor: function() { return this.edit; }, |
| 346 |
rightOriginal: function() { return this.right && this.right.orig; }, |
| 347 |
leftOriginal: function() { return this.left && this.left.orig; }, |
| 348 |
setShowDifferences: function(val) { |
| 349 |
if (this.right) this.right.setShowDifferences(val); |
| 350 |
if (this.left) this.left.setShowDifferences(val); |
| 351 |
} |
| 352 |
}; |
| 353 |
|
| 354 |
// Operations on diffs |
| 355 |
|
| 356 |
var dmp = new diff_match_patch(); |
| 357 |
function getDiff(a, b) { |
| 358 |
var diff = dmp.diff_main(a, b); |
| 359 |
dmp.diff_cleanupSemantic(diff); |
| 360 |
// The library sometimes leaves in empty parts, which confuse the algorithm |
| 361 |
for (var i = 0; i < diff.length; ++i) { |
| 362 |
var part = diff[i]; |
| 363 |
if (!part[1]) { |
| 364 |
diff.splice(i--, 1); |
| 365 |
} else if (i && diff[i - 1][0] == part[0]) { |
| 366 |
diff.splice(i--, 1); |
| 367 |
diff[i][1] += part[1]; |
| 368 |
} |
| 369 |
} |
| 370 |
return diff; |
| 371 |
} |
| 372 |
|
| 373 |
function iterateChunks(diff, f) { |
| 374 |
var startEdit = 0, startOrig = 0; |
| 375 |
var edit = Pos(0, 0), orig = Pos(0, 0); |
| 376 |
for (var i = 0; i < diff.length; ++i) { |
| 377 |
var part = diff[i], tp = part[0]; |
| 378 |
if (tp == DIFF_EQUAL) { |
| 379 |
var startOff = startOfLineClean(diff, i) ? 0 : 1; |
| 380 |
var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff; |
| 381 |
moveOver(edit, part[1], null, orig); |
| 382 |
var endOff = endOfLineClean(diff, i) ? 1 : 0; |
| 383 |
var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff; |
| 384 |
if (cleanToEdit > cleanFromEdit) { |
| 385 |
if (i) f(startOrig, cleanFromOrig, startEdit, cleanFromEdit); |
| 386 |
startEdit = cleanToEdit; startOrig = cleanToOrig; |
| 387 |
} |
| 388 |
} else { |
| 389 |
moveOver(tp == DIFF_INSERT ? edit : orig, part[1]); |
| 390 |
} |
| 391 |
} |
| 392 |
if (startEdit <= edit.line || startOrig <= orig.line) |
| 393 |
f(startOrig, orig.line + 1, startEdit, edit.line + 1); |
| 394 |
} |
| 395 |
|
| 396 |
function endOfLineClean(diff, i) { |
| 397 |
if (i == diff.length - 1) return true; |
| 398 |
var next = diff[i + 1][1]; |
| 399 |
if (next.length == 1 || next.charCodeAt(0) != 10) return false; |
| 400 |
if (i == diff.length - 2) return true; |
| 401 |
next = diff[i + 2][1]; |
| 402 |
return next.length > 1 && next.charCodeAt(0) == 10; |
| 403 |
} |
| 404 |
|
| 405 |
function startOfLineClean(diff, i) { |
| 406 |
if (i == 0) return true; |
| 407 |
var last = diff[i - 1][1]; |
| 408 |
if (last.charCodeAt(last.length - 1) != 10) return false; |
| 409 |
if (i == 1) return true; |
| 410 |
last = diff[i - 2][1]; |
| 411 |
return last.charCodeAt(last.length - 1) == 10; |
| 412 |
} |
| 413 |
|
| 414 |
function chunkBoundariesAround(diff, n, nInEdit) { |
| 415 |
var beforeE, afterE, beforeO, afterO; |
| 416 |
iterateChunks(diff, function(fromOrig, toOrig, fromEdit, toEdit) { |
| 417 |
var fromLocal = nInEdit ? fromEdit : fromOrig; |
| 418 |
var toLocal = nInEdit ? toEdit : toOrig; |
| 419 |
if (afterE == null) { |
| 420 |
if (fromLocal > n) { afterE = fromEdit; afterO = fromOrig; } |
| 421 |
else if (toLocal > n) { afterE = toEdit; afterO = toOrig; } |
| 422 |
} |
| 423 |
if (toLocal <= n) { beforeE = toEdit; beforeO = toOrig; } |
| 424 |
else if (fromLocal <= n) { beforeE = fromEdit; beforeO = fromOrig; } |
| 425 |
}); |
| 426 |
return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}}; |
| 427 |
} |
| 428 |
|
| 429 |
// General utilities |
| 430 |
|
| 431 |
function elt(tag, content, className, style) { |
| 432 |
var e = document.createElement(tag); |
| 433 |
if (className) e.className = className; |
| 434 |
if (style) e.style.cssText = style; |
| 435 |
if (typeof content == "string") e.appendChild(document.createTextNode(content)); |
| 436 |
else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); |
| 437 |
return e; |
| 438 |
} |
| 439 |
|
| 440 |
function clear(node) { |
| 441 |
for (var count = node.childNodes.length; count > 0; --count) |
| 442 |
node.removeChild(node.firstChild); |
| 443 |
} |
| 444 |
|
| 445 |
function attrs(elt) { |
| 446 |
for (var i = 1; i < arguments.length; i += 2) |
| 447 |
elt.setAttribute(arguments[i], arguments[i+1]); |
| 448 |
} |
| 449 |
|
| 450 |
function copyObj(obj, target) { |
| 451 |
if (!target) target = {}; |
| 452 |
for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop]; |
| 453 |
return target; |
| 454 |
} |
| 455 |
|
| 456 |
function moveOver(pos, str, copy, other) { |
| 457 |
var out = copy ? Pos(pos.line, pos.ch) : pos, at = 0; |
| 458 |
for (;;) { |
| 459 |
var nl = str.indexOf("\n", at); |
| 460 |
if (nl == -1) break; |
| 461 |
++out.line; |
| 462 |
if (other) ++other.line; |
| 463 |
at = nl + 1; |
| 464 |
} |
| 465 |
out.ch = (at ? 0 : out.ch) + (str.length - at); |
| 466 |
if (other) other.ch = (at ? 0 : other.ch) + (str.length - at); |
| 467 |
return out; |
| 468 |
} |
| 469 |
|
| 470 |
function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; } |
| 471 |
function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; } |
| 472 |
function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } |
| 473 |
})(); |
| 474 |
|