diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index faa1159e00..2a633e24f8 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -322,10 +322,6 @@ define [ doc = session.getDocument() doc.on "change", onChange - sharejs_doc.on "remoteop.recordRemote", (op, oldSnapshot, msg) -> - undoManager.nextUpdateIsRemote = true - trackChangesManager.nextUpdateMetaData = msg?.meta - editor.initing = true sharejs_doc.attachToAce(editor) editor.initing = false diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/undo/UndoManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/undo/UndoManager.coffee index a4348bed98..1f53fffe52 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/undo/UndoManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/undo/UndoManager.coffee @@ -11,10 +11,10 @@ define [ show_remote_warning: false @reset() - @nextUpdateIsRemote = false @editor.on "changeSession", (e) => @reset() + @session = e.session e.session.setUndoManager(@) showUndoConflictWarning: () -> @@ -38,20 +38,44 @@ define [ @firstUpdate = false return aceDeltaSets = options.args[0] - @session = options.args[1] return if !aceDeltaSets? + @session = options.args[1] - lines = @session.getDocument().getAllLines() - linesBeforeChange = @_revertAceDeltaSetsOnDocLines(aceDeltaSets, lines) - simpleDeltaSets = @_aceDeltaSetsToSimpleDeltaSets(aceDeltaSets, linesBeforeChange) - @undoStack.push( - deltaSets: simpleDeltaSets - remote: @nextUpdateIsRemote - ) + # We need to split the delta sets into local or remote groups before pushing onto + # the undo stack, since these are treated differently. + splitDeltaSets = [] + currentDeltaSet = null # Make global to this function + do newDeltaSet = () -> + currentDeltaSet = {group: "doc", deltas: []} + splitDeltaSets.push currentDeltaSet + currentRemoteState = null + + for deltaSet in aceDeltaSets or [] + if deltaSet.group == "doc" # ignore code folding etc. + for delta in deltaSet.deltas + if currentDeltaSet.remote? and currentDeltaSet.remote != !!delta.remote + newDeltaSet() + currentDeltaSet.deltas.push delta + currentDeltaSet.remote = !!delta.remote + + # The lines are currently as they are after applying all these deltas, but to turn into simple deltas, + # we need the lines before each delta group. + docLines = @session.getDocument().getAllLines() + docLines = @_revertAceDeltaSetsOnDocLines(aceDeltaSets, docLines) + for deltaSet in splitDeltaSets + {simpleDeltaSet, docLines} = @_aceDeltaSetToSimpleDeltaSet(deltaSet, docLines) + frame = { + deltaSets: [simpleDeltaSet] + remote: deltaSet.remote + } + @undoStack.push frame @redoStack = [] - @nextUpdateIsRemote = false undo: (dontSelect) -> + # We rely on the doclines being in sync with the undo stack, so make sure + # any pending undo deltas are processed. + @session.$syncInformUndoManager() + localUpdatesMade = @_shiftLocalChangeToTopOfUndoStack() return if !localUpdatesMade @@ -206,19 +230,16 @@ define [ throw "Unknown delta type" return doc.split("\n") - _aceDeltaSetsToSimpleDeltaSets: (aceDeltaSets, docLines) -> - simpleDeltaSets = [] - for deltaSet in aceDeltaSets - if deltaSet.group == "doc" # ignore fold changes - simpleDeltas = [] - for delta in deltaSet.deltas - simpleDeltas.push @_aceDeltaToSimpleDelta(delta, docLines) - docLines = @_applyAceDeltasToDocLines([delta], docLines) - simpleDeltaSets.push { - deltas: simpleDeltas - group: deltaSet.group - } - return simpleDeltaSets + _aceDeltaSetToSimpleDeltaSet: (deltaSet, docLines) -> + simpleDeltas = [] + for delta in deltaSet.deltas + simpleDeltas.push @_aceDeltaToSimpleDelta(delta, docLines) + docLines = @_applyAceDeltasToDocLines([delta], docLines) + simpleDeltaSet = { + deltas: simpleDeltas + group: deltaSet.group + } + return {simpleDeltaSet, docLines} _simpleDeltaSetsToAceDeltaSets: (simpleDeltaSets, docLines) -> for deltaSet in simpleDeltaSets diff --git a/services/web/public/coffee/ide/editor/sharejs/vendor/client/ace.coffee b/services/web/public/coffee/ide/editor/sharejs/vendor/client/ace.coffee index b382a439ff..ac0db65b1a 100644 --- a/services/web/public/coffee/ide/editor/sharejs/vendor/client/ace.coffee +++ b/services/web/public/coffee/ide/editor/sharejs/vendor/client/ace.coffee @@ -110,16 +110,46 @@ window.sharejs.extendDoc 'attach_ace', (editor, keepEditorContents, maxDocLength row:row, column:offset + # We want to insert a remote:true into the delta if the op comes from the + # underlying sharejs doc (which means it is from a remote op), so we have to do + # the work of editorDoc.insert and editorDoc.remove manually. These methods are + # copied from ace.js doc#insert and #remove, and then inject the remote:true + # flag into the delta. doc.on 'insert', (pos, text) -> + if (editorDoc.getLength() <= 1) + editorDoc.$detectNewLine(text) + + lines = editorDoc.$split(text) + position = offsetToPos(pos) + start = editorDoc.clippedPos(position.row, position.column) + end = { + row: start.row + lines.length - 1, + column: (if lines.length == 1 then start.column else 0) + lines[lines.length - 1].length + } + suppress = true - editorDoc.insert offsetToPos(pos), text + editorDoc.applyDelta({ + start: start, + end: end, + action: "insert", + lines: lines, + remote: true + }); suppress = false check() doc.on 'delete', (pos, text) -> - suppress = true range = Range.fromPoints offsetToPos(pos), offsetToPos(pos + text.length) - editorDoc.remove range + start = editorDoc.clippedPos(range.start.row, range.start.column) + end = editorDoc.clippedPos(range.end.row, range.end.column) + suppress = true + editorDoc.applyDelta({ + start: start, + end: end, + action: "remove", + lines: editorDoc.getLinesForRange({start: start, end: end}) + remote: true + }); suppress = false check()