-- undo/redo by managing the sequence of events in the current session -- based on https://github.com/akkartik/mu1/blob/master/edit/012-editor-undo.mu -- makes a copy of lines on every single keystroke; will be inefficient with really long lines. -- TODO: highlight stuff inserted by any undo/redo operation -- TODO: coalesce multiple similar operations function record_undo_event(editor, data) editor.history[editor.next_history] = data editor.next_history = editor.next_history+1 for i=editor.next_history,#editor.history do editor.history[i] = nil end end function undo_event(editor) if editor.next_history > 1 then --? print('moving to history', editor.next_history-1) editor.next_history = editor.next_history-1 local result = editor.history[editor.next_history] return result end end function redo_event(editor) if editor.next_history <= #editor.history then --? print('restoring history', editor.next_history+1) local result = editor.history[editor.next_history] editor.next_history = editor.next_history+1 return result end end -- Copy all relevant global state. -- Make copies of objects; the rest of the app may mutate them in place, but undo requires immutable histories. function snapshot(editor, s,e) -- Snapshot everything by default, but subset if requested. assert(s, 'failed to snapshot operation for undo history') if e == nil then e = s end assert(#editor.lines > 0, 'failed to snapshot operation for undo history') if s < 1 then s = 1 end if s > #editor.lines then s = #editor.lines end if e < 1 then e = 1 end if e > #editor.lines then e = #editor.lines end -- compare with edit.new local event = { screen_top=deepcopy(editor.screen_top), selection=deepcopy(editor.selection1), cursor=deepcopy(editor.cursor), current_drawing_mode=Drawing_mode, previous_drawing_mode=editor.previous_drawing_mode, lines={}, start_line=s, end_line=e, -- no filename; undo history is cleared when filename changes } for i=s,e do table.insert(event.lines, deepcopy(editor.lines[i])) end return event end function patch(lines, from, to) --? if #from.lines == 1 and #to.lines == 1 then --? assert(from.start_line == from.end_line) --? assert(to.start_line == to.end_line) --? assert(from.start_line == to.start_line) --? lines[from.start_line] = to.lines[1] --? return --? end assert(from.start_line == to.start_line, 'failed to patch undo operation') for i=from.end_line,from.start_line,-1 do table.remove(lines, i) end assert(#to.lines == to.end_line-to.start_line+1, 'failed to patch undo operation') for i=1,#to.lines do table.insert(lines, to.start_line+i-1, to.lines[i]) end end -- https://stackoverflow.com/questions/640642/how-do-you-copy-a-lua-table-by-value/26367080#26367080 function deepcopy(obj, seen) if type(obj) ~= 'table' then return obj end if seen and seen[obj] then return seen[obj] end local s = seen or {} local result = setmetatable({}, getmetatable(obj)) s[obj] = result for k,v in pairs(obj) do result[deepcopy(k, s)] = deepcopy(v, s) end return result end function minmax(a, b) return math.min(a,b), math.max(a,b) end