diff options
-rw-r--r-- | edit.lua | 20 | ||||
-rw-r--r-- | select.lua | 8 | ||||
-rw-r--r-- | source_edit.lua | 16 | ||||
-rw-r--r-- | source_text.lua | 42 | ||||
-rw-r--r-- | source_undo.lua | 21 | ||||
-rw-r--r-- | text.lua | 44 | ||||
-rw-r--r-- | undo.lua | 15 |
7 files changed, 64 insertions, 102 deletions
diff --git a/edit.lua b/edit.lua index eaa415e..729791e 100644 --- a/edit.lua +++ b/edit.lua @@ -198,7 +198,7 @@ function edit.mouse_release(State, x,y, mouse_button) --? print_and_log(('edit.mouse_release(%d,%d): cursor at %d,%d'):format(x,y, State.cursor1.line, State.cursor1.pos)) State.mouse_down = nil if y < State.top then - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} + State.cursor1 = deepcopy(State.screen_top1) edit.clean_up_mouse_press(State) return end @@ -237,7 +237,7 @@ end function edit.mouse_wheel_move(State, dx,dy) if dy > 0 then - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} + State.cursor1 = deepcopy(State.screen_top1) for i=1,math.floor(dy) do Text.up(State) end @@ -274,7 +274,7 @@ function edit.keychord_press(State, chord, key) State.cursor1 = State.search_backup.cursor State.screen_top1 = State.search_backup.screen_top State.search_backup = nil - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks elseif chord == 'return' then State.search_term = nil State.search_backup = nil @@ -282,6 +282,9 @@ function edit.keychord_press(State, chord, key) local len = utf8.len(State.search_term) local byte_offset = Text.offset(State.search_term, len) State.search_term = string.sub(State.search_term, 1, byte_offset-1) + State.cursor = deepcopy(State.search_backup.cursor) + State.screen_top = deepcopy(State.search_backup.screen_top) + Text.search_next(State) elseif chord == 'down' then State.cursor1.pos = State.cursor1.pos+1 Text.search_next(State) @@ -316,9 +319,7 @@ function edit.keychord_press(State, chord, key) State.cursor1 = deepcopy(src.cursor) State.selection1 = deepcopy(src.selection) patch(State.lines, event.after, event.before) - patch_placeholders(State.line_cache, event.after, event.before) - -- if we're scrolling, reclaim all fragments to avoid memory leaks - Text.redraw_all(State) + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks schedule_save(State) end elseif chord == 'C-y' then @@ -329,8 +330,7 @@ function edit.keychord_press(State, chord, key) State.cursor1 = deepcopy(src.cursor) State.selection1 = deepcopy(src.selection) patch(State.lines, event.before, event.after) - -- if we're scrolling, reclaim all fragments to avoid memory leaks - Text.redraw_all(State) + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks schedule_save(State) end -- clipboard @@ -376,9 +376,9 @@ end function edit.key_release(State, key, scancode) end -function edit.update_font_settings(State, font_height) +function edit.update_font_settings(State, font_height, font) State.font_height = font_height - State.font = love.graphics.newFont(State.font_height) + State.font = font or love.graphics.newFont(State.font_height) State.line_height = math.floor(font_height*1.3) end diff --git a/select.lua b/select.lua index e8df6f9..34039c9 100644 --- a/select.lua +++ b/select.lua @@ -1,9 +1,8 @@ -- helpers for selecting portions of text --- Return any intersection of the region from State.selection1 to State.cursor1 (or --- current mouse, if mouse is pressed; or recent mouse if mouse is pressed and --- currently over a drawing) with the region between {line=line_index, pos=apos} --- and {line=line_index, pos=bpos}. +-- Return any intersection of the region from State.selection1 to +-- State.cursor1 (or current mouse, if mouse is pressed) with the region +-- between {line=line_index, pos=apos} and {line=line_index, pos=bpos}. -- apos must be less than bpos. However State.selection1 and State.cursor1 can be in any order. -- Result: positions spos,epos between apos,bpos. function Text.clip_selection(State, line_index, apos, bpos) @@ -45,7 +44,6 @@ function Text.clip_selection(State, line_index, apos, bpos) end -- draw highlight for line corresponding to (lo,hi) given an approximate x,y and pos on the same screen line --- Creates text objects every time, so use this sparingly. -- Returns some intermediate computation useful elsewhere. function Text.draw_highlight(State, line, x,y, pos, lo,hi) if lo then diff --git a/source_edit.lua b/source_edit.lua index 5351857..fd8e321 100644 --- a/source_edit.lua +++ b/source_edit.lua @@ -53,8 +53,7 @@ function edit.initialize_state(top, left, right, font, font_height, line_height) -- rendering wrapped text lines needs some additional short-lived data per line: -- startpos, the index of data the line starts rendering from, can only be >1 for topmost line on screen - -- fragments: snippets of the line guaranteed to not straddle screen lines - -- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line + -- screen_line_starting_pos: optional array of codepoint indices if it wraps over more than one screen line line_cache = {}, -- Given wrapping, any potential location for the text cursor can be described in two ways: @@ -308,7 +307,7 @@ function edit.mouse_release(State, x,y, mouse_button) else --? print_and_log('edit.mouse_release: no current drawing') if y < State.top then - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} + State.cursor1 = deepcopy(State.screen_top1) edit.clean_up_mouse_press(State) return end @@ -351,7 +350,7 @@ end function edit.mouse_wheel_move(State, dx,dy) if dy > 0 then - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} + State.cursor1 = deepcopy(State.screen_top1) edit.put_cursor_on_next_text_line(State) for i=1,math.floor(dy) do Text.up(State) @@ -400,7 +399,7 @@ function edit.keychord_press(State, chord, key) State.cursor1 = State.search_backup.cursor State.screen_top1 = State.search_backup.screen_top State.search_backup = nil - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks elseif chord == 'return' then State.search_term = nil State.search_backup = nil @@ -442,11 +441,9 @@ function edit.keychord_press(State, chord, key) State.cursor1 = deepcopy(src.cursor) State.selection1 = deepcopy(src.selection) patch(State.lines, event.after, event.before) - patch_placeholders(State.line_cache, event.after, event.before) -- invalidate various cached bits of lines State.lines.current_drawing = nil - -- if we're scrolling, reclaim all fragments to avoid memory leaks - Text.redraw_all(State) + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks schedule_save(State) end elseif chord == 'C-y' then @@ -459,8 +456,7 @@ function edit.keychord_press(State, chord, key) patch(State.lines, event.before, event.after) -- invalidate various cached bits of lines State.lines.current_drawing = nil - -- if we're scrolling, reclaim all fragments to avoid memory leaks - Text.redraw_all(State) + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks schedule_save(State) end -- clipboard diff --git a/source_text.lua b/source_text.lua index 6e0c4f9..70bb610 100644 --- a/source_text.lua +++ b/source_text.lua @@ -289,7 +289,7 @@ function Text.keychord_press(State, chord) line=State.cursor1.line, pos=Text.pos_at_start_of_screen_line(State, State.cursor1), } - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks end Text.clear_screen_line_cache(State, State.cursor1.line) assert(Text.le1(State.screen_top1, State.cursor1), ('screen_top (line=%d,pos=%d) is below cursor (line=%d,pos=%d)'):format(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)) @@ -338,12 +338,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'S-left' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.left(State) elseif chord == 'S-right' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.right(State) -- C- hotkeys reserved for drawings, so we'll use M- @@ -355,12 +355,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'M-S-left' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.word_left(State) elseif chord == 'M-S-right' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.word_right(State) elseif chord == 'home' then @@ -371,12 +371,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'S-home' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.start_of_line(State) elseif chord == 'S-end' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.end_of_line(State) elseif chord == 'up' then @@ -387,12 +387,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'S-up' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.up(State) elseif chord == 'S-down' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.down(State) elseif chord == 'pageup' then @@ -403,12 +403,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'S-pageup' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.pageup(State) elseif chord == 'S-pagedown' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.pagedown(State) end @@ -425,9 +425,9 @@ end function Text.pageup(State) State.screen_top1 = Text.previous_screen_top1(State) - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} + State.cursor1 = deepcopy(State.screen_top1) Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State) - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks end -- return the top y coordinate of a given line_index, @@ -474,9 +474,9 @@ end function Text.pagedown(State) State.screen_top1 = Text.screen_bottom1(State) - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} + State.cursor1 = deepcopy(State.screen_top1) Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State) - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks end -- return the location of the start of the bottom-most line on screen @@ -538,7 +538,7 @@ function Text.up(State) line=State.cursor1.line, pos=Text.pos_at_start_of_screen_line(State, State.cursor1), } - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks end end @@ -594,7 +594,7 @@ end function Text.start_of_line(State) State.cursor1.pos = 1 if Text.lt1(State.cursor1, State.screen_top1) then - State.screen_top1 = {line=State.cursor1.line, pos=State.cursor1.pos} -- copy + State.screen_top1 = deepcopy(State.cursor1) end end @@ -684,7 +684,7 @@ function Text.left(State) line=State.cursor1.line, pos=Text.pos_at_start_of_screen_line(State, State.cursor1), } - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks end end @@ -829,7 +829,7 @@ function Text.snap_cursor_to_bottom_of_screen(State) State.screen_top1 = Text.to1(State, top2) --? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos) --? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks end function Text.in_line(State, line_index, x,y) @@ -1103,7 +1103,7 @@ function Text.tweak_screen_top_and_cursor(State) -- make sure cursor is on screen local screen_bottom1 = Text.screen_bottom1(State) if Text.lt1(State.cursor1, State.screen_top1) then - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} + State.cursor1 = deepcopy(State.screen_top1) elseif State.cursor1.line >= screen_bottom1.line then if Text.cursor_out_of_screen(State) then State.cursor1 = Text.final_text_loc_on_screen(State) @@ -1118,7 +1118,7 @@ function Text.cursor_out_of_screen(State) end function Text.redraw_all(State) ---? print('clearing fragments') +--? print('clearing line caches') -- Perform some early sanity checking here, in hopes that we correctly call -- this whenever we change editor state. if State.right <= State.left then diff --git a/source_undo.lua b/source_undo.lua index e5dea93..d91fecd 100644 --- a/source_undo.lua +++ b/source_undo.lua @@ -57,16 +57,8 @@ function snapshot(State, s,e) 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 + table.insert(event.lines, deepcopy(State.lines[i])) end return event end @@ -89,17 +81,6 @@ function patch(lines, from, to) 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 diff --git a/text.lua b/text.lua index 5814693..0d3f07b 100644 --- a/text.lua +++ b/text.lua @@ -47,7 +47,7 @@ function Text.draw(State, line_index, y, startpos) end end end - -- render fragment + -- render screen line App.color(Text_color) App.screen.print(screen_line, State.left,y) y = y + State.line_height @@ -208,7 +208,7 @@ function Text.keychord_press(State, chord) line=State.cursor1.line, pos=Text.pos_at_start_of_screen_line(State, State.cursor1), } - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks end Text.clear_screen_line_cache(State, State.cursor1.line) assert(Text.le1(State.screen_top1, State.cursor1), ('screen_top (line=%d,pos=%d) is below cursor (line=%d,pos=%d)'):format(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)) @@ -255,12 +255,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'S-left' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.left(State) elseif chord == 'S-right' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.right(State) -- C- hotkeys reserved for drawings, so we'll use M- @@ -272,12 +272,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'M-S-left' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.word_left(State) elseif chord == 'M-S-right' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.word_right(State) elseif chord == 'home' then @@ -288,12 +288,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'S-home' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.start_of_line(State) elseif chord == 'S-end' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.end_of_line(State) elseif chord == 'up' then @@ -304,12 +304,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'S-up' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.up(State) elseif chord == 'S-down' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.down(State) elseif chord == 'pageup' then @@ -320,12 +320,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'S-pageup' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.pageup(State) elseif chord == 'S-pagedown' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} + State.selection1 = deepcopy(State.cursor1) end Text.pagedown(State) end @@ -342,9 +342,9 @@ end function Text.pageup(State) State.screen_top1 = Text.previous_screen_top1(State) - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} + State.cursor1 = deepcopy(State.screen_top1) Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State) - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks end -- return the top y coordinate of a given line_index, @@ -380,9 +380,9 @@ end function Text.pagedown(State) State.screen_top1 = Text.screen_bottom1(State) - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} + State.cursor1 = deepcopy(State.screen_top1) Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State) - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks end -- return the location of the start of the bottom-most line on screen @@ -435,7 +435,7 @@ function Text.up(State) line=State.cursor1.line, pos=Text.pos_at_start_of_screen_line(State, State.cursor1), } - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks end end @@ -484,7 +484,7 @@ end function Text.start_of_line(State) State.cursor1.pos = 1 if Text.lt1(State.cursor1, State.screen_top1) then - State.screen_top1 = {line=State.cursor1.line, pos=State.cursor1.pos} -- copy + State.screen_top1 = deepcopy(State.cursor1) end end @@ -564,7 +564,7 @@ function Text.left(State) line=State.cursor1.line, pos=Text.pos_at_start_of_screen_line(State, State.cursor1), } - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks end end @@ -658,7 +658,7 @@ function Text.snap_cursor_to_bottom_of_screen(State) State.screen_top1 = Text.to1(State, top2) --? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos) --? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) - Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks + Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks end function Text.in_line(State, line_index, x,y) @@ -924,7 +924,7 @@ function Text.tweak_screen_top_and_cursor(State) -- make sure cursor is on screen local screen_bottom1 = Text.screen_bottom1(State) if Text.lt1(State.cursor1, State.screen_top1) then - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} + State.cursor1 = deepcopy(State.screen_top1) elseif State.cursor1.line >= screen_bottom1.line then if Text.cursor_out_of_screen(State) then State.cursor1 = Text.final_text_loc_on_screen(State) @@ -939,7 +939,7 @@ function Text.cursor_out_of_screen(State) end function Text.redraw_all(State) ---? print('clearing fragments') +--? print('clearing line caches') -- Perform some early sanity checking here, in hopes that we correctly call -- this whenever we change editor state. if State.right <= State.left then diff --git a/undo.lua b/undo.lua index a41ba38..08b867c 100644 --- a/undo.lua +++ b/undo.lua @@ -55,10 +55,8 @@ function snapshot(State, s,e) 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] - table.insert(event.lines, {data=line.data}) -- I've forgotten: should we deepcopy(line.data)? + table.insert(event.lines, deepcopy(State.lines[i])) end return event end @@ -81,17 +79,6 @@ function patch(lines, from, to) 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 |