007b965b11
Each one should provide a message that will show up within LÖVE. Stop relying on nearby prints to the terminal. I also found some unnecessary ones. There is some potential here for performance regressions: the format() calls will trigger whether or not the assertion fails, and cause allocations. So far Lua's GC seems good enough to manage the load even with Moby Dick, even in some situations that caused issues in the past like undo.
119 lines
4.0 KiB
Lua
119 lines
4.0 KiB
Lua
-- 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
|
|
|
|
-- Incredibly inefficient; we make a copy of lines on every single keystroke.
|
|
-- The hope here is that we're either editing small files or just reading large files.
|
|
-- TODO: highlight stuff inserted by any undo/redo operation
|
|
-- TODO: coalesce multiple similar operations
|
|
|
|
function record_undo_event(State, data)
|
|
State.history[State.next_history] = data
|
|
State.next_history = State.next_history+1
|
|
for i=State.next_history,#State.history do
|
|
State.history[i] = nil
|
|
end
|
|
end
|
|
|
|
function undo_event(State)
|
|
if State.next_history > 1 then
|
|
--? print('moving to history', State.next_history-1)
|
|
State.next_history = State.next_history-1
|
|
local result = State.history[State.next_history]
|
|
return result
|
|
end
|
|
end
|
|
|
|
function redo_event(State)
|
|
if State.next_history <= #State.history then
|
|
--? print('restoring history', State.next_history+1)
|
|
local result = State.history[State.next_history]
|
|
State.next_history = State.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(State, 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(#State.lines > 0, 'failed to snapshot operation for undo history')
|
|
if s < 1 then s = 1 end
|
|
if s > #State.lines then s = #State.lines end
|
|
if e < 1 then e = 1 end
|
|
if e > #State.lines then e = #State.lines end
|
|
-- compare with App.initialize_globals
|
|
local event = {
|
|
screen_top=deepcopy(State.screen_top1),
|
|
selection=deepcopy(State.selection1),
|
|
cursor=deepcopy(State.cursor1),
|
|
current_drawing_mode=Drawing_mode,
|
|
previous_drawing_mode=State.previous_drawing_mode,
|
|
lines={},
|
|
start_line=s,
|
|
end_line=e,
|
|
-- no filename; undo history is cleared when filename changes
|
|
}
|
|
-- deep copy lines without cached stuff like text fragments
|
|
for i=s,e do
|
|
local line = State.lines[i]
|
|
if line.mode == 'text' then
|
|
table.insert(event.lines, {mode='text', data=line.data}) -- I've forgotten: should we deepcopy(line.data)?
|
|
elseif line.mode == 'drawing' then
|
|
table.insert(event.lines, {mode='drawing', h=line.h, points=deepcopy(line.points), shapes=deepcopy(line.shapes), pending={}})
|
|
else
|
|
assert(false, ('unknown line mode %s'):format(line.mode))
|
|
end
|
|
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
|
|
|
|
function patch_placeholders(line_cache, from, to)
|
|
assert(from.start_line == to.start_line, 'failed to patch undo operation')
|
|
for i=from.end_line,from.start_line,-1 do
|
|
table.remove(line_cache, 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(line_cache, to.start_line+i-1, {})
|
|
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
|