about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2023-11-18 12:21:30 -0800
committerKartik K. Agaram <vc@akkartik.com>2023-11-18 12:21:30 -0800
commitde66b015bcfbf4a995b25cc74c5f95d2362760a8 (patch)
tree2fb180163f064f2a2a2713012b26dcb9a1d74273
parenta8dc0ee2be9ebbc55f40bbc12b591a6b2d4a0cfe (diff)
parent007b965b11b681550ee2e2244a2f53e64e88697d (diff)
downloadtext.love-de66b015bcfbf4a995b25cc74c5f95d2362760a8.tar.gz
Merge lines.love
-rw-r--r--commands.lua2
-rw-r--r--drawing.lua28
-rw-r--r--edit.lua10
-rw-r--r--geom.lua3
-rw-r--r--log_browser.lua4
-rw-r--r--search.lua2
-rw-r--r--select.lua10
-rw-r--r--source_edit.lua13
-rw-r--r--source_file.lua13
-rw-r--r--source_select.lua10
-rw-r--r--source_text.lua45
-rw-r--r--source_undo.lua15
-rw-r--r--text.lua29
-rw-r--r--undo.lua12
14 files changed, 84 insertions, 112 deletions
diff --git a/commands.lua b/commands.lua
index a76bae5..1f8e304 100644
--- a/commands.lua
+++ b/commands.lua
@@ -136,7 +136,7 @@ end
 
 function move_candidate_to_front(s)
   local index = array.find(File_navigation.all_candidates, s)
-  assert(index)
+  assert(index, 'file missing from manifest')
   table.remove(File_navigation.all_candidates, index)
   table.insert(File_navigation.all_candidates, 1, s)
 end
diff --git a/drawing.lua b/drawing.lua
index a98f5b2..615ea62 100644
--- a/drawing.lua
+++ b/drawing.lua
@@ -33,7 +33,6 @@ function Drawing.draw(State, line_index, y)
   local my = Drawing.coord(pmy-line_cache.starty, State.width)
 
   for _,shape in ipairs(line.shapes) do
-    assert(shape)
     if geom.on_shape(mx,my, line, shape) then
       App.color(Focus_stroke_color)
     else
@@ -113,8 +112,7 @@ function Drawing.draw_shape(drawing, shape, top, left,right)
   elseif shape.mode == 'deleted' then
     -- ignore
   else
-    print(shape.mode)
-    assert(false)
+    assert(false, ('unknown drawing mode %s'):format(shape.mode))
   end
 end
 
@@ -207,8 +205,7 @@ function Drawing.draw_pending_shape(drawing, top, left,right)
   elseif shape.mode == 'name' then
     -- nothing pending; changes are immediately committed
   else
-    print(shape.mode)
-    assert(false)
+    assert(false, ('unknown drawing mode %s'):format(shape.mode))
   end
 end
 
@@ -239,8 +236,7 @@ function Drawing.mouse_press(State, drawing_index, x,y, mouse_button)
   elseif State.current_drawing_mode == 'name' then
     -- nothing
   else
-    print(State.current_drawing_mode)
-    assert(false)
+    assert(false, ('unknown drawing mode %s'):format(State.current_drawing_mode))
   end
 end
 
@@ -255,7 +251,7 @@ function Drawing.update(State)
     -- just skip this frame
     return
   end
-  assert(drawing.mode == 'drawing')
+  assert(drawing.mode == 'drawing', 'Drawing.update: line is not a drawing')
   local pmx, pmy = App.mouse_x(), App.mouse_y()
   local mx = Drawing.coord(pmx-State.left, State.width)
   local my = Drawing.coord(pmy-line_cache.starty, State.width)
@@ -342,7 +338,7 @@ function Drawing.mouse_release(State, x,y, mouse_button)
           table.insert(drawing.shapes, drawing.pending)
         end
       elseif drawing.pending.mode == 'rectangle' then
-        assert(#drawing.pending.vertices <= 2)
+        assert(#drawing.pending.vertices <= 2, 'Drawing.mouse_release: rectangle has too many pending vertices')
         if #drawing.pending.vertices == 2 then
           local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
           if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
@@ -357,7 +353,7 @@ function Drawing.mouse_release(State, x,y, mouse_button)
           -- too few points; draw nothing
         end
       elseif drawing.pending.mode == 'square' then
-        assert(#drawing.pending.vertices <= 2)
+        assert(#drawing.pending.vertices <= 2, 'Drawing.mouse_release: square has too many pending vertices')
         if #drawing.pending.vertices == 2 then
           local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
           if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
@@ -386,8 +382,7 @@ function Drawing.mouse_release(State, x,y, mouse_button)
       elseif drawing.pending.mode == 'name' then
         -- drop it
       else
-        print(drawing.pending.mode)
-        assert(false)
+        assert(false, ('unknown drawing mode %s'):format(drawing.pending.mode))
       end
       State.lines.current_drawing.pending = {}
       State.lines.current_drawing = nil
@@ -545,7 +540,7 @@ function Drawing.keychord_press(State, chord)
         if Drawing.contains_point(shape, i) then
           if shape.mode == 'polygon' then
             local idx = table.find(shape.vertices, i)
-            assert(idx)
+            assert(idx, 'point to delete is not in vertices')
             table.remove(shape.vertices, idx)
             if #shape.vertices < 3 then
               shape.mode = 'deleted'
@@ -641,7 +636,6 @@ function Drawing.select_shape_at_mouse(State)
       if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
         local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
         for i,shape in ipairs(drawing.shapes) do
-          assert(shape)
           if geom.on_shape(mx,my, drawing, shape) then
             return drawing,line_cache,i,shape
           end
@@ -659,7 +653,6 @@ function Drawing.select_point_at_mouse(State)
       if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
         local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
         for i,point in ipairs(drawing.points) do
-          assert(point)
           if Drawing.near(point, mx,my, State.width) then
             return drawing_index,drawing,line_cache,i,point
           end
@@ -696,13 +689,12 @@ function Drawing.contains_point(shape, p)
   elseif shape.mode == 'deleted' then
     -- already done
   else
-    print(shape.mode)
-    assert(false)
+    assert(false, ('unknown drawing mode %s'):format(shape.mode))
   end
 end
 
 function Drawing.smoothen(shape)
-  assert(shape.mode == 'freehand')
+  assert(shape.mode == 'freehand', 'can only smoothen freehand shapes')
   for _=1,7 do
     for i=2,#shape.points-1 do
       local a = shape.points[i-1]
diff --git a/edit.lua b/edit.lua
index 56f3f10..bfd2709 100644
--- a/edit.lua
+++ b/edit.lua
@@ -100,14 +100,8 @@ end
 -- return y drawn until
 function edit.draw(State)
   App.color(Text_color)
-  if #State.lines ~= #State.line_cache then
-    print(('line_cache is out of date; %d when it should be %d'):format(#State.line_cache, #State.lines))
-    assert(false)
-  end
-  if not Text.le1(State.screen_top1, State.cursor1) then
-    print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
-    assert(false)
-  end
+  assert(#State.lines == #State.line_cache, ('line_cache is out of date; %d elements when it should be %d'):format(#State.line_cache, #State.lines))
+  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))
   State.cursor_x = nil
   State.cursor_y = nil
   local y = State.top
diff --git a/geom.lua b/geom.lua
index 891e98d..ea007b7 100644
--- a/geom.lua
+++ b/geom.lua
@@ -38,8 +38,7 @@ function geom.on_shape(x,y, drawing, shape)
     return geom.angle_between(center.x,center.y, x,y, shape.start_angle,shape.end_angle)
   elseif shape.mode == 'deleted' then
   else
-    print(shape.mode)
-    assert(false)
+    assert(false, ('unknown drawing mode %s'):format(shape.mode))
   end
 end
 
diff --git a/log_browser.lua b/log_browser.lua
index 6b71da6..b6195b9 100644
--- a/log_browser.lua
+++ b/log_browser.lua
@@ -75,7 +75,7 @@ function table.shallowcopy(x)
 end
 
 function log_browser.draw(State, hide_cursor)
-  assert(#State.lines == #State.line_cache)
+  assert(#State.lines == #State.line_cache, ('line_cache is out of date; %d elements when it should be %d'):format(#State.line_cache, #State.lines))
   local mouse_line_index = log_browser.line_index(State, App.mouse_x(), App.mouse_y())
   local y = State.top
   for line_index = State.screen_top1.line,#State.lines do
@@ -95,7 +95,7 @@ function log_browser.draw(State, hide_cursor)
           love.graphics.line(xleft,sectiony, xleft+50-2,sectiony)
           love.graphics.print(line.section_name, xleft+50,y)
           love.graphics.line(xleft+50+App.width(line.section_name)+2,sectiony, xright,sectiony)
-        else assert(line.section_end)
+        else assert(line.section_end, "log line has a section name, but it's neither the start nor end of a section")
           local sectiony = y+State.line_height-Section_border_padding_vertical
           love.graphics.line(xleft,y, xleft,sectiony)
           love.graphics.line(xright,y, xright,sectiony)
diff --git a/search.lua b/search.lua
index fe57ac9..67b82b3 100644
--- a/search.lua
+++ b/search.lua
@@ -139,7 +139,7 @@ function rfind(s, pat, i, plain)
   local rendpos = rs:find(rpat, ri, plain)
   if rendpos == nil then return nil end
   local endpos = #s - rendpos + 1
-  assert (endpos >= #pat)
+  assert (endpos >= #pat, ('rfind: endpos %d should be >= #pat %d at this point'):format(endpos, #pat))
   return endpos-#pat+1
 end
 
diff --git a/select.lua b/select.lua
index a24b828..083d7ab 100644
--- a/select.lua
+++ b/select.lua
@@ -33,13 +33,13 @@ function Text.clip_selection(State, line_index, apos, bpos)
     -- fully contained
     return apos,bpos
   elseif a_ge then
-    assert(maxl == line_index)
+    assert(maxl == line_index, ('maxl %d not equal to line_index %d'):format(maxl, line_index))
     return apos,maxp
   elseif b_lt then
-    assert(minl == line_index)
+    assert(minl == line_index, ('minl %d not equal to line_index %d'):format(minl, line_index))
     return minp,bpos
   else
-    assert(minl == maxl and minl == line_index)
+    assert(minl == maxl and minl == line_index, ('minl %d, maxl %d and line_index %d are not all equal'):format(minl, maxl, line_index))
     return minp,maxp
   end
 end
@@ -125,7 +125,7 @@ function Text.delete_selection_without_undo(State)
     State.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..State.lines[minl].data:sub(max_offset)
     return
   end
-  assert(minl < maxl)
+  assert(minl < maxl, ('minl %d not < maxl %d'):format(minl, maxl))
   local rhs = State.lines[maxl].data:sub(max_offset)
   for i=maxl,minl+1,-1 do
     table.remove(State.lines, i)
@@ -152,7 +152,7 @@ function Text.selection(State)
   if minl == maxl then
     return State.lines[minl].data:sub(min_offset, max_offset-1)
   end
-  assert(minl < maxl)
+  assert(minl < maxl, ('minl %d not < maxl %d'):format(minl, maxl))
   local result = {State.lines[minl].data:sub(min_offset)}
   for i=minl+1,maxl-1 do
     table.insert(result, State.lines[i].data)
diff --git a/source_edit.lua b/source_edit.lua
index 12c2bae..6259ebe 100644
--- a/source_edit.lua
+++ b/source_edit.lua
@@ -158,14 +158,8 @@ end
 function edit.draw(State, hide_cursor, show_line_numbers)
   State.button_handlers = {}
   App.color(Text_color)
-  if #State.lines ~= #State.line_cache then
-    print(('line_cache is out of date; %d when it should be %d'):format(#State.line_cache, #State.lines))
-    assert(false)
-  end
-  if not Text.le1(State.screen_top1, State.cursor1) then
-    print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
-    assert(false)
-  end
+  assert(#State.lines == #State.line_cache, ('line_cache is out of date; %d elements when it should be %d'):format(#State.line_cache, #State.lines))
+  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))
   State.cursor_x = nil
   State.cursor_y = nil
   local y = State.top
@@ -209,8 +203,7 @@ function edit.draw(State, hide_cursor, show_line_numbers)
       Drawing.draw(State, line_index, y)
       y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom
     else
-      print(line.mode)
-      assert(false)
+      assert(false, ('unknown line mode %s'):format(line.mode))
     end
   end
   State.screen_bottom1 = screen_bottom1
diff --git a/source_file.lua b/source_file.lua
index dbb6f95..6d04f6b 100644
--- a/source_file.lua
+++ b/source_file.lua
@@ -63,7 +63,7 @@ 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)
+    assert(line, 'drawing in file is incomplete')
     if line == '```' then break end
     local shape = json.decode(line)
     if shape.mode == 'freehand' then
@@ -88,8 +88,7 @@ function load_drawing(infile_next_line)
     elseif shape.mode == 'deleted' then
       -- ignore
     else
-      print(shape.mode)
-      assert(false)
+      assert(false, ('unknown drawing mode %s'):format(shape.mode))
     end
     table.insert(drawing.shapes, shape)
   end
@@ -123,8 +122,7 @@ function store_drawing(outfile, drawing)
     elseif shape.mode == 'deleted' then
       -- ignore
     else
-      print(shape.mode)
-      assert(false)
+      assert(false, ('unknown drawing mode %s'):format(shape.mode))
     end
   end
   outfile:write('```\n')
@@ -162,7 +160,7 @@ function load_drawing_from_array(iter, a, i)
   local line
   while true do
     i, line = iter(a, i)
-    assert(i)
+    assert(i, 'drawing in array is incomplete')
 --?     print(i)
     if line == '```' then break end
     local shape = json.decode(line)
@@ -188,8 +186,7 @@ function load_drawing_from_array(iter, a, i)
     elseif shape.mode == 'deleted' then
       -- ignore
     else
-      print(shape.mode)
-      assert(false)
+      assert(false, ('unknown drawing mode %s'):format(shape.mode))
     end
     table.insert(drawing.shapes, shape)
   end
diff --git a/source_select.lua b/source_select.lua
index 9b2a278..9ede1da 100644
--- a/source_select.lua
+++ b/source_select.lua
@@ -33,13 +33,13 @@ function Text.clip_selection(State, line_index, apos, bpos)
     -- fully contained
     return apos,bpos
   elseif a_ge then
-    assert(maxl == line_index)
+    assert(maxl == line_index, ('maxl %d not equal to line_index %d'):format(maxl, line_index))
     return apos,maxp
   elseif b_lt then
-    assert(minl == line_index)
+    assert(minl == line_index, ('minl %d not equal to line_index %d'):format(minl, line_index))
     return minp,bpos
   else
-    assert(minl == maxl and minl == line_index)
+    assert(minl == maxl and minl == line_index, ('minl %d, maxl %d and line_index %d are not all equal'):format(minl, maxl, line_index))
     return minp,maxp
   end
 end
@@ -127,7 +127,7 @@ function Text.delete_selection_without_undo(State)
     State.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..State.lines[minl].data:sub(max_offset)
     return
   end
-  assert(minl < maxl)
+  assert(minl < maxl, ('minl %d not < maxl %d'):format(minl, maxl))
   local rhs = State.lines[maxl].data:sub(max_offset)
   for i=maxl,minl+1,-1 do
     table.remove(State.lines, i)
@@ -154,7 +154,7 @@ function Text.selection(State)
   if minl == maxl then
     return State.lines[minl].data:sub(min_offset, max_offset-1)
   end
-  assert(minl < maxl)
+  assert(minl < maxl, ('minl %d not < maxl %d'):format(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
diff --git a/source_text.lua b/source_text.lua
index fb5123a..8b34d52 100644
--- a/source_text.lua
+++ b/source_text.lua
@@ -17,7 +17,7 @@ function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_number
     love.graphics.print(line_index, State.left-Line_number_width*App.width('m')+10,y)
   end
   initialize_color()
-  assert(#line_cache.screen_line_starting_pos >= 1)
+  assert(#line_cache.screen_line_starting_pos >= 1, 'line cache missing screen line info')
   for i=1,#line_cache.screen_line_starting_pos do
     local pos = line_cache.screen_line_starting_pos[i]
     if pos < startpos then
@@ -209,7 +209,7 @@ function Text.text_input(State, t)
 end
 
 function Text.insert_at_cursor(State, t)
-  assert(State.lines[State.cursor1.line].mode == 'text')
+  assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
   local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
   State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].data, byte_offset)
   Text.clear_screen_line_cache(State, State.cursor1.line)
@@ -286,7 +286,7 @@ function Text.keychord_press(State, chord)
       Text.redraw_all(State)  -- if we're scrolling, reclaim all fragments to avoid memory leaks
     end
     Text.clear_screen_line_cache(State, State.cursor1.line)
-    assert(Text.le1(State.screen_top1, State.cursor1))
+    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))
     schedule_save(State)
     record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
   elseif chord == 'delete' then
@@ -452,7 +452,7 @@ function Text.pagedown(State)
 end
 
 function Text.up(State)
-  assert(State.lines[State.cursor1.line].mode == 'text')
+  assert(State.lines[State.cursor1.line].mode == 'text', 'line is not 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
@@ -478,7 +478,7 @@ function Text.up(State)
     end
   else
     -- move up one screen line in current line
-    assert(screen_line_index > 1)
+    assert(screen_line_index > 1, 'bumped up against top screen line in line')
     local new_screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos[screen_line_index-1]
     local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, new_screen_line_starting_pos)
     local s = string.sub(State.lines[State.cursor1.line].data, new_screen_line_starting_byte_offset)
@@ -495,9 +495,9 @@ function Text.up(State)
 end
 
 function Text.down(State)
-  assert(State.lines[State.cursor1.line].mode == 'text')
+  assert(State.lines[State.cursor1.line].mode == 'text', 'line is not 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)
-  assert(State.cursor1.pos)
+  assert(State.cursor1.pos, 'cursor has no 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')
@@ -571,7 +571,7 @@ function Text.word_left(State)
     if State.cursor1.pos == 1 then
       break
     end
-    assert(State.cursor1.pos > 1)
+    assert(State.cursor1.pos > 1, 'bumped up against start of line')
     if Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos-1, '%s') then
       break
     end
@@ -605,15 +605,14 @@ end
 
 function Text.match(s, pos, pat)
   local start_offset = Text.offset(s, pos)
-  assert(start_offset)
   local end_offset = Text.offset(s, pos+1)
-  assert(end_offset > start_offset)
+  assert(end_offset > start_offset, ('end_offset %d not > start_offset %d'):format(end_offset, start_offset))
   local curr = s:sub(start_offset, end_offset-1)
   return curr:match(pat)
 end
 
 function Text.left(State)
-  assert(State.lines[State.cursor1.line].mode == 'text')
+  assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
   if State.cursor1.pos > 1 then
     State.cursor1.pos = State.cursor1.pos-1
   else
@@ -646,7 +645,7 @@ function Text.right(State)
 end
 
 function Text.right_without_scroll(State)
-  assert(State.lines[State.cursor1.line].mode == 'text')
+  assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
   if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
     State.cursor1.pos = State.cursor1.pos+1
   else
@@ -671,7 +670,7 @@ function Text.pos_at_start_of_screen_line(State, loc1)
       return spos,i
     end
   end
-  assert(false)
+  assert(false, ('invalid pos %d'):format(loc1.pos))
 end
 
 function Text.pos_at_end_of_screen_line(State, loc1)
@@ -685,7 +684,7 @@ function Text.pos_at_end_of_screen_line(State, loc1)
     end
     most_recent_final_pos = spos-1
   end
-  assert(false)
+  assert(false, ('invalid pos %d'):format(loc1.pos))
 end
 
 function Text.cursor_at_final_screen_line(State)
@@ -710,7 +709,7 @@ function Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necess
   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)
+    assert(State.cursor1.line == #State.lines+1, 'tried to ensure bottom line of file is text, but failed')
     table.insert(State.lines, {mode='text', data=''})
     table.insert(State.line_cache, {})
   end
@@ -742,8 +741,8 @@ function Text.snap_cursor_to_bottom_of_screen(State)
       end
       y = y - h
     else
-      assert(top2.line > 1)
-      assert(State.lines[top2.line-1].mode == 'drawing')
+      assert(top2.line > 1, 'tried to snap cursor to buttom of screen but failed')
+      assert(State.lines[top2.line-1].mode == 'drawing', "expected a drawing but it's not")
       -- 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)
@@ -775,7 +774,7 @@ end
 function Text.to_pos_on_line(State, line_index, mx, my)
   local line = State.lines[line_index]
   local line_cache = State.line_cache[line_index]
-  assert(my >= line_cache.starty)
+  assert(my >= line_cache.starty, 'failed to map y pixel to line')
   -- duplicate some logic from Text.draw
   local y = line_cache.starty
   local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos)
@@ -798,7 +797,7 @@ function Text.to_pos_on_line(State, line_index, mx, my)
     end
     y = nexty
   end
-  assert(false)
+  assert(false, 'failed to map y pixel to line')
 end
 
 function Text.screen_line_width(State, line_index, i)
@@ -864,7 +863,7 @@ function Text.nearest_cursor_pos(line, x, left)
       leftpos = curr
     end
   end
-  assert(false)
+  assert(false, 'failed to map x pixel to pos')
 end
 
 -- return the nearest index of line (in utf8 code points) which lies entirely
@@ -895,7 +894,7 @@ function Text.nearest_pos_less_than(line, x)
       left = curr
     end
   end
-  assert(false)
+  assert(false, 'failed to map x pixel to pos')
 end
 
 function Text.x_after(s, pos)
@@ -926,7 +925,7 @@ function Text.to2(State, loc1)
       break
     end
   end
-  assert(result.screen_pos)
+  assert(result.screen_pos, 'failed to convert schema-1 coordinate to schema-2')
   return result
 end
 
@@ -968,7 +967,7 @@ function Text.offset(s, pos1)
   if result == nil then
     print(pos1, #s, s)
   end
-  assert(result)
+  assert(result, "Text.offset returned nil; this is likely a failure to handle utf8")
   return result
 end
 
diff --git a/source_undo.lua b/source_undo.lua
index d3d5f0f..e5dea93 100644
--- a/source_undo.lua
+++ b/source_undo.lua
@@ -36,11 +36,11 @@ end
 -- Make copies of objects; the rest of the app may mutate them in place, but undo requires immutable histories.
 function snapshot(State, s,e)
   -- Snapshot everything by default, but subset if requested.
-  assert(s)
+  assert(s, 'failed to snapshot operation for undo history')
   if e == nil then
     e = s
   end
-  assert(#State.lines > 0)
+  assert(#State.lines > 0, 'failed to snapshot operation for undo history')
   if s < 1 then s = 1 end
   if s > #State.lines then s = #State.lines end
   if e < 1 then e = 1 end
@@ -65,8 +65,7 @@ function snapshot(State, s,e)
     elseif line.mode == 'drawing' then
       table.insert(event.lines, {mode='drawing', h=line.h, points=deepcopy(line.points), shapes=deepcopy(line.shapes), pending={}})
     else
-      print(line.mode)
-      assert(false)
+      assert(false, ('unknown line mode %s'):format(line.mode))
     end
   end
   return event
@@ -80,22 +79,22 @@ function patch(lines, from, to)
 --?     lines[from.start_line] = to.lines[1]
 --?     return
 --?   end
-  assert(from.start_line == to.start_line)
+  assert(from.start_line == to.start_line, 'failed to patch undo operation')
   for i=from.end_line,from.start_line,-1 do
     table.remove(lines, i)
   end
-  assert(#to.lines == to.end_line-to.start_line+1)
+  assert(#to.lines == to.end_line-to.start_line+1, 'failed to patch undo operation')
   for i=1,#to.lines do
     table.insert(lines, to.start_line+i-1, to.lines[i])
   end
 end
 
 function patch_placeholders(line_cache, from, to)
-  assert(from.start_line == to.start_line)
+  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)
+  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
diff --git a/text.lua b/text.lua
index 84339d4..f25e232 100644
--- a/text.lua
+++ b/text.lua
@@ -12,7 +12,7 @@ function Text.draw(State, line_index, y, startpos)
   -- wrap long lines
   local final_screen_line_starting_pos = startpos  -- track value to return
   Text.populate_screen_line_starting_pos(State, line_index)
-  assert(#line_cache.screen_line_starting_pos >= 1)
+  assert(#line_cache.screen_line_starting_pos >= 1, 'line cache missing screen line info')
   for i=1,#line_cache.screen_line_starting_pos do
     local pos = line_cache.screen_line_starting_pos[i]
     if pos < startpos then
@@ -205,7 +205,7 @@ function Text.keychord_press(State, chord)
       Text.redraw_all(State)  -- if we're scrolling, reclaim all fragments to avoid memory leaks
     end
     Text.clear_screen_line_cache(State, State.cursor1.line)
-    assert(Text.le1(State.screen_top1, State.cursor1))
+    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))
     schedule_save(State)
     record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
   elseif chord == 'delete' then
@@ -398,7 +398,7 @@ function Text.up(State)
     end
   else
     -- move up one screen line in current line
-    assert(screen_line_index > 1)
+    assert(screen_line_index > 1, 'bumped up against top screen line in line')
     local new_screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos[screen_line_index-1]
     local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, new_screen_line_starting_pos)
     local s = string.sub(State.lines[State.cursor1.line].data, new_screen_line_starting_byte_offset)
@@ -416,7 +416,7 @@ end
 
 function Text.down(State)
 --?   print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
-  assert(State.cursor1.pos)
+  assert(State.cursor1.pos, 'cursor has no 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')
@@ -484,7 +484,7 @@ function Text.word_left(State)
     if State.cursor1.pos == 1 then
       break
     end
-    assert(State.cursor1.pos > 1)
+    assert(State.cursor1.pos > 1, 'bumped up against start of line')
     if Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos-1, '%s') then
       break
     end
@@ -518,9 +518,8 @@ end
 
 function Text.match(s, pos, pat)
   local start_offset = Text.offset(s, pos)
-  assert(start_offset)
   local end_offset = Text.offset(s, pos+1)
-  assert(end_offset > start_offset)
+  assert(end_offset > start_offset, ('end_offset %d not > start_offset %d'):format(end_offset, start_offset))
   local curr = s:sub(start_offset, end_offset-1)
   return curr:match(pat)
 end
@@ -567,7 +566,7 @@ function Text.pos_at_start_of_screen_line(State, loc1)
       return spos,i
     end
   end
-  assert(false)
+  assert(false, ('invalid pos %d'):format(loc1.pos))
 end
 
 function Text.pos_at_end_of_screen_line(State, loc1)
@@ -581,7 +580,7 @@ function Text.pos_at_end_of_screen_line(State, loc1)
     end
     most_recent_final_pos = spos-1
   end
-  assert(false)
+  assert(false, ('invalid pos %d'):format(loc1.pos))
 end
 
 function Text.cursor_at_final_screen_line(State)
@@ -639,7 +638,7 @@ end
 function Text.to_pos_on_line(State, line_index, mx, my)
   local line = State.lines[line_index]
   local line_cache = State.line_cache[line_index]
-  assert(my >= line_cache.starty)
+  assert(my >= line_cache.starty, 'failed to map y pixel to line')
   -- duplicate some logic from Text.draw
   local y = line_cache.starty
   local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos)
@@ -662,7 +661,7 @@ function Text.to_pos_on_line(State, line_index, mx, my)
     end
     y = nexty
   end
-  assert(false)
+  assert(false, 'failed to map y pixel to line')
 end
 
 function Text.screen_line_width(State, line_index, i)
@@ -728,7 +727,7 @@ function Text.nearest_cursor_pos(line, x, left)
       leftpos = curr
     end
   end
-  assert(false)
+  assert(false, 'failed to map x pixel to pos')
 end
 
 -- return the nearest index of line (in utf8 code points) which lies entirely
@@ -759,7 +758,7 @@ function Text.nearest_pos_less_than(line, x)
       left = curr
     end
   end
-  assert(false)
+  assert(false, 'failed to map x pixel to pos')
 end
 
 function Text.x_after(s, pos)
@@ -787,7 +786,7 @@ function Text.to2(State, loc1)
       break
     end
   end
-  assert(result.screen_pos)
+  assert(result.screen_pos, 'failed to convert schema-1 coordinate to schema-2')
   return result
 end
 
@@ -829,7 +828,7 @@ function Text.offset(s, pos1)
   if result == nil then
     print(pos1, #s, s)
   end
-  assert(result)
+  assert(result, "Text.offset returned nil; this is likely a failure to handle utf8")
   return result
 end
 
diff --git a/undo.lua b/undo.lua
index 873feea..a41ba38 100644
--- a/undo.lua
+++ b/undo.lua
@@ -36,11 +36,11 @@ end
 -- Make copies of objects; the rest of the app may mutate them in place, but undo requires immutable histories.
 function snapshot(State, s,e)
   -- Snapshot everything by default, but subset if requested.
-  assert(s)
+  assert(s, 'failed to snapshot operation for undo history')
   if e == nil then
     e = s
   end
-  assert(#State.lines > 0)
+  assert(#State.lines > 0, 'failed to snapshot operation for undo history')
   if s < 1 then s = 1 end
   if s > #State.lines then s = #State.lines end
   if e < 1 then e = 1 end
@@ -71,22 +71,22 @@ function patch(lines, from, to)
 --?     lines[from.start_line] = to.lines[1]
 --?     return
 --?   end
-  assert(from.start_line == to.start_line)
+  assert(from.start_line == to.start_line, 'failed to patch undo operation')
   for i=from.end_line,from.start_line,-1 do
     table.remove(lines, i)
   end
-  assert(#to.lines == to.end_line-to.start_line+1)
+  assert(#to.lines == to.end_line-to.start_line+1, 'failed to patch undo operation')
   for i=1,#to.lines do
     table.insert(lines, to.start_line+i-1, to.lines[i])
   end
 end
 
 function patch_placeholders(line_cache, from, to)
-  assert(from.start_line == to.start_line)
+  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)
+  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