ソースを参照

Stop local and remote ops being batched together in the undo manager if they happen in the same flush

James Allen 9 年 前
親
コミット
a6679a1aeb

+ 0 - 4
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

+ 44 - 23
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

+ 33 - 3
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()