UndoManager.coffee 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. define [
  2. "ace/range"
  3. "ace/edit_session"
  4. "ace/document"
  5. ], () ->
  6. Range = require("ace/range").Range
  7. EditSession = require("ace/edit_session").EditSession
  8. Doc = require("ace/document").Document
  9. class UndoManager
  10. constructor: (@manager) ->
  11. @reset()
  12. @nextUpdateIsRemote = false
  13. reset: () ->
  14. @undoStack = []
  15. @redoStack = []
  16. execute: (options) ->
  17. aceDeltaSets = options.args[0]
  18. @session = options.args[1]
  19. return if !aceDeltaSets?
  20. lines = @session.getDocument().getAllLines()
  21. linesBeforeChange = @_revertAceDeltaSetsOnDocLines(aceDeltaSets, lines)
  22. simpleDeltaSets = @_aceDeltaSetsToSimpleDeltaSets(aceDeltaSets, linesBeforeChange)
  23. @undoStack.push(
  24. deltaSets: simpleDeltaSets
  25. remote: @nextUpdateIsRemote
  26. )
  27. @redoStack = []
  28. @nextUpdateIsRemote = false
  29. undo: (dontSelect) ->
  30. localUpdatesMade = @_shiftLocalChangeToTopOfUndoStack()
  31. return if !localUpdatesMade
  32. update = @undoStack.pop()
  33. return if !update?
  34. if update.remote
  35. @manager.showUndoConflictWarning()
  36. lines = @session.getDocument().getAllLines()
  37. linesBeforeDelta = @_revertSimpleDeltaSetsOnDocLines(update.deltaSets, lines)
  38. deltaSets = @_simpleDeltaSetsToAceDeltaSets(update.deltaSets, linesBeforeDelta)
  39. selectionRange = @session.undoChanges(deltaSets, dontSelect)
  40. @redoStack.push(update)
  41. return selectionRange
  42. redo: (dontSelect) ->
  43. update = @redoStack.pop()
  44. return if !update?
  45. lines = @session.getDocument().getAllLines()
  46. deltaSets = @_simpleDeltaSetsToAceDeltaSets(update.deltaSets, lines)
  47. selectionRange = @session.redoChanges(deltaSets, dontSelect)
  48. @undoStack.push(update)
  49. return selectionRange
  50. _shiftLocalChangeToTopOfUndoStack: () ->
  51. head = []
  52. localChangeExists = false
  53. while @undoStack.length > 0
  54. update = @undoStack.pop()
  55. head.unshift update
  56. if !update.remote
  57. localChangeExists = true
  58. break
  59. if !localChangeExists
  60. @undoStack = @undoStack.concat head
  61. return false
  62. else
  63. # Undo stack looks like undoStack ++ reorderedhead ++ head
  64. # Reordered head starts of empty and consumes entries from head
  65. # while keeping the localChange at the top for as long as it can
  66. localChange = head.shift()
  67. reorderedHead = [localChange]
  68. while head.length > 0
  69. remoteChange = head.shift()
  70. localChange = reorderedHead.pop()
  71. result = @_swapSimpleDeltaSetsOrder(localChange.deltaSets, remoteChange.deltaSets)
  72. if result?
  73. remoteChange.deltaSets = result[0]
  74. localChange.deltaSets = result[1]
  75. reorderedHead.push remoteChange
  76. reorderedHead.push localChange
  77. else
  78. reorderedHead.push localChange
  79. reorderedHead.push remoteChange
  80. break
  81. @undoStack = @undoStack.concat(reorderedHead).concat(head)
  82. return true
  83. _swapSimpleDeltaSetsOrder: (firstDeltaSets, secondDeltaSets) ->
  84. newFirstDeltaSets = @_copyDeltaSets(firstDeltaSets)
  85. newSecondDeltaSets = @_copyDeltaSets(secondDeltaSets)
  86. for firstDeltaSet in newFirstDeltaSets.slice(0).reverse()
  87. for firstDelta in firstDeltaSet.deltas.slice(0).reverse()
  88. for secondDeltaSet in newSecondDeltaSets
  89. for secondDelta in secondDeltaSet.deltas
  90. success = @_swapSimpleDeltaOrderInPlace(firstDelta, secondDelta)
  91. return null if !success
  92. return [newSecondDeltaSets, newFirstDeltaSets]
  93. _copyDeltaSets: (deltaSets) ->
  94. newDeltaSets = []
  95. for deltaSet in deltaSets
  96. newDeltaSet =
  97. deltas: []
  98. group: deltaSet.group
  99. newDeltaSets.push newDeltaSet
  100. for delta in deltaSet.deltas
  101. newDelta =
  102. position: delta.position
  103. newDelta.insert = delta.insert if delta.insert?
  104. newDelta.remove = delta.remove if delta.remove?
  105. newDeltaSet.deltas.push newDelta
  106. return newDeltaSets
  107. _swapSimpleDeltaOrderInPlace: (firstDelta, secondDelta) ->
  108. result = @_swapSimpleDeltaOrder(firstDelta, secondDelta)
  109. return false if !result?
  110. firstDelta.position = result[1].position
  111. secondDelta.position = result[0].position
  112. return true
  113. _swapSimpleDeltaOrder: (firstDelta, secondDelta) ->
  114. if firstDelta.insert? and secondDelta.insert?
  115. if secondDelta.position >= firstDelta.position + firstDelta.insert.length
  116. secondDelta.position -= firstDelta.insert.length
  117. return [secondDelta, firstDelta]
  118. else if secondDelta.position > firstDelta.position
  119. return null
  120. else
  121. firstDelta.position += secondDelta.insert.length
  122. return [secondDelta, firstDelta]
  123. else if firstDelta.remove? and secondDelta.remove?
  124. if secondDelta.position >= firstDelta.position
  125. secondDelta.position += firstDelta.remove.length
  126. return [secondDelta, firstDelta]
  127. else if secondDelta.position + secondDelta.remove.length > firstDelta.position
  128. return null
  129. else
  130. firstDelta.position -= secondDelta.remove.length
  131. return [secondDelta, firstDelta]
  132. else if firstDelta.insert? and secondDelta.remove?
  133. if secondDelta.position >= firstDelta.position + firstDelta.insert.length
  134. secondDelta.position -= firstDelta.insert.length
  135. return [secondDelta, firstDelta]
  136. else if secondDelta.position + secondDelta.remove.length > firstDelta.position
  137. return null
  138. else
  139. firstDelta.position -= secondDelta.remove.length
  140. return [secondDelta, firstDelta]
  141. else if firstDelta.remove? and secondDelta.insert?
  142. if secondDelta.position >= firstDelta.position
  143. secondDelta.position += firstDelta.remove.length
  144. return [secondDelta, firstDelta]
  145. else
  146. firstDelta.position += secondDelta.insert.length
  147. return [secondDelta, firstDelta]
  148. else
  149. throw "Unknown delta types"
  150. _applyAceDeltasToDocLines: (deltas, docLines) ->
  151. doc = new Doc(docLines.join("\n"))
  152. doc.applyDeltas(deltas)
  153. return doc.getAllLines()
  154. _revertAceDeltaSetsOnDocLines: (deltaSets, docLines) ->
  155. session = new EditSession(docLines.join("\n"))
  156. session.undoChanges(deltaSets)
  157. return session.getDocument().getAllLines()
  158. _revertSimpleDeltaSetsOnDocLines: (deltaSets, docLines) ->
  159. doc = docLines.join("\n")
  160. for deltaSet in deltaSets.slice(0).reverse()
  161. for delta in deltaSet.deltas.slice(0).reverse()
  162. if delta.remove?
  163. doc = doc.slice(0, delta.position) + delta.remove + doc.slice(delta.position)
  164. else if delta.insert?
  165. doc = doc.slice(0, delta.position) + doc.slice(delta.position + delta.insert.length)
  166. else
  167. throw "Unknown delta type"
  168. return doc.split("\n")
  169. _aceDeltaSetsToSimpleDeltaSets: (aceDeltaSets, docLines) ->
  170. for deltaSet in aceDeltaSets
  171. simpleDeltas = []
  172. for delta in deltaSet.deltas
  173. simpleDeltas.push @_aceDeltaToSimpleDelta(delta, docLines)
  174. docLines = @_applyAceDeltasToDocLines([delta], docLines)
  175. {
  176. deltas: simpleDeltas
  177. group: deltaSet.group
  178. }
  179. _simpleDeltaSetsToAceDeltaSets: (simpleDeltaSets, docLines) ->
  180. for deltaSet in simpleDeltaSets
  181. aceDeltas = []
  182. for delta in deltaSet.deltas
  183. newAceDeltas = @_simpleDeltaToAceDeltas(delta, docLines)
  184. docLines = @_applyAceDeltasToDocLines(newAceDeltas, docLines)
  185. aceDeltas = aceDeltas.concat newAceDeltas
  186. {
  187. deltas: aceDeltas
  188. group: deltaSet.group
  189. }
  190. _aceDeltaToSimpleDelta: (aceDelta, docLines) ->
  191. start = aceDelta.range.start
  192. linesBefore = docLines.slice(0, start.row)
  193. position =
  194. linesBefore.join("").length + # full lines
  195. linesBefore.length + # new line characters
  196. start.column # partial line
  197. switch aceDelta.action
  198. when "insertText"
  199. return {
  200. position: position
  201. insert: aceDelta.text
  202. }
  203. when "insertLines"
  204. return {
  205. position: position
  206. insert: aceDelta.lines.join("\n") + "\n"
  207. }
  208. when "removeText"
  209. return {
  210. position: position
  211. remove: aceDelta.text
  212. }
  213. when "removeLines"
  214. return {
  215. position: position
  216. remove: aceDelta.lines.join("\n") + "\n"
  217. }
  218. else
  219. throw "Unknown Ace action: #{aceDelta.action}"
  220. _simplePositionToAcePosition: (position, docLines) ->
  221. column = 0
  222. row = 0
  223. for line in docLines
  224. if position > line.length
  225. row++
  226. position -= (line + "\n").length
  227. else
  228. column = position
  229. break
  230. return {row: row, column: column}
  231. _textToAceActions: (simpleText, row, column, type) ->
  232. aceDeltas = []
  233. lines = simpleText.split("\n")
  234. range = (options) -> new Range(options.start.row, options.start.column, options.end.row, options.end.column)
  235. do stripFirstLine = () ->
  236. firstLine = lines.shift()
  237. if firstLine.length > 0
  238. aceDeltas.push {
  239. text: firstLine
  240. range: range(
  241. start: column: column, row: row
  242. end: column: column + firstLine.length, row: row
  243. )
  244. action: "#{type}Text"
  245. }
  246. column += firstLine.length
  247. do stripFirstNewLine = () ->
  248. if lines.length > 0
  249. aceDeltas.push {
  250. text: "\n"
  251. range: range(
  252. start: column: column, row: row
  253. end: column: 0, row: row + 1
  254. )
  255. action: "#{type}Text"
  256. }
  257. row += 1
  258. do stripMiddleFullLines = () ->
  259. middleLines = lines.slice(0, -1)
  260. if middleLines.length > 0
  261. aceDeltas.push {
  262. lines: middleLines
  263. range: range(
  264. start: column: 0, row: row
  265. end: column: 0, row: row + middleLines.length
  266. )
  267. action: "#{type}Lines"
  268. }
  269. row += middleLines.length
  270. do stripLastLine = () ->
  271. if lines.length > 0
  272. lastLine = lines.pop()
  273. aceDeltas.push {
  274. text: lastLine
  275. range: range(
  276. start: column: 0, row: row
  277. end: column: lastLine.length , row: row
  278. )
  279. action: "#{type}Text"
  280. }
  281. return aceDeltas
  282. _simpleDeltaToAceDeltas: (simpleDelta, docLines) ->
  283. {row, column} = @_simplePositionToAcePosition(simpleDelta.position, docLines)
  284. if simpleDelta.insert?
  285. return @_textToAceActions(simpleDelta.insert, row, column, "insert")
  286. if simpleDelta.remove?
  287. return @_textToAceActions(simpleDelta.remove, row, column, "remove").reverse()
  288. else
  289. throw "Unknown simple delta: #{simpleDelta}"
  290. _concatSimpleDeltas: (deltas) ->
  291. return [] if deltas.length == 0
  292. concattedDeltas = []
  293. previousDelta = deltas.shift()
  294. for delta in deltas
  295. if delta.insert? and previousDelta.insert?
  296. if previousDelta.position + previousDelta.insert.length == delta.position
  297. previousDelta =
  298. insert: previousDelta.insert + delta.insert
  299. position: previousDelta.position
  300. else
  301. concattedDeltas.push previousDelta
  302. previousDelta = delta
  303. else if delta.remove? and previousDelta.remove?
  304. if previousDelta.position == delta.position
  305. previousDelta =
  306. remove: previousDelta.remove + delta.remove
  307. position: delta.position
  308. else
  309. concattedDeltas.push previousDelta
  310. previousDelta = delta
  311. else
  312. concattedDeltas.push previousDelta
  313. previousDelta = delta
  314. concattedDeltas.push previousDelta
  315. return concattedDeltas
  316. hasUndo: () -> @undoStack.length > 0
  317. hasRedo: () -> @redoStack.length > 0