source_undo.lua 3.2 KB

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