Procházet zdrojové kódy

Add a note on the need of reverse sorting changes when rejecting.

Paulo Reis před 9 roky
rodič
revize
73a67c6617

+ 53 - 0
services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee

@@ -220,6 +220,59 @@ define [
 		rejectChangeIds: (change_ids) ->
 			changes = @rangesTracker.getChanges(change_ids)
 			return if changes.length == 0
+
+			# When doing bulk rejections, adjacent changes might interact with each other.
+			# Consider an insertion with an adjacent deletion (which is a common use-case, replacing words):
+			#
+			#     "foo bar baz" -> "foo quux baz"
+			#
+			# The change above will be modeled with two ops, with the insertion going first:
+			#
+			#     foo quux baz
+			#         |--| -> insertion of "quux", op 1, at position 4
+			#             | -> deletion of "bar", op 2, pushed forward by "quux" to position 8 
+			#
+			# When rejecting these changes at once, if the insertion is rejected first, we get unexpected 
+			# results. What happens is:
+			#
+			#     1) Rejecting the insertion deletes the added word "quux", i.e., it removes 4 chars 
+			#        starting from position 4;
+			#
+			#           "foo quux baz" -> "foo  baz"
+			#                |--| -> 4 characters to be removed
+			#
+			#     2) Rejecting the deletion adds the deleted word "bar" at position 8 (i.e. it will act as if
+			#        the word "quuux" was still present).
+			#
+			#            "foo  baz" -> "foo  bazbar"
+			#                     | -> deletion of "bar" is reverted by reinserting "bar" at position 8
+			#
+			# While the intended result would be "foo bar baz", what we get is:
+			#
+			#      "foo  bazbar" (note "bar" readded at position 8)
+			#
+			# The issue happens because of step 1. To revert the insertion of "quux", 4 characters are deleted
+			# from position 4. This includes the position where the deletion exists; when that position is 
+			# cleared, the RangesTracker considers that the deletion is gone and stops tracking/updating it.
+			# As we still hold a reference to it, the code tries to revert it by readding the deleted text, but
+			# does so at the outdated position (position 8, which was valid when "quux" was present).
+			#
+			# To avoid this kind of problem, we need to make sure that reverting operations doesn't affect 
+			# subsequent operations that come after. Reverse sorting the operations based on position will 
+			# achieve it; in the case above, it makes sure that the the deletion is reverted first:
+			#
+			#     1) Rejecting the deletion adds the deleted word "bar" at position 8 
+			#
+			#            "foo quux baz" -> "foo quuxbar baz"
+			#                                       | -> deletion of "bar" is reverted by 
+			#                                            reinserting "bar" at position 8
+			#
+			#     2) Rejecting the insertion deletes the added word "quux", i.e., it removes 4 chars 
+			#        starting from position 4 and achieves the expected result:
+			#
+			#           "foo quuxbar baz" -> "foo bar baz"
+			#                |--| -> 4 characters to be removed
+			
 			changes.sort((a, b) -> b.op.p - a.op.p)
 
 			session = @editor.getSession()