diff --git a/www/code/inner.js b/www/code/inner.js index adca461f4..e5fe025c7 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -345,7 +345,8 @@ define([ var addMark = function (from, to, uid) { var author = authormarks.authors[uid] || {}; - editor.markText(from, to, { + uid = Number(uid); + return editor.markText(from, to, { inclusiveLeft: uid === myAuthorId, inclusiveRight: uid === myAuthorId, css: "background-color: " + author.color + MARK_OPACITY, @@ -366,7 +367,17 @@ define([ if (a[2] > b[2]) { return 1; } return 0; }; - + var parseMark = function (array) { + if (!Array.isArray(array)) { return {}; } + var multiline = typeof(array[4]) !== "undefined"; + var singleChar = typeof(array[3]) === "undefined"; + return { + startLine: array[1], + startCh: array[2], + endLine: multiline ? array[3] : array[1], + endCh: singleChar ? (array[2]+1) : array[3] + }; + }; var $print = $('#cp-app-code-print'); var $content = $('#cp-app-code-preview-content'); @@ -390,53 +401,132 @@ define([ CodeMirror.configureTheme(common); } + var oldMarks = authormarks; + // Remove marks added by OT and fix the incorrect ones + // first: data about the change with the lowest offset + // last: data about the change with the latest offset + // in the comments, "I" am "first" + var fixMarks = function (first, last) { + var toKeepEnd = []; + var toKeepStart = []; + // Get their start position compared to the authdoc + var lastOldOffset = last.offset - first.size; // (their offset minus my size) + var lastOldPos = SFCodeMirror.posToCursor(lastOldOffset, last.doc); + // Keep their changes in the marks (after their offset) + last.marks.some(function (array, i) { + var p = parseMark(array); + // End of the mark before offset? ignore + if (p.endLine < lastOldPos.line) { return; } + // Take everything from the first mark ending after the pos + if (p.endLine > lastOldPos.line || p.endCh >= lastOldPos.ch) { + toKeepEnd = last.marks.slice(i); + return true; + } + }); + // Keep my marks (based on currentDoc) before their changes + first.marks.some(function (array, i) { + var p = parseMark(array); + // End of the mark before offset? ignore + if (p.endLine < last.startLine) { return; } + // Take everything from the first mark ending after the pos + if (p.endLine > last.startLine || p.endCh >= last.startCh) { + toKeepStart = first.marks.slice(0,i); + return true; + } + }); + + console.info('to keep'); + console.info(JSON.stringify(toKeepStart)); + console.info(JSON.stringify(toKeepEnd)); + + // Fix their offset + var addLine = first.endLine - first.startLine; + var addCh = first.endCh - first.startCh; + toKeepEnd.forEach(function (array) { + // Push to correct lines + array[1] += addLine; + if (typeof(array[4]) !== "undefined") { array[3] += addLine; } + // If they have markers on my end line, push their "ch" + if (array[1] === first.endLine) { + array[2] += addCh; + // If they have no end line, it means end line === start line, + // so we also push their end offset + if (!array[4] && array[3]) { array[3] += addCh; } + } + }); + Array.prototype.push.apply(toKeepStart, toKeepEnd); + authormarks.marks = toKeepStart; + }; var checkAuthors = function (userDoc) { var chainpad = framework._.cpNfInner.chainpad; var authDoc = JSON.parse(chainpad.getAuthDoc() || '{}'); if (!authDoc.content || !userDoc.content) { return; } if (!authormarks || !Array.isArray(authormarks.marks)) { return; } - var oldDoc = CodeMirror.canonicalize(editor.getValue()); - var theirOps = ChainPad.Diff.diff(oldDoc, userDoc.content); + var localDoc = CodeMirror.canonicalize(editor.getValue()); + + // Their changes are the diff between my local doc (my local changes only) + // and the userDoc (my local changes + their changes pushed to the authdoc) + var theirOps = ChainPad.Diff.diff(localDoc, userDoc.content); + // My changes are the diff between my userDoc (my local changes + their changes) + // and the authDoc (their changes only) var myOps = ChainPad.Diff.diff(authDoc.content, userDoc.content); + // If I have uncommited content when receiving a remote patch, and they have // pushed content to the same line as me, I need to update all the authormarks // after their changes to push them by the length of the text I added - var changed = false; console.log(JSON.stringify(authDoc.authormarks)); console.log(JSON.stringify(authormarks)); console.warn(myOps); console.warn(theirOps); + var marks = authormarks.marks; + myOps.forEach(function (op) { - var pos = SFCodeMirror.posToCursor(op.offset, authDoc.content); + var pos = SFCodeMirror.posToCursor(op.offset, userDoc.content); var size = (op.toInsert.length - op.toRemove); + var pos2 = SFCodeMirror.posToCursor(op.offset+size, userDoc.content); + + var me = { + offset: op.offset, + size: size, + startLine: pos.line, + startCh: pos.ch, + endLine: pos2.line, + endCh: pos2.ch, + marks: (oldMarks && oldMarks.marks) || [], + doc: localDoc + }; - // If the remote change includes an operation on the same line, - // fix the offsets and continue to my next operation - // NOTE: we need to fix all the marks that are **after** the change with - // the bigger offset theirOps.some(function (_op) { - var _pos = SFCodeMirror.posToCursor(_op.offset, oldDoc); - if (_pos.line !== pos.line) { return; } - - var ch = Math.max(_pos.ch, pos.ch); - // Get the marks from this line and check offsets after the change - authormarks.marks.forEach(function (array) { - if (array[1] !== pos.line) { return; } - // Move the end position if it's on the same line and after the change - if (!array[4] && array[3] >= ch) { - array[3] += size; - changed = true; - } - // Move the start position if it's after the change - if (array[2] >= ch) { - array[2] += size; - changed = true; - } - }); + // XXX we need the take the first operation after my changes and the one just before my change + // XXX if they have multiple operations and one of them (not the first) + // is multiline... + + var _size = (_op.toInsert.length - _op.toRemove); + var _pos = SFCodeMirror.posToCursor(_op.offset, userDoc.content); + var _pos2 = SFCodeMirror.posToCursor(_op.offset+_size, userDoc.content); + + var them = { + offset: _op.offset, + size: _size, + startLine: _pos.line, + startCh: _pos.ch, + endLine: _pos2.line, + endCh: _pos2.ch, + marks: (authDoc.authormarks && authDoc.authormarks.marks) || [], + doc: authDoc.content + }; + + if (_op.offset > op.offset) { + console.error('me first', me, them); + fixMarks(me, them); + } else { + console.error('them first', me, them); + fixMarks(them, me); + } + console.warn(JSON.stringify(authormarks.marks)); return true; }); }); - framework.localChange(); }; framework.onContentUpdate(function (newContent) { @@ -446,6 +536,7 @@ define([ } if (newContent.authormarks) { + oldMarks = authormarks; authormarks = newContent.authormarks; if (!authormarks.marks) { authormarks.marks = []; } if (!authormarks.authors) { authormarks.authors = {}; } @@ -460,6 +551,7 @@ define([ CodeMirror.contentUpdate(newContent); //, authormarks.marks, authormarksLocal); previewPane.draw(); + framework.localChange(); }); framework.setContentGetter(function () { @@ -468,34 +560,43 @@ define([ content.highlightMode = CodeMirror.highlightMode; previewPane.draw(); - var colorlist = content.colorlist || {}; - // get author marks var authors = authormarks.authors || {}; var _marks = []; - var previous; + var all = []; + + var i = 0; editor.getAllMarks().forEach(function (mark) { var pos = mark.find(); var attributes = mark.attributes || {}; if (!pos || attributes['data-type'] !== 'authormark') { return; } - var uid = attributes['data-uid'] || 0; + var uid = Number(attributes['data-uid']) || 0; var author = authors[uid] || {}; - // Check if we need to merge - if (previous && previous.data && previous.data[0] === uid) { - if (previous.pos.to.line === pos.from.line - && previous.pos.to.ch === pos.from.ch) { - // Merge the marks - previous.mark.clear(); + all.forEach(function (obj,i) { + if (obj.uid !== uid) { return; } + if (obj.removed) { return; } + // Merge left + if (obj.pos.to.line === pos.from.line && obj.pos.to.ch === pos.from.ch) { + obj.removed = true; + _marks[obj.index] = undefined; + obj.mark.clear(); mark.clear(); - addMark(previous.pos.from, pos.to, uid); - // Remove the data for the previous one - _marks.pop(); - // Update the position to create the new data - pos.from = previous.pos.from; + mark = addMark(obj.pos.from, pos.to, uid); + pos.from = obj.pos.from; + return; } - } + // Merge right + if (obj.pos.from.line === pos.to.line && obj.pos.from.ch === pos.to.ch) { + obj.removed = true; + _marks[obj.index] = undefined; + obj.mark.clear(); + mark.clear(); + mark = addMark(pos.from, obj.pos.to, uid); + pos.to = obj.pos.to; + } + }); var array = [uid, pos.from.line, pos.from.ch]; if (pos.from.line === pos.to.line && pos.to.ch > (pos.from.ch+1)) { @@ -506,14 +607,17 @@ define([ Array.prototype.push.apply(array, [pos.to.line, pos.to.ch]); } _marks.push(array); - previous = { + all.push({ + uid: uid, pos: pos, mark: mark, - data: array - }; + index: i + }); + i++; }); _marks.sort(sortMarks); - content.authormarks = {marks: _marks, authors: authormarks.authors}; + authormarks.marks = _marks.filter(Boolean); + content.authormarks = authormarks; //authormarksLocal = _marks.slice(); return content;