about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Manual_tests.md2
-rw-r--r--README.md86
-rw-r--r--edit.lua232
-rw-r--r--file.lua134
-rw-r--r--main.lua12
-rw-r--r--run.lua9
-rw-r--r--select.lua10
-rw-r--r--source.lua4
-rw-r--r--text.lua152
-rw-r--r--text_tests.lua60
-rw-r--r--undo.lua16
11 files changed, 134 insertions, 583 deletions
diff --git a/Manual_tests.md b/Manual_tests.md
index 358ea8b..80ddc6c 100644
--- a/Manual_tests.md
+++ b/Manual_tests.md
@@ -9,7 +9,7 @@ Startup:
 Initializing settings:
     - delete app settings, start; window opens running the text editor
     - quit while running the text editor, restart; window opens running the text editor in same position+dimensions
-    - quit while editing source (color; no drawings; no selection), restart; window opens editing source in same position+dimensions
+    - quit while editing source (color; no selection), restart; window opens editing source in same position+dimensions
     - start out running the text editor, move window, press ctrl+e twice; window is running text editor in same position+dimensions
     - start out editing source, move window, press ctrl+e twice; window is editing source in same position+dimensions
     - no log file; switching to source works
diff --git a/README.md b/README.md
index 1ba565f..1abf90d 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,12 @@
-# Plain text with lines
+# An editor for plain text.
+
+Not very useful by itself, but it's a fork of [lines.love](http://akkartik.name/lines.html)
+that you can take in other directions besides line drawings, while easily
+sharing patches between forks.
 
-An editor for plain text where you can also seamlessly insert line drawings.
 Designed above all to be easy to modify and give you early warning if your
 modifications break something.
 
-http://akkartik.name/lines.html
-
 ## Invocation
 
 To run from the terminal, [pass this directory to LÖVE](https://love2d.org/wiki/Getting_Started#Running_Games),
@@ -13,13 +14,13 @@ optionally with a file path to edit.
 
 Alternatively, turn it into a .love file you can double-click on:
 ```
-$ zip -r /tmp/lines.love *.lua
+$ zip -r /tmp/text.love *.lua
 ```
 
-By default, lines.love reads/writes the file `lines.txt` in your default
+By default, it reads/writes the file `lines.txt` in your default
 user/home directory (`https://love2d.org/wiki/love.filesystem.getUserDirectory`).
 
-To open a different file, drop it on the lines.love window.
+To open a different file, drop it on the app window.
 
 ## Keyboard shortcuts
 
@@ -31,12 +32,7 @@ While editing text:
 * `alt+right`/`alt+left` to jump to the next/previous word, respectively
 * `ctrl+e` to modify the sources
 
-For shortcuts while editing drawings, consult the online help. Either:
-* hover on a drawing and hit `ctrl+h`, or
-* click on a drawing to start a stroke and then press and hold `h` to see your
-  options at any point during a stroke.
-
-lines.love has been exclusively tested so far with a US keyboard layout. If
+Exclusively tested so far with a US keyboard layout. If
 you use a different layout, please let me know if things worked, or if you
 found anything amiss: http://akkartik.name/contact
 
@@ -47,26 +43,11 @@ found anything amiss: http://akkartik.name/contact
 * No support yet for right-to-left languages.
 
 * Undo/redo may be sluggish in large files. Large files may grow sluggish in
-  other ways. lines.love works well in all circumstances with files under
-  50KB.
+  other ways. Works well in all circumstances with files under 50KB.
 
 * If you kill the process, say by force-quitting because things things get
   sluggish, you can lose data.
 
-* The text cursor will always stay on the screen. This can have some strange
-  implications:
-
-    * A long series of drawings will get silently skipped when you hit
-      page-down, until a line of text can be showed on screen.
-    * If there's no line of text at the top of the file, you may not be able
-      to scroll back up to the top with page-up.
-
-  So far this app isn't really designed for drawing-heavy files. For now I'm
-  targeting mostly-text files with a few drawings mixed in.
-
-* No clipping yet for drawings. In particular, circles/squares/rectangles and
-  point labels can overflow a drawing.
-
 * Long wrapping lines can't yet distinguish between the cursor at end of one
   screen line and start of the next, so clicking the mouse to position the
   cursor can very occasionally do the wrong thing.
@@ -83,35 +64,24 @@ found anything amiss: http://akkartik.name/contact
 
 ## Mirrors and Forks
 
-Updates to lines.love can be downloaded from the following mirrors in addition
-to the website above:
-* https://github.com/akkartik/lines.love
-* https://repo.or.cz/lines.love.git
-* https://codeberg.org/akkartik/lines.love
-* https://tildegit.org/akkartik/lines.love
-* https://git.tilde.institute/akkartik/lines.love
-* https://git.sr.ht/~akkartik/lines.love
-* https://notabug.org/akkartik/lines.love
-* https://pagure.io/lines.love
-
-Forks of lines.love are encouraged. If you show me your fork, I'll link to it
-here.
-
-* https://github.com/akkartik/lines-polygon-experiment -- an experiment that
-  uses separate shortcuts for regular polygons. `ctrl+3` for triangles,
-  `ctrl+4` for squares, etc.
-* https://codeberg.org/akkartik/text.love -- a stripped down version without
-  drawings; useful starting point for some forks
-* https://git.sr.ht/~akkartik/pensieve.love -- a note-taking app on an
-  infinite 2D surface. Still in development.
-* https://git.sr.ht/~akkartik/capture.love -- a blank-slate mode for the
-  note-taking app, so all the stuff pensieve.love puts on screen doesn't cause
-  you to forget what you came to write down.
-
-## Associated tools
-
-* https://codeberg.org/akkartik/lines2md exports lines.love files to Markdown
-  and (non-editable) SVG.
+This repo is a fork of lines.love at [http://akkartik.name/lines.html](http://akkartik.name/lines.html).
+Updates to it can be downloaded from the following mirrors:
+
+* https://codeberg.org/akkartik/text.love
+* https://repo.or.cz/text.love.git
+* https://tildegit.org/akkartik/text.love
+* https://git.tilde.institute/akkartik/text.love
+* https://git.sr.ht/~akkartik/text.love
+* https://notabug.org/akkartik/text.love
+* https://github.com/akkartik/text.love
+* https://pagure.io/text.love
+
+Further forks are encouraged. If you show me your fork, I'll link to it here.
+
+* https://codeberg.org/akkartik/view.love -- a stripped down version without
+  support for modifying files; useful starting point for some forks.
+* https://codeberg.org/akkartik/pong.love -- a fairly minimal example app that
+  can edit and debug its own source code.
 
 ## Feedback
 
diff --git a/edit.lua b/edit.lua
index 3c722a7..dae6d77 100644
--- a/edit.lua
+++ b/edit.lua
@@ -1,51 +1,19 @@
 -- some constants people might like to tweak
 Text_color = {r=0, g=0, b=0}
 Cursor_color = {r=1, g=0, b=0}
-Stroke_color = {r=0, g=0, b=0}
-Current_stroke_color = {r=0.7, g=0.7, b=0.7}  -- in process of being drawn
-Current_name_background_color = {r=1, g=0, b=0, a=0.1}  -- name currently being edited
 Focus_stroke_color = {r=1, g=0, b=0}  -- what mouse is hovering over
 Highlight_color = {r=0.7, g=0.7, b=0.9}  -- selected text
-Icon_color = {r=0.7, g=0.7, b=0.7}  -- color of current mode icon in drawings
-Help_color = {r=0, g=0.5, b=0}
-Help_background_color = {r=0, g=0.5, b=0, a=0.1}
 
 Margin_top = 15
 Margin_left = 25
 Margin_right = 25
 
-Drawing_padding_top = 10
-Drawing_padding_bottom = 10
-Drawing_padding_height = Drawing_padding_top + Drawing_padding_bottom
-
-Same_point_distance = 4  -- pixel distance at which two points are considered the same
-
 edit = {}
 
 -- run in both tests and a real run
 function edit.initialize_state(top, left, right, font_height, line_height)  -- currently always draws to bottom of screen
   local result = {
-    -- a line is either text or a drawing
-    -- a text is a table with:
-    --    mode = 'text',
-    --    string data,
-    -- a drawing is a table with:
-    --    mode = 'drawing'
-    --    a (y) coord in pixels (updated while painting screen),
-    --    a (h)eight,
-    --    an array of points, and
-    --    an array of shapes
-    -- a shape is a table containing:
-    --    a mode
-    --    an array points for mode 'freehand' (raw x,y coords; freehand drawings don't pollute the points array of a drawing)
-    --    an array vertices for mode 'polygon', 'rectangle', 'square'
-    --    p1, p2 for mode 'line'
-    --    center, radius for mode 'circle'
-    --    center, radius, start_angle, end_angle for mode 'arc'
-    -- Unless otherwise specified, coord fields are normalized; a drawing is always 256 units wide
-    -- The field names are carefully chosen so that switching modes in midstream
-    -- remembers previously entered points where that makes sense.
-    lines = {{mode='text', data=''}},  -- array of lines
+    lines = {{data=''}},  -- array of strings
 
     -- Lines can be too long to fit on screen, in which case they _wrap_ into
     -- multiple _screen lines_.
@@ -82,9 +50,6 @@ function edit.initialize_state(top, left, right, font_height, line_height)  -- c
     cursor_x = 0,
     cursor_y = 0,
 
-    current_drawing_mode = 'line',
-    previous_drawing_mode = nil,  -- extra state for some ephemeral modes like moving/deleting/naming points
-
     font_height = font_height,
     line_height = line_height,
     em = App.newText(love.graphics.getFont(), 'm'),  -- widest possible character width
@@ -109,15 +74,6 @@ function edit.initialize_state(top, left, right, font_height, line_height)  -- c
   return result
 end  -- App.initialize_state
 
-function edit.fixup_cursor(State)
-  for i,line in ipairs(State.lines) do
-    if line.mode == 'text' then
-      State.cursor1.line = i
-      break
-    end
-  end
-end
-
 function edit.draw(State)
   State.button_handlers = {}
   App.color(Text_color)
@@ -135,39 +91,14 @@ function edit.draw(State)
 --?     print('draw:', y, line_index, line)
     if y + State.line_height > App.screen.height then break end
     State.screen_bottom1 = {line=line_index, pos=nil}
-    if line.mode == 'text' then
---?       print('text.draw', y, line_index)
-      local startpos = 1
-      if line_index == State.screen_top1.line then
-        startpos = State.screen_top1.pos
-      end
-      if line.data == '' then
-        -- button to insert new drawing
-        button(State, 'draw', {x=4,y=y+4, w=12,h=12, color={1,1,0},
-          icon = icon.insert_drawing,
-          onpress1 = function()
-                       Drawing.before = snapshot(State, line_index-1, line_index)
-                       table.insert(State.lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}})
-                       table.insert(State.line_cache, line_index, {})
-                       if State.cursor1.line >= line_index then
-                         State.cursor1.line = State.cursor1.line+1
-                       end
-                       schedule_save(State)
-                       record_undo_event(State, {before=Drawing.before, after=snapshot(State, line_index-1, line_index+1)})
-                     end,
-        })
-      end
-      y, State.screen_bottom1.pos = Text.draw(State, line_index, y, startpos)
-      y = y + State.line_height
---?       print('=> y', y)
-    elseif line.mode == 'drawing' then
-      y = y+Drawing_padding_top
-      Drawing.draw(State, line_index, y)
-      y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom
-    else
-      print(line.mode)
-      assert(false)
+--?     print('text.draw', y, line_index)
+    local startpos = 1
+    if line_index == State.screen_top1.line then
+      startpos = State.screen_top1.pos
     end
+    y, State.screen_bottom1.pos = Text.draw(State, line_index, y, startpos)
+    y = y + State.line_height
+--?     print('=> y', y)
   end
   if State.search_term then
     Text.draw_search_bar(State)
@@ -175,7 +106,6 @@ function edit.draw(State)
 end
 
 function edit.update(State, dt)
-  Drawing.update(State, dt)
   if State.next_save and State.next_save < App.getTime() then
     save_to_disk(State)
     State.next_save = nil
@@ -204,36 +134,25 @@ function edit.mouse_pressed(State, x,y, mouse_button)
   end
 
   for line_index,line in ipairs(State.lines) do
-    if line.mode == 'text' then
-      if Text.in_line(State, line_index, x,y) then
-        -- delicate dance between cursor, selection and old cursor/selection
-        -- scenarios:
-        --  regular press+release: sets cursor, clears selection
-        --  shift press+release:
-        --    sets selection to old cursor if not set otherwise leaves it untouched
-        --    sets cursor
-        --  press and hold to start a selection: sets selection on press, cursor on release
-        --  press and hold, then press shift: ignore shift
-        --    i.e. mouse_released should never look at shift state
-        State.old_cursor1 = State.cursor1
-        State.old_selection1 = State.selection1
-        State.mousepress_shift = App.shift_down()
-        State.selection1 = {
-            line=line_index,
-            pos=Text.to_pos_on_line(State, line_index, x, y),
-        }
---?         print('selection', State.selection1.line, State.selection1.pos)
-        break
-      end
-    elseif line.mode == 'drawing' then
-      local line_cache = State.line_cache[line_index]
-      if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then
-        State.lines.current_drawing_index = line_index
-        State.lines.current_drawing = line
-        Drawing.before = snapshot(State, line_index)
-        Drawing.mouse_pressed(State, line_index, x,y, mouse_button)
-        break
-      end
+    if Text.in_line(State, line_index, x,y) then
+      -- delicate dance between cursor, selection and old cursor/selection
+      -- scenarios:
+      --  regular press+release: sets cursor, clears selection
+      --  shift press+release:
+      --    sets selection to old cursor if not set otherwise leaves it untouched
+      --    sets cursor
+      --  press and hold to start a selection: sets selection on press, cursor on release
+      --  press and hold, then press shift: ignore shift
+      --    i.e. mouse_released should never look at shift state
+      State.old_cursor1 = State.cursor1
+      State.old_selection1 = State.selection1
+      State.mousepress_shift = App.shift_down()
+      State.selection1 = {
+          line=line_index,
+          pos=Text.to_pos_on_line(State, line_index, x, y),
+      }
+--?       print('selection', State.selection1.line, State.selection1.pos)
+      break
     end
   end
 end
@@ -241,40 +160,29 @@ end
 function edit.mouse_released(State, x,y, mouse_button)
   if State.search_term then return end
 --?   print('release')
-  if State.lines.current_drawing then
-    Drawing.mouse_released(State, x,y, mouse_button)
-    schedule_save(State)
-    if Drawing.before then
-      record_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})
-      Drawing.before = nil
-    end
-  else
-    for line_index,line in ipairs(State.lines) do
-      if line.mode == 'text' then
-        if Text.in_line(State, line_index, x,y) then
---?           print('reset selection')
-          State.cursor1 = {
-              line=line_index,
-              pos=Text.to_pos_on_line(State, line_index, x, y),
-          }
---?           print('cursor', State.cursor1.line, State.cursor1.pos)
-          if State.mousepress_shift then
-            if State.old_selection1.line == nil then
-              State.selection1 = State.old_cursor1
-            else
-              State.selection1 = State.old_selection1
-            end
-          end
-          State.old_cursor1, State.old_selection1, State.mousepress_shift = nil
-          if eq(State.cursor1, State.selection1) then
-            State.selection1 = {}
-          end
-          break
+  for line_index,line in ipairs(State.lines) do
+    if Text.in_line(State, line_index, x,y) then
+--?       print('reset selection')
+      State.cursor1 = {
+          line=line_index,
+          pos=Text.to_pos_on_line(State, line_index, x, y),
+      }
+--?       print('cursor', State.cursor1.line, State.cursor1.pos)
+      if State.mousepress_shift then
+        if State.old_selection1.line == nil then
+          State.selection1 = State.old_cursor1
+        else
+          State.selection1 = State.old_selection1
         end
       end
+      State.old_cursor1, State.old_selection1, State.mousepress_shift = nil
+      if eq(State.cursor1, State.selection1) then
+        State.selection1 = {}
+      end
+      break
     end
---?     print('selection:', State.selection1.line, State.selection1.pos)
   end
+--?   print('selection:', State.selection1.line, State.selection1.pos)
 end
 
 function edit.textinput(State, t)
@@ -283,12 +191,6 @@ function edit.textinput(State, t)
     State.search_term = State.search_term..t
     State.search_text = nil
     Text.search_next(State)
-  elseif State.current_drawing_mode == 'name' then
-    local before = snapshot(State, State.lines.current_drawing_index)
-    local drawing = State.lines.current_drawing
-    local p = drawing.points[drawing.pending.target_point]
-    p.name = p.name..t
-    record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
   else
     Text.textinput(State, t)
   end
@@ -297,7 +199,6 @@ end
 
 function edit.keychord_pressed(State, chord, key)
   if State.selection1.line and
-      not State.lines.current_drawing and
       -- printable character created using shift key => delete selection
       -- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)
       (not App.shift_down() or utf8.len(key) == 1) and
@@ -356,8 +257,6 @@ function edit.keychord_pressed(State, chord, key)
       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)
       schedule_save(State)
@@ -371,8 +270,6 @@ function edit.keychord_pressed(State, chord, key)
       State.cursor1 = deepcopy(src.cursor)
       State.selection1 = deepcopy(src.selection)
       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)
       schedule_save(State)
@@ -410,42 +307,7 @@ function edit.keychord_pressed(State, chord, key)
     end
     schedule_save(State)
     record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
-  -- dispatch to drawing or text
-  elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then
-    -- DON'T reset line_cache.starty here
-    local drawing_index, drawing = Drawing.current_drawing(State)
-    if drawing_index then
-      local before = snapshot(State, drawing_index)
-      Drawing.keychord_pressed(State, chord)
-      record_undo_event(State, {before=before, after=snapshot(State, drawing_index)})
-      schedule_save(State)
-    end
-  elseif chord == 'escape' and not App.mouse_down(1) then
-    for _,line in ipairs(State.lines) do
-      if line.mode == 'drawing' then
-        line.show_help = false
-      end
-    end
-  elseif State.current_drawing_mode == 'name' then
-    if chord == 'return' then
-      State.current_drawing_mode = State.previous_drawing_mode
-      State.previous_drawing_mode = nil
-    else
-      local before = snapshot(State, State.lines.current_drawing_index)
-      local drawing = State.lines.current_drawing
-      local p = drawing.points[drawing.pending.target_point]
-      if chord == 'escape' then
-        p.name = nil
-        record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
-      elseif chord == 'backspace' then
-        local len = utf8.len(p.name)
-        local byte_offset = Text.offset(p.name, len-1)
-        if len == 1 then byte_offset = 0 end
-        p.name = string.sub(p.name, 1, byte_offset)
-        record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
-      end
-    end
-    schedule_save(State)
+  -- dispatch to text
   else
     for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end  -- just in case we scroll
     Text.keychord_pressed(State, chord)
diff --git a/file.lua b/file.lua
index 6c460a9..98311da 100644
--- a/file.lua
+++ b/file.lua
@@ -22,15 +22,11 @@ function load_from_file(infile)
     while true do
       local line = infile_next_line()
       if line == nil then break end
-      if line == '```lines' then  -- inflexible with whitespace since these files are always autogenerated
-        table.insert(result, load_drawing(infile_next_line))
-      else
-        table.insert(result, {mode='text', data=line})
-      end
+      table.insert(result, {data=line})
     end
   end
   if #result == 0 then
-    table.insert(result, {mode='text', data=''})
+    table.insert(result, {data=''})
   end
   return result
 end
@@ -41,82 +37,12 @@ function save_to_disk(State)
     error('failed to write to "'..State.filename..'"')
   end
   for _,line in ipairs(State.lines) do
-    if line.mode == 'drawing' then
-      store_drawing(outfile, line)
-    else
-      outfile:write(line.data)
-      outfile:write('\n')
-    end
+    outfile:write(line.data)
+    outfile:write('\n')
   end
   outfile:close()
 end
 
-function load_drawing(infile_next_line)
-  local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
-  while true do
-    local line = infile_next_line()
-    assert(line)
-    if line == '```' then break end
-    local shape = json.decode(line)
-    if shape.mode == 'freehand' then
-      -- no changes needed
-    elseif shape.mode == 'line' or shape.mode == 'manhattan' then
-      local name = shape.p1.name
-      shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600)
-      drawing.points[shape.p1].name = name
-      name = shape.p2.name
-      shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600)
-      drawing.points[shape.p2].name = name
-    elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
-      for i,p in ipairs(shape.vertices) do
-        local name = p.name
-        shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600)
-        drawing.points[shape.vertices[i]].name = name
-      end
-    elseif shape.mode == 'circle' or shape.mode == 'arc' then
-      local name = shape.center.name
-      shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600)
-      drawing.points[shape.center].name = name
-    elseif shape.mode == 'deleted' then
-      -- ignore
-    else
-      print(shape.mode)
-      assert(false)
-    end
-    table.insert(drawing.shapes, shape)
-  end
-  return drawing
-end
-
-function store_drawing(outfile, drawing)
-  outfile:write('```lines\n')
-  for _,shape in ipairs(drawing.shapes) do
-    if shape.mode == 'freehand' then
-      outfile:write(json.encode(shape), '\n')
-    elseif shape.mode == 'line' or shape.mode == 'manhattan' then
-      local line = json.encode({mode=shape.mode, p1=drawing.points[shape.p1], p2=drawing.points[shape.p2]})
-      outfile:write(line, '\n')
-    elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
-      local obj = {mode=shape.mode, vertices={}}
-      for _,p in ipairs(shape.vertices) do
-        table.insert(obj.vertices, drawing.points[p])
-      end
-      local line = json.encode(obj)
-      outfile:write(line, '\n')
-    elseif shape.mode == 'circle' then
-      outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius}), '\n')
-    elseif shape.mode == 'arc' then
-      outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius, start_angle=shape.start_angle, end_angle=shape.end_angle}), '\n')
-    elseif shape.mode == 'deleted' then
-      -- ignore
-    else
-      print(shape.mode)
-      assert(false)
-    end
-  end
-  outfile:write('```\n')
-end
-
 -- for tests
 function load_array(a)
   local result = {}
@@ -125,58 +51,10 @@ function load_array(a)
   while true do
     i,line = next_line(a, i)
     if i == nil then break end
---?     print(line)
-    if line == '```lines' then  -- inflexible with whitespace since these files are always autogenerated
---?       print('inserting drawing')
-      i, drawing = load_drawing_from_array(next_line, a, i)
---?       print('i now', i)
-      table.insert(result, drawing)
-    else
---?       print('inserting text')
-      table.insert(result, {mode='text', data=line})
-    end
+    table.insert(result, {data=line})
   end
   if #result == 0 then
-    table.insert(result, {mode='text', data=''})
+    table.insert(result, {data=''})
   end
   return result
 end
-
-function load_drawing_from_array(iter, a, i)
-  local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
-  local line
-  while true do
-    i, line = iter(a, i)
-    assert(i)
---?     print(i)
-    if line == '```' then break end
-    local shape = json.decode(line)
-    if shape.mode == 'freehand' then
-      -- no changes needed
-    elseif shape.mode == 'line' or shape.mode == 'manhattan' then
-      local name = shape.p1.name
-      shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600)
-      drawing.points[shape.p1].name = name
-      name = shape.p2.name
-      shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600)
-      drawing.points[shape.p2].name = name
-    elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
-      for i,p in ipairs(shape.vertices) do
-        local name = p.name
-        shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600)
-        drawing.points[shape.vertices[i]].name = name
-      end
-    elseif shape.mode == 'circle' or shape.mode == 'arc' then
-      local name = shape.center.name
-      shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600)
-      drawing.points[shape.center].name = name
-    elseif shape.mode == 'deleted' then
-      -- ignore
-    else
-      print(shape.mode)
-      assert(false)
-    end
-    table.insert(drawing.shapes, shape)
-  end
-  return i, drawing
-end
diff --git a/main.lua b/main.lua
index fd0fe45..8e926c8 100644
--- a/main.lua
+++ b/main.lua
@@ -24,13 +24,6 @@ load_file_from_source_or_save_directory('button.lua')
 -- both sides require (different parts of) the logging framework
 load_file_from_source_or_save_directory('log.lua')
 
--- both sides use drawings
-load_file_from_source_or_save_directory('icons.lua')
-load_file_from_source_or_save_directory('drawing.lua')
-  load_file_from_source_or_save_directory('geom.lua')
-  load_file_from_source_or_save_directory('help.lua')
-load_file_from_source_or_save_directory('drawing_tests.lua')
-
 -- but some files we want to only load sometimes
 function App.load()
   if love.filesystem.getInfo('config') then
@@ -64,6 +57,11 @@ function App.load()
         load_file_from_source_or_save_directory('source_undo.lua')
         load_file_from_source_or_save_directory('colorize.lua')
       load_file_from_source_or_save_directory('source_text_tests.lua')
+      load_file_from_source_or_save_directory('icons.lua')
+      load_file_from_source_or_save_directory('drawing.lua')
+        load_file_from_source_or_save_directory('geom.lua')
+        load_file_from_source_or_save_directory('help.lua')
+      load_file_from_source_or_save_directory('drawing_tests.lua')
     load_file_from_source_or_save_directory('source_tests.lua')
   else
     assert(false, 'unknown app "'..Current_app..'"')
diff --git a/run.lua b/run.lua
index 188269b..16857f9 100644
--- a/run.lua
+++ b/run.lua
@@ -32,15 +32,11 @@ function run.initialize(arg)
     Text.redraw_all(Editor_state)
     Editor_state.screen_top1 = {line=1, pos=1}
     Editor_state.cursor1 = {line=1, pos=1}
-    edit.fixup_cursor(Editor_state)
   else
     load_from_disk(Editor_state)
     Text.redraw_all(Editor_state)
-    if Editor_state.cursor1.line > #Editor_state.lines or Editor_state.lines[Editor_state.cursor1.line].mode ~= 'text' then
-      edit.fixup_cursor(Editor_state)
-    end
   end
-  love.window.setTitle('lines.love - '..Editor_state.filename)
+  love.window.setTitle('text.love - '..Editor_state.filename)
 
   if #arg > 1 then
     print('ignoring commandline args after '..arg[1])
@@ -116,8 +112,7 @@ function run.filedropped(file)
   Editor_state.lines = load_from_file(file)
   file:close()
   Text.redraw_all(Editor_state)
-  edit.fixup_cursor(Editor_state)
-  love.window.setTitle('lines.love - '..Editor_state.filename)
+  love.window.setTitle('text.love - '..Editor_state.filename)
 end
 
 function run.draw()
diff --git a/select.lua b/select.lua
index bae9504..b7cf65f 100644
--- a/select.lua
+++ b/select.lua
@@ -89,10 +89,8 @@ end
 
 function Text.to_pos(State, x,y)
   for line_index,line in ipairs(State.lines) do
-    if line.mode == 'text' then
-      if Text.in_line(State, line_index, x,y) then
-        return line_index, Text.to_pos_on_line(State, line_index, x,y)
-      end
+    if Text.in_line(State, line_index, x,y) then
+      return line_index, Text.to_pos_on_line(State, line_index, x,y)
     end
   end
 end
@@ -172,9 +170,7 @@ function Text.selection(State)
   assert(minl < maxl)
   local result = {State.lines[minl].data:sub(min_offset)}
   for i=minl+1,maxl-1 do
-    if State.lines[i].mode == 'text' then
-      table.insert(result, State.lines[i].data)
-    end
+    table.insert(result, State.lines[i].data)
   end
   table.insert(result, State.lines[maxl].data:sub(1, max_offset-1))
   return table.concat(result, '\n')
diff --git a/source.lua b/source.lua
index fc2ff3c..f4527ca 100644
--- a/source.lua
+++ b/source.lua
@@ -73,7 +73,7 @@ function source.initialize()
   Menu_status_bar_height = 5 + Editor_state.line_height + 5
   Editor_state.top = Editor_state.top + Menu_status_bar_height
   Log_browser_state.top = Log_browser_state.top + Menu_status_bar_height
-  love.window.setTitle('lines.love - source')
+  love.window.setTitle('text.love - source')
 end
 
 -- environment for a mutable file of bifolded text
@@ -201,7 +201,7 @@ function source.filedropped(file)
   Text.redraw_all(Editor_state)
   Editor_state.screen_top1 = {line=1, pos=1}
   Editor_state.cursor1 = {line=1, pos=1}
-  love.window.setTitle('lines.love - source')
+  love.window.setTitle('text.love - source')
 end
 
 -- a copy of source.filedropped when given a filename
diff --git a/text.lua b/text.lua
index 9720bfa..ff958f9 100644
--- a/text.lua
+++ b/text.lua
@@ -77,7 +77,6 @@ end
 
 function Text.populate_screen_line_starting_pos(State, line_index)
   local line = State.lines[line_index]
-  if line.mode ~= 'text' then return end
   local line_cache = State.line_cache[line_index]
   if line_cache.screen_line_starting_pos then
     return
@@ -104,7 +103,6 @@ end
 function Text.compute_fragments(State, line_index)
 --?   print('compute_fragments', line_index, 'between', State.left, State.right)
   local line = State.lines[line_index]
-  if line.mode ~= 'text' then return end
   local line_cache = State.line_cache[line_index]
   if line_cache.fragments then
     return
@@ -214,16 +212,11 @@ function Text.keychord_pressed(State, chord)
       end
     elseif State.cursor1.line > 1 then
       before = snapshot(State, State.cursor1.line-1, State.cursor1.line)
-      if State.lines[State.cursor1.line-1].mode == 'drawing' then
-        table.remove(State.lines, State.cursor1.line-1)
-        table.remove(State.line_cache, State.cursor1.line-1)
-      else
-        -- join lines
-        State.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1].data)+1
-        State.lines[State.cursor1.line-1].data = State.lines[State.cursor1.line-1].data..State.lines[State.cursor1.line].data
-        table.remove(State.lines, State.cursor1.line)
-        table.remove(State.line_cache, State.cursor1.line)
-      end
+      -- join lines
+      State.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1].data)+1
+      State.lines[State.cursor1.line-1].data = State.lines[State.cursor1.line-1].data..State.lines[State.cursor1.line].data
+      table.remove(State.lines, State.cursor1.line)
+      table.remove(State.line_cache, State.cursor1.line)
       State.cursor1.line = State.cursor1.line-1
     end
     if State.screen_top1.line > #State.lines then
@@ -264,10 +257,8 @@ function Text.keychord_pressed(State, chord)
         -- no change to State.cursor1.pos
       end
     elseif State.cursor1.line < #State.lines then
-      if State.lines[State.cursor1.line+1].mode == 'text' then
-        -- join lines
-        State.lines[State.cursor1.line].data = State.lines[State.cursor1.line].data..State.lines[State.cursor1.line+1].data
-      end
+      -- join lines
+      State.lines[State.cursor1.line].data = State.lines[State.cursor1.line].data..State.lines[State.cursor1.line+1].data
       table.remove(State.lines, State.cursor1.line+1)
       table.remove(State.line_cache, State.cursor1.line+1)
     end
@@ -361,7 +352,7 @@ end
 
 function Text.insert_return(State)
   local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
-  table.insert(State.lines, State.cursor1.line+1, {mode='text', data=string.sub(State.lines[State.cursor1.line].data, byte_offset)})
+  table.insert(State.lines, State.cursor1.line+1, {data=string.sub(State.lines[State.cursor1.line].data, byte_offset)})
   table.insert(State.line_cache, State.cursor1.line+1, {})
   State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)
   Text.clear_screen_line_cache(State, State.cursor1.line)
@@ -377,11 +368,7 @@ function Text.pageup(State)
   while y >= State.top do
 --?     print(y, top2.line, top2.screen_line, top2.screen_pos)
     if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end
-    if State.lines[State.screen_top1.line].mode == 'text' then
-      y = y - State.line_height
-    elseif State.lines[State.screen_top1.line].mode == 'drawing' then
-      y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)
-    end
+    y = y - State.line_height
     top2 = Text.previous_screen_line(State, top2)
   end
   State.screen_top1 = Text.to1(State, top2)
@@ -415,29 +402,24 @@ function Text.pagedown(State)
 end
 
 function Text.up(State)
-  assert(State.lines[State.cursor1.line].mode == 'text')
 --?   print('up', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
   local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)
   if screen_line_starting_pos == 1 then
 --?     print('cursor is at first screen line of its line')
     -- line is done; skip to previous text line
-    local new_cursor_line = State.cursor1.line
-    while new_cursor_line > 1 do
-      new_cursor_line = new_cursor_line-1
-      if State.lines[new_cursor_line].mode == 'text' then
---?         print('found previous text line')
-        State.cursor1 = {line=State.cursor1.line-1, pos=nil}
-        Text.populate_screen_line_starting_pos(State, State.cursor1.line)
-        -- previous text line found, pick its final screen line
---?         print('has multiple screen lines')
-        local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos
---?         print(#screen_line_starting_pos)
-        screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos]
-        local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos)
-        local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset)
-        State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1
-        break
-      end
+    if State.cursor1.line > 1 then
+      local new_cursor_line = State.cursor1.line-1
+--?       print('found previous text line')
+      State.cursor1.line = new_cursor_line
+      Text.populate_screen_line_starting_pos(State, State.cursor1.line)
+      -- previous text line found, pick its final screen line
+--?       print('has multiple screen lines')
+      local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos
+--?       print(#screen_line_starting_pos)
+      screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos]
+      local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos)
+      local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset)
+      State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1
     end
   else
     -- move up one screen line in current line
@@ -456,22 +438,15 @@ function Text.up(State)
 end
 
 function Text.down(State)
-  assert(State.lines[State.cursor1.line].mode == 'text')
 --?   print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
   if Text.cursor_at_final_screen_line(State) then
     -- line is done, skip to next text line
 --?     print('cursor at final screen line of its line')
-    local new_cursor_line = State.cursor1.line
-    while new_cursor_line < #State.lines do
-      new_cursor_line = new_cursor_line+1
-      if State.lines[new_cursor_line].mode == 'text' then
-        State.cursor1 = {
-          line = new_cursor_line,
-          pos = Text.nearest_cursor_pos(State.lines[new_cursor_line].data, State.cursor_x, State.left),
-        }
---?         print(State.cursor1.pos)
-        break
-      end
+    if State.cursor1.line < #State.lines then
+      local new_cursor_line = State.cursor1.line+1
+      State.cursor1.line = new_cursor_line
+      State.cursor1.pos = Text.nearest_cursor_pos(State.lines[State.cursor1.line].data, State.cursor_x, State.left)
+--?       print(State.cursor1.pos)
     end
     if State.cursor1.line > State.screen_bottom1.line then
 --?       print('screen top before:', State.screen_top1.line, State.screen_top1.pos)
@@ -573,21 +548,11 @@ function Text.match(s, pos, pat)
 end
 
 function Text.left(State)
-  assert(State.lines[State.cursor1.line].mode == 'text')
   if State.cursor1.pos > 1 then
     State.cursor1.pos = State.cursor1.pos-1
-  else
-    local new_cursor_line = State.cursor1.line
-    while new_cursor_line > 1 do
-      new_cursor_line = new_cursor_line-1
-      if State.lines[new_cursor_line].mode == 'text' then
-        State.cursor1 = {
-          line = new_cursor_line,
-          pos = utf8.len(State.lines[new_cursor_line].data) + 1,
-        }
-        break
-      end
-    end
+  elseif State.cursor1.line > 1 then
+    State.cursor1.line = State.cursor1.line-1
+    State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1
   end
   if Text.lt1(State.cursor1, State.screen_top1) then
     local top2 = Text.to2(State, State.screen_top1)
@@ -604,18 +569,11 @@ function Text.right(State)
 end
 
 function Text.right_without_scroll(State)
-  assert(State.lines[State.cursor1.line].mode == 'text')
   if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
     State.cursor1.pos = State.cursor1.pos+1
-  else
-    local new_cursor_line = State.cursor1.line
-    while new_cursor_line <= #State.lines-1 do
-      new_cursor_line = new_cursor_line+1
-      if State.lines[new_cursor_line].mode == 'text' then
-        State.cursor1 = {line=new_cursor_line, pos=1}
-        break
-      end
-    end
+  elseif State.cursor1.line <= #State.lines-1 then
+    State.cursor1.line = State.cursor1.line+1
+    State.cursor1.pos = 1
   end
 end
 
@@ -639,23 +597,7 @@ function Text.cursor_at_final_screen_line(State)
 end
 
 function Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
-  local y = State.top
-  while State.cursor1.line <= #State.lines do
-    if State.lines[State.cursor1.line].mode == 'text' then
-      break
-    end
---?     print('cursor skips', State.cursor1.line)
-    y = y + Drawing_padding_height + Drawing.pixels(State.lines[State.cursor1.line].h, State.width)
-    State.cursor1.line = State.cursor1.line + 1
-  end
-  -- hack: insert a text line at bottom of file if necessary
-  if State.cursor1.line > #State.lines then
-    assert(State.cursor1.line == #State.lines+1)
-    table.insert(State.lines, {mode='text', data=''})
-    table.insert(State.line_cache, {})
-  end
---?   print(y, App.screen.height, App.screen.height-State.line_height)
-  if y > App.screen.height - State.line_height then
+  if State.top > App.screen.height - State.line_height then
 --?     print('scroll up')
     Text.snap_cursor_to_bottom_of_screen(State)
   end
@@ -675,24 +617,11 @@ function Text.snap_cursor_to_bottom_of_screen(State)
   while true do
 --?     print(y, 'top2:', top2.line, top2.screen_line, top2.screen_pos)
     if top2.line == 1 and top2.screen_line == 1 then break end
-    if top2.screen_line > 1 or State.lines[top2.line-1].mode == 'text' then
-      local h = State.line_height
-      if y - h < State.top then
-        break
-      end
-      y = y - h
-    else
-      assert(top2.line > 1)
-      assert(State.lines[top2.line-1].mode == 'drawing')
-      -- We currently can't draw partial drawings, so either skip it entirely
-      -- or not at all.
-      local h = Drawing_padding_height + Drawing.pixels(State.lines[top2.line-1].h, State.width)
-      if y - h < State.top then
-        break
-      end
---?       print('skipping drawing of height', h)
-      y = y - h
+    local h = State.line_height
+    if y - h < State.top then
+      break
     end
+    y = y - h
     top2 = Text.previous_screen_line(State, top2)
   end
 --?   print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos)
@@ -855,9 +784,6 @@ function Text.x(s, pos)
 end
 
 function Text.to2(State, loc1)
-  if State.lines[loc1.line].mode == 'drawing' then
-    return {line=loc1.line, screen_line=1, screen_pos=1}
-  end
   local result = {line=loc1.line}
   local line_cache = State.line_cache[loc1.line]
   Text.populate_screen_line_starting_pos(State, loc1.line)
@@ -920,8 +846,6 @@ function Text.previous_screen_line(State, loc2)
     return {line=loc2.line, screen_line=loc2.screen_line-1, screen_pos=1}
   elseif loc2.line == 1 then
     return loc2
-  elseif State.lines[loc2.line-1].mode == 'drawing' then
-    return {line=loc2.line-1, screen_line=1, screen_pos=1}
   else
     local l = State.lines[loc2.line-1]
     Text.populate_screen_line_starting_pos(State, loc2.line-1)
diff --git a/text_tests.lua b/text_tests.lua
index fa4c173..7f9b2e4 100644
--- a/text_tests.lua
+++ b/text_tests.lua
@@ -14,34 +14,6 @@ function test_initial_state()
   check_eq(Editor_state.screen_top1.pos, 1, 'F - test_initial_state/screen_top:pos')
 end
 
-function test_click_to_create_drawing()
-  io.write('\ntest_click_to_create_drawing')
-  App.screen.init{width=120, height=60}
-  Editor_state = edit.initialize_test_state()
-  Editor_state.lines = load_array{}
-  Text.redraw_all(Editor_state)
-  edit.draw(Editor_state)
-  edit.run_after_mouse_click(Editor_state, 8,Editor_state.top+8, 1)
-  -- cursor skips drawing to always remain on text
-  check_eq(#Editor_state.lines, 2, 'F - test_click_to_create_drawing/#lines')
-  check_eq(Editor_state.cursor1.line, 2, 'F - test_click_to_create_drawing/cursor')
-end
-
-function test_backspace_to_delete_drawing()
-  io.write('\ntest_backspace_to_delete_drawing')
-  -- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
-  App.screen.init{width=120, height=60}
-  Editor_state = edit.initialize_test_state()
-  Editor_state.lines = load_array{'```lines', '```', ''}
-  Text.redraw_all(Editor_state)
-  -- cursor is on text as always (outside tests this will get initialized correctly)
-  Editor_state.cursor1.line = 2
-  -- backspacing deletes the drawing
-  edit.run_after_keychord(Editor_state, 'backspace')
-  check_eq(#Editor_state.lines, 1, 'F - test_backspace_to_delete_drawing/#lines')
-  check_eq(Editor_state.cursor1.line, 1, 'F - test_backspace_to_delete_drawing/cursor')
-end
-
 function test_backspace_from_start_of_final_line()
   io.write('\ntest_backspace_from_start_of_final_line')
   -- display final line of text with cursor at start of it
@@ -994,36 +966,6 @@ function test_pagedown()
   App.screen.check(y, 'ghi', 'F - test_pagedown/screen:2')
 end
 
-function test_pagedown_skips_drawings()
-  io.write('\ntest_pagedown_skips_drawings')
-  -- some lines of text with a drawing intermixed
-  local drawing_width = 50
-  App.screen.init{width=Editor_state.left+drawing_width, height=80}
-  Editor_state = edit.initialize_test_state()
-  Editor_state.lines = load_array{'abc',               -- height 15
-                                  '```lines', '```',   -- height 25
-                                  'def',               -- height 15
-                                  'ghi'}               -- height 15
-  Text.redraw_all(Editor_state)
-  check_eq(Editor_state.lines[2].mode, 'drawing', 'F - test_pagedown_skips_drawings/baseline/lines')
-  Editor_state.cursor1 = {line=1, pos=1}
-  Editor_state.screen_top1 = {line=1, pos=1}
-  Editor_state.screen_bottom1 = {}
-  local drawing_height = Drawing_padding_height + drawing_width/2  -- default
-  -- initially the screen displays the first line and the drawing
-  -- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px
-  edit.draw(Editor_state)
-  local y = Editor_state.top
-  App.screen.check(y, 'abc', 'F - test_pagedown_skips_drawings/baseline/screen:1')
-  -- after pagedown the screen draws the drawing up top
-  -- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80px
-  edit.run_after_keychord(Editor_state, 'pagedown')
-  check_eq(Editor_state.screen_top1.line, 2, 'F - test_pagedown_skips_drawings/screen_top')
-  check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_skips_drawings/cursor')
-  y = Editor_state.top + drawing_height
-  App.screen.check(y, 'def', 'F - test_pagedown_skips_drawings/screen:1')
-end
-
 function test_pagedown_often_shows_start_of_wrapping_line()
   io.write('\ntest_pagedown_often_shows_start_of_wrapping_line')
   -- draw a few lines ending in part of a wrapping line
@@ -2022,7 +1964,7 @@ function test_search()
   io.write('\ntest_search')
   App.screen.init{width=120, height=60}
   Editor_state = edit.initialize_test_state()
-  Editor_state.lines = load_array{'```lines', '```', 'def', 'ghi', 'deg'}
+  Editor_state.lines = load_array{'abc', 'def', 'ghi', 'deg'}
   Text.redraw_all(Editor_state)
   Editor_state.cursor1 = {line=1, pos=1}
   Editor_state.screen_top1 = {line=1, pos=1}
diff --git a/undo.lua b/undo.lua
index d699211..912c949 100644
--- a/undo.lua
+++ b/undo.lua
@@ -50,8 +50,6 @@ function snapshot(State, s,e)
     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,
@@ -60,19 +58,7 @@ function snapshot(State, s,e)
   -- 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})
-    elseif line.mode == 'drawing' then
-      local points=deepcopy(line.points)
---?       print('copying', line.points, 'with', #line.points, 'points into', points)
-      local shapes=deepcopy(line.shapes)
---?       print('copying', line.shapes, 'with', #line.shapes, 'shapes into', shapes)
-      table.insert(event.lines, {mode='drawing', h=line.h, points=points, shapes=shapes, pending={}})
---?       table.insert(event.lines, {mode='drawing', h=line.h, points=deepcopy(line.points), shapes=deepcopy(line.shapes), pending={}})
-    else
-      print(line.mode)
-      assert(false)
-    end
+    table.insert(event.lines, {data=line.data})
   end
   return event
 end