about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--edit.lua20
-rw-r--r--select.lua8
-rw-r--r--source_edit.lua16
-rw-r--r--source_text.lua42
-rw-r--r--source_undo.lua21
-rw-r--r--text.lua44
-rw-r--r--undo.lua15
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