undo.lua 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. -- undo/redo by managing the sequence of events in the current session
  2. -- based on https://github.com/akkartik/mu1/blob/master/edit/012-editor-undo.mu
  3. -- makes a copy of lines on every single keystroke; will be inefficient with really long lines.
  4. -- TODO: highlight stuff inserted by any undo/redo operation
  5. -- TODO: coalesce multiple similar operations
  6. function record_undo_event(State, data)
  7. State.history[State.next_history] = data
  8. State.next_history = State.next_history+1
  9. for i=State.next_history,#State.history do
  10. State.history[i] = nil
  11. end
  12. end
  13. function undo_event(State)
  14. if State.next_history > 1 then
  15. --? print('moving to history', State.next_history-1)
  16. State.next_history = State.next_history-1
  17. local result = State.history[State.next_history]
  18. return result
  19. end
  20. end
  21. function redo_event(State)
  22. if State.next_history <= #State.history then
  23. --? print('restoring history', State.next_history+1)
  24. local result = State.history[State.next_history]
  25. State.next_history = State.next_history+1
  26. return result
  27. end
  28. end
  29. -- Copy all relevant global state.
  30. -- Make copies of objects; the rest of the app may mutate them in place, but undo requires immutable histories.
  31. function snapshot(State, s,e)
  32. -- Snapshot everything by default, but subset if requested.
  33. assert(s, 'failed to snapshot operation for undo history')
  34. if e == nil then
  35. e = s
  36. end
  37. assert(#State.lines > 0, 'failed to snapshot operation for undo history')
  38. if s < 1 then s = 1 end
  39. if s > #State.lines then s = #State.lines end
  40. if e < 1 then e = 1 end
  41. if e > #State.lines then e = #State.lines end
  42. -- compare with App.initialize_globals
  43. local event = {
  44. screen_top=deepcopy(State.screen_top1),
  45. selection=deepcopy(State.selection1),
  46. cursor=deepcopy(State.cursor1),
  47. lines={},
  48. start_line=s,
  49. end_line=e,
  50. -- no filename; undo history is cleared when filename changes
  51. }
  52. for i=s,e do
  53. table.insert(event.lines, deepcopy(State.lines[i]))
  54. end
  55. return event
  56. end
  57. function patch(lines, from, to)
  58. --? if #from.lines == 1 and #to.lines == 1 then
  59. --? assert(from.start_line == from.end_line)
  60. --? assert(to.start_line == to.end_line)
  61. --? assert(from.start_line == to.start_line)
  62. --? lines[from.start_line] = to.lines[1]
  63. --? return
  64. --? end
  65. assert(from.start_line == to.start_line, 'failed to patch undo operation')
  66. for i=from.end_line,from.start_line,-1 do
  67. table.remove(lines, i)
  68. end
  69. assert(#to.lines == to.end_line-to.start_line+1, 'failed to patch undo operation')
  70. for i=1,#to.lines do
  71. table.insert(lines, to.start_line+i-1, to.lines[i])
  72. end
  73. end
  74. -- https://stackoverflow.com/questions/640642/how-do-you-copy-a-lua-table-by-value/26367080#26367080
  75. function deepcopy(obj, seen)
  76. if type(obj) ~= 'table' then return obj end
  77. if seen and seen[obj] then return seen[obj] end
  78. local s = seen or {}
  79. local result = setmetatable({}, getmetatable(obj))
  80. s[obj] = result
  81. for k,v in pairs(obj) do
  82. result[deepcopy(k, s)] = deepcopy(v, s)
  83. end
  84. return result
  85. end
  86. function minmax(a, b)
  87. return math.min(a,b), math.max(a,b)
  88. end