diff --git a/sharejs-codemirror/cm.js b/sharejs-codemirror/cm.js index 4b04e36..6f50593 100644 --- a/sharejs-codemirror/cm.js +++ b/sharejs-codemirror/cm.js @@ -1,66 +1,113 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 2.1.0 (function() { + /* + This code connects a CodeMirror editor to a sharejs document. + It was originally based on + https://github.com/HansPinckaers/shareJS/blob/master/src/client/cm.coffee + which was in turn inspiredfrom the ace editor hook. + Then it was tweaked slightly by mizzao for the meteor-sharejs project, + and finally fixed to handle multiple deltas by Erik Demaine. + */ var applyToShareJS; - applyToShareJS = function(editorDoc, delta, doc) { - var delLen, i, rm, startPos, _i, _len, _ref; - - startPos = 0; - i = 0; - while (i < delta.from.line) { - startPos += editorDoc.lineInfo(i).text.length + 1; - i++; - } - startPos += delta.from.ch; - if (delta.to.line === delta.from.line && delta.to.ch === delta.from.ch) { - doc.insert(startPos, delta.text.join('\n')); - } else { - delLen = 0; - _ref = delta.removed; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - rm = _ref[_i]; - delLen += rm.length; + // Convert CodeMirror deltas into ops understood by share.js + applyToShareJS = function(editorDoc, deltas, doc) { + var delLen, delta, i, index, j, k, l, len, len1, len2, m, pos, ref, ref1, ref2, results, rm, text; + // Handle single delta with simpler code + if (deltas.length === 1) { + delta = deltas[0]; + pos = editorDoc.indexFromPos(delta.from); + if (delta.removed.length) { // Deleted/replaced line(s) of text + delLen = delta.removed.length - 1; // count newlines + ref = delta.removed; + for (j = 0, len = ref.length; j < len; j++) { + rm = ref[j]; + delLen += rm.length; + } + doc.del(pos, delLen); } - delLen += delta.removed.length - 1; - doc.del(startPos, delLen); - if (delta.text) { - doc.insert(startPos, delta.text.join('\n')); + if (delta.text.length) { // Insertion/replacement line(s) of text + doc.insert(pos, delta.text.join('\n')); } + return; } - if (delta.next) { - return applyToShareJS(editorDoc, delta.next, doc); + results = []; + // Handle multiple deltas. This is tricky because each delta adjusts the + // indexing within the document, but all deltas are indexed in terms of + // the current document, yet we only have the "after" editor document. + // We use the current sharejs document as a proxy for the current document. + for (k = 0, len1 = deltas.length; k < len1; k++) { + delta = deltas[k]; + //console.log delta + // Compute index for edit's "from" line/ch using current document text. + text = doc.getText(); + index = 0; + for (i = l = 0, ref1 = delta.from.line; 0 <= ref1 ? l < ref1 : l > ref1; i = 0 <= ref1 ? ++l : --l) { + index = 1 + text.indexOf('\n', index); + } + index += delta.from.ch; + if (delta.removed.length) { // Deleted/replaced + delLen = delta.removed.length - 1; // count newlines + ref2 = delta.removed; + for (m = 0, len2 = ref2.length; m < len2; m++) { + rm = ref2[m]; + delLen += rm.length; + } + if (delLen) { + //console.log 'deleting', index, delLen + doc.del(index, delLen); + } + } + if (delta.text.length) { // Insertion/replacement + //console.log 'inserting', index, delta.text.join '\\n' + results.push(doc.insert(index, delta.text.join('\n'))); + } else { + results.push(void 0); + } } + return results; }; + // Attach a CodeMirror editor to the document. The editor's contents are replaced + // with the document's contents unless keepEditorContents is true. (In which case + // the document's contents are nuked and replaced with the editor's). window.sharejs.extendDoc('attach_cm', function(editor, keepEditorContents) { - var check, editorListener, sharedoc, suppress; - + var check, editorListener, sharedoc, suppress, undoDepth; if (!this.provides.text) { - throw new Error('Only text documents can be attached to CodeMirror 2 or 3'); + throw new Error('Only text documents can be attached to CodeMirror'); } + // When we apply ops from sharejs, CodeMirror emits edit events. + // We need to ignore those to prevent an infinite typing loop. + suppress = false; sharedoc = this; check = function() { return window.setTimeout(function() { var editorText, otText; - - editorText = editor.getValue('\n'); + editorText = editor.getValue(); otText = sharedoc.getText(); if (editorText !== otText) { console.error("Text does not match!"); - console.error("editor: " + editorText); - console.error("ot: " + otText); - return editor.setValue(sharedoc.getText()); + console.error(`editor: ${editorText}`); + console.error(`ot: ${otText}`); + // Replace the editor text with the doc snapshot. + suppress = true; + editor.setValue(sharedoc.getText()); + return suppress = false; } }, 0); }; if (keepEditorContents) { - this.del(0, sharedoc.getText('\n').length); + this.del(0, sharedoc.getText().length); this.insert(0, editor.getValue()); } else { + // Prevent immediate undo from going before initially loaded text. + undoDepth = editor.getOption('undoDepth'); + editor.setOption('undoDepth', 0); editor.setValue(sharedoc.getText()); + editor.setOption('undoDepth', undoDepth); } check(); - suppress = false; + // Listen for edits in CodeMirror. editorListener = function(ed, change) { if (suppress) { return; @@ -68,8 +115,9 @@ applyToShareJS(editor, change, sharedoc); return check(); }; - editor.on('change', editorListener); + editor.on('changes', editorListener); this.on('insert', function(pos, text) { + // All the primitives we need are already in CM's API. suppress = true; editor.replaceRange(text, editor.posFromIndex(pos)); suppress = false; @@ -77,7 +125,6 @@ }); this.on('delete', function(pos, text) { var from, to; - suppress = true; from = editor.posFromIndex(pos); to = editor.posFromIndex(pos + text.length); @@ -86,7 +133,8 @@ return check(); }); this.detach_cm = function() { - editor.off('change', editorListener); + // TODO: can we remove the insert and delete event callbacks? + editor.off('changes', editorListener); return delete this.detach_cm; }; });