audit all asserts

Each one should provide a message that will show up within LÖVE. Stop
relying on nearby prints to the terminal.

I also found some unnecessary ones.

There is some potential here for performance regressions: the format()
calls will trigger whether or not the assertion fails, and cause
allocations. So far Lua's GC seems good enough to manage the load even
with Moby Dick, even in some situations that caused issues in the past
like undo.
This commit is contained in:
Kartik K. Agaram 2023-11-18 11:30:57 -08:00
parent 5cce511550
commit 007b965b11
15 changed files with 100 additions and 132 deletions

View File

@ -136,7 +136,7 @@ end
function move_candidate_to_front(s) function move_candidate_to_front(s)
local index = array.find(File_navigation.all_candidates, 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.remove(File_navigation.all_candidates, index)
table.insert(File_navigation.all_candidates, 1, s) table.insert(File_navigation.all_candidates, 1, s)
end end

View File

@ -33,7 +33,6 @@ function Drawing.draw(State, line_index, y)
local my = Drawing.coord(pmy-line_cache.starty, State.width) local my = Drawing.coord(pmy-line_cache.starty, State.width)
for _,shape in ipairs(line.shapes) do for _,shape in ipairs(line.shapes) do
assert(shape)
if geom.on_shape(mx,my, line, shape) then if geom.on_shape(mx,my, line, shape) then
App.color(Focus_stroke_color) App.color(Focus_stroke_color)
else else
@ -113,8 +112,7 @@ function Drawing.draw_shape(drawing, shape, top, left,right)
elseif shape.mode == 'deleted' then elseif shape.mode == 'deleted' then
-- ignore -- ignore
else else
print(shape.mode) assert(false, ('unknown drawing mode %s'):format(shape.mode))
assert(false)
end end
end end
@ -207,8 +205,7 @@ function Drawing.draw_pending_shape(drawing, top, left,right)
elseif shape.mode == 'name' then elseif shape.mode == 'name' then
-- nothing pending; changes are immediately committed -- nothing pending; changes are immediately committed
else else
print(shape.mode) assert(false, ('unknown drawing mode %s'):format(shape.mode))
assert(false)
end end
end end
@ -239,8 +236,7 @@ function Drawing.mouse_press(State, drawing_index, x,y, mouse_button)
elseif State.current_drawing_mode == 'name' then elseif State.current_drawing_mode == 'name' then
-- nothing -- nothing
else else
print(State.current_drawing_mode) assert(false, ('unknown drawing mode %s'):format(State.current_drawing_mode))
assert(false)
end end
end end
@ -255,7 +251,7 @@ function Drawing.update(State)
-- just skip this frame -- just skip this frame
return return
end 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 pmx, pmy = App.mouse_x(), App.mouse_y()
local mx = Drawing.coord(pmx-State.left, State.width) local mx = Drawing.coord(pmx-State.left, State.width)
local my = Drawing.coord(pmy-line_cache.starty, 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) table.insert(drawing.shapes, drawing.pending)
end end
elseif drawing.pending.mode == 'rectangle' then 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 if #drawing.pending.vertices == 2 then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) 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 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 -- too few points; draw nothing
end end
elseif drawing.pending.mode == 'square' then 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 if #drawing.pending.vertices == 2 then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) 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 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 elseif drawing.pending.mode == 'name' then
-- drop it -- drop it
else else
print(drawing.pending.mode) assert(false, ('unknown drawing mode %s'):format(drawing.pending.mode))
assert(false)
end end
State.lines.current_drawing.pending = {} State.lines.current_drawing.pending = {}
State.lines.current_drawing = nil State.lines.current_drawing = nil
@ -545,7 +540,7 @@ function Drawing.keychord_press(State, chord)
if Drawing.contains_point(shape, i) then if Drawing.contains_point(shape, i) then
if shape.mode == 'polygon' then if shape.mode == 'polygon' then
local idx = table.find(shape.vertices, i) local idx = table.find(shape.vertices, i)
assert(idx) assert(idx, 'point to delete is not in vertices')
table.remove(shape.vertices, idx) table.remove(shape.vertices, idx)
if #shape.vertices < 3 then if #shape.vertices < 3 then
shape.mode = 'deleted' 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 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) 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 for i,shape in ipairs(drawing.shapes) do
assert(shape)
if geom.on_shape(mx,my, drawing, shape) then if geom.on_shape(mx,my, drawing, shape) then
return drawing,line_cache,i,shape return drawing,line_cache,i,shape
end 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 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) 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 for i,point in ipairs(drawing.points) do
assert(point)
if Drawing.near(point, mx,my, State.width) then if Drawing.near(point, mx,my, State.width) then
return drawing_index,drawing,line_cache,i,point return drawing_index,drawing,line_cache,i,point
end end
@ -696,13 +689,12 @@ function Drawing.contains_point(shape, p)
elseif shape.mode == 'deleted' then elseif shape.mode == 'deleted' then
-- already done -- already done
else else
print(shape.mode) assert(false, ('unknown drawing mode %s'):format(shape.mode))
assert(false)
end end
end end
function Drawing.smoothen(shape) function Drawing.smoothen(shape)
assert(shape.mode == 'freehand') assert(shape.mode == 'freehand', 'can only smoothen freehand shapes')
for _=1,7 do for _=1,7 do
for i=2,#shape.points-1 do for i=2,#shape.points-1 do
local a = shape.points[i-1] local a = shape.points[i-1]

View File

@ -80,7 +80,7 @@ function edit.initialize_state(top, left, right, font_height, line_height) -- c
cursor_x = 0, cursor_x = 0,
cursor_y = 0, cursor_y = 0,
current_drawing_mode = 'line', current_drawing_mode = 'line', -- one of the available shape modes
previous_drawing_mode = nil, -- extra state for some ephemeral modes like moving/deleting/naming points previous_drawing_mode = nil, -- extra state for some ephemeral modes like moving/deleting/naming points
font_height = font_height, font_height = font_height,
@ -157,14 +157,8 @@ end
function edit.draw(State) function edit.draw(State)
State.button_handlers = {} State.button_handlers = {}
App.color(Text_color) App.color(Text_color)
if #State.lines ~= #State.line_cache then 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))
print(('line_cache is out of date; %d 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))
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
State.cursor_x = nil State.cursor_x = nil
State.cursor_y = nil State.cursor_y = nil
local y = State.top local y = State.top
@ -204,8 +198,7 @@ function edit.draw(State)
Drawing.draw(State, line_index, y) Drawing.draw(State, line_index, y)
y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom
else else
print(line.mode) assert(false, ('unknown line mode %s'):format(line.mode))
assert(false)
end end
end end
State.screen_bottom1 = screen_bottom1 State.screen_bottom1 = screen_bottom1

View File

@ -55,7 +55,7 @@ function load_drawing(infile_next_line)
local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}} local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
while true do while true do
local line = infile_next_line() local line = infile_next_line()
assert(line) assert(line, 'drawing in file is incomplete')
if line == '```' then break end if line == '```' then break end
local shape = json.decode(line) local shape = json.decode(line)
if shape.mode == 'freehand' then if shape.mode == 'freehand' then
@ -80,8 +80,7 @@ function load_drawing(infile_next_line)
elseif shape.mode == 'deleted' then elseif shape.mode == 'deleted' then
-- ignore -- ignore
else else
print(shape.mode) assert(false, ('unknown drawing mode %s'):format(shape.mode))
assert(false)
end end
table.insert(drawing.shapes, shape) table.insert(drawing.shapes, shape)
end end
@ -115,8 +114,7 @@ function store_drawing(outfile, drawing)
elseif shape.mode == 'deleted' then elseif shape.mode == 'deleted' then
-- ignore -- ignore
else else
print(shape.mode) assert(false, ('unknown drawing mode %s'):format(shape.mode))
assert(false)
end end
end end
outfile:write('```\n') outfile:write('```\n')
@ -152,7 +150,7 @@ function load_drawing_from_array(iter, a, i)
local line local line
while true do while true do
i, line = iter(a, i) i, line = iter(a, i)
assert(i) assert(i, 'drawing in array is incomplete')
--? print(i) --? print(i)
if line == '```' then break end if line == '```' then break end
local shape = json.decode(line) local shape = json.decode(line)
@ -178,8 +176,7 @@ function load_drawing_from_array(iter, a, i)
elseif shape.mode == 'deleted' then elseif shape.mode == 'deleted' then
-- ignore -- ignore
else else
print(shape.mode) assert(false, ('unknown drawing mode %s'):format(shape.mode))
assert(false)
end end
table.insert(drawing.shapes, shape) table.insert(drawing.shapes, shape)
end end

View File

@ -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) return geom.angle_between(center.x,center.y, x,y, shape.start_angle,shape.end_angle)
elseif shape.mode == 'deleted' then elseif shape.mode == 'deleted' then
else else
print(shape.mode) assert(false, ('unknown drawing mode %s'):format(shape.mode))
assert(false)
end end
end end

View File

@ -75,7 +75,7 @@ function table.shallowcopy(x)
end end
function log_browser.draw(State, hide_cursor) 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 mouse_line_index = log_browser.line_index(State, App.mouse_x(), App.mouse_y())
local y = State.top local y = State.top
for line_index = State.screen_top1.line,#State.lines do 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.line(xleft,sectiony, xleft+50-2,sectiony)
love.graphics.print(line.section_name, xleft+50,y) love.graphics.print(line.section_name, xleft+50,y)
love.graphics.line(xleft+50+App.width(line.section_name)+2,sectiony, xright,sectiony) 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 local sectiony = y+State.line_height-Section_border_padding_vertical
love.graphics.line(xleft,y, xleft,sectiony) love.graphics.line(xleft,y, xleft,sectiony)
love.graphics.line(xright,y, xright,sectiony) love.graphics.line(xright,y, xright,sectiony)

View File

@ -139,7 +139,7 @@ function rfind(s, pat, i, plain)
local rendpos = rs:find(rpat, ri, plain) local rendpos = rs:find(rpat, ri, plain)
if rendpos == nil then return nil end if rendpos == nil then return nil end
local endpos = #s - rendpos + 1 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 return endpos-#pat+1
end end

View File

@ -33,13 +33,13 @@ function Text.clip_selection(State, line_index, apos, bpos)
-- fully contained -- fully contained
return apos,bpos return apos,bpos
elseif a_ge then 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 return apos,maxp
elseif b_lt then 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 return minp,bpos
else 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 return minp,maxp
end end
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) State.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..State.lines[minl].data:sub(max_offset)
return return
end end
assert(minl < maxl) assert(minl < maxl, ('minl %d not < maxl %d'):format(minl, maxl))
local rhs = State.lines[maxl].data:sub(max_offset) local rhs = State.lines[maxl].data:sub(max_offset)
for i=maxl,minl+1,-1 do for i=maxl,minl+1,-1 do
table.remove(State.lines, i) table.remove(State.lines, i)
@ -154,7 +154,7 @@ function Text.selection(State)
if minl == maxl then if minl == maxl then
return State.lines[minl].data:sub(min_offset, max_offset-1) return State.lines[minl].data:sub(min_offset, max_offset-1)
end end
assert(minl < maxl) assert(minl < maxl, ('minl %d not < maxl %d'):format(minl, maxl))
local result = {State.lines[minl].data:sub(min_offset)} local result = {State.lines[minl].data:sub(min_offset)}
for i=minl+1,maxl-1 do for i=minl+1,maxl-1 do
if State.lines[i].mode == 'text' then if State.lines[i].mode == 'text' then

View File

@ -158,14 +158,8 @@ end
function edit.draw(State, hide_cursor, show_line_numbers) function edit.draw(State, hide_cursor, show_line_numbers)
State.button_handlers = {} State.button_handlers = {}
App.color(Text_color) App.color(Text_color)
if #State.lines ~= #State.line_cache then 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))
print(('line_cache is out of date; %d 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))
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
State.cursor_x = nil State.cursor_x = nil
State.cursor_y = nil State.cursor_y = nil
local y = State.top local y = State.top
@ -209,8 +203,7 @@ function edit.draw(State, hide_cursor, show_line_numbers)
Drawing.draw(State, line_index, y) Drawing.draw(State, line_index, y)
y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom
else else
print(line.mode) assert(false, ('unknown line mode %s'):format(line.mode))
assert(false)
end end
end end
State.screen_bottom1 = screen_bottom1 State.screen_bottom1 = screen_bottom1

View File

@ -63,7 +63,7 @@ function load_drawing(infile_next_line)
local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}} local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
while true do while true do
local line = infile_next_line() local line = infile_next_line()
assert(line) assert(line, 'drawing in file is incomplete')
if line == '```' then break end if line == '```' then break end
local shape = json.decode(line) local shape = json.decode(line)
if shape.mode == 'freehand' then if shape.mode == 'freehand' then
@ -88,8 +88,7 @@ function load_drawing(infile_next_line)
elseif shape.mode == 'deleted' then elseif shape.mode == 'deleted' then
-- ignore -- ignore
else else
print(shape.mode) assert(false, ('unknown drawing mode %s'):format(shape.mode))
assert(false)
end end
table.insert(drawing.shapes, shape) table.insert(drawing.shapes, shape)
end end
@ -123,8 +122,7 @@ function store_drawing(outfile, drawing)
elseif shape.mode == 'deleted' then elseif shape.mode == 'deleted' then
-- ignore -- ignore
else else
print(shape.mode) assert(false, ('unknown drawing mode %s'):format(shape.mode))
assert(false)
end end
end end
outfile:write('```\n') outfile:write('```\n')
@ -162,7 +160,7 @@ function load_drawing_from_array(iter, a, i)
local line local line
while true do while true do
i, line = iter(a, i) i, line = iter(a, i)
assert(i) assert(i, 'drawing in array is incomplete')
--? print(i) --? print(i)
if line == '```' then break end if line == '```' then break end
local shape = json.decode(line) local shape = json.decode(line)
@ -188,8 +186,7 @@ function load_drawing_from_array(iter, a, i)
elseif shape.mode == 'deleted' then elseif shape.mode == 'deleted' then
-- ignore -- ignore
else else
print(shape.mode) assert(false, ('unknown drawing mode %s'):format(shape.mode))
assert(false)
end end
table.insert(drawing.shapes, shape) table.insert(drawing.shapes, shape)
end end

View File

@ -33,13 +33,13 @@ function Text.clip_selection(State, line_index, apos, bpos)
-- fully contained -- fully contained
return apos,bpos return apos,bpos
elseif a_ge then 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 return apos,maxp
elseif b_lt then 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 return minp,bpos
else 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 return minp,maxp
end end
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) State.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..State.lines[minl].data:sub(max_offset)
return return
end end
assert(minl < maxl) assert(minl < maxl, ('minl %d not < maxl %d'):format(minl, maxl))
local rhs = State.lines[maxl].data:sub(max_offset) local rhs = State.lines[maxl].data:sub(max_offset)
for i=maxl,minl+1,-1 do for i=maxl,minl+1,-1 do
table.remove(State.lines, i) table.remove(State.lines, i)
@ -154,7 +154,7 @@ function Text.selection(State)
if minl == maxl then if minl == maxl then
return State.lines[minl].data:sub(min_offset, max_offset-1) return State.lines[minl].data:sub(min_offset, max_offset-1)
end end
assert(minl < maxl) assert(minl < maxl, ('minl %d not < maxl %d'):format(minl, maxl))
local result = {State.lines[minl].data:sub(min_offset)} local result = {State.lines[minl].data:sub(min_offset)}
for i=minl+1,maxl-1 do for i=minl+1,maxl-1 do
if State.lines[i].mode == 'text' then if State.lines[i].mode == 'text' then

View File

@ -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) love.graphics.print(line_index, State.left-Line_number_width*App.width('m')+10,y)
end end
initialize_color() 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 for i=1,#line_cache.screen_line_starting_pos do
local pos = line_cache.screen_line_starting_pos[i] local pos = line_cache.screen_line_starting_pos[i]
if pos < startpos then if pos < startpos then
@ -209,7 +209,7 @@ function Text.text_input(State, t)
end end
function Text.insert_at_cursor(State, t) 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) 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) 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) 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 Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
end end
Text.clear_screen_line_cache(State, State.cursor1.line) 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) schedule_save(State)
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)}) record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
elseif chord == 'delete' then elseif chord == 'delete' then
@ -452,7 +452,7 @@ function Text.pagedown(State)
end end
function Text.up(State) 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) --? 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) 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 if screen_line_starting_pos == 1 then
@ -478,7 +478,7 @@ function Text.up(State)
end end
else else
-- move up one screen line in current line -- 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_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 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) 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 end
function Text.down(State) 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) --? 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 if Text.cursor_at_final_screen_line(State) then
-- line is done, skip to next text line -- line is done, skip to next text line
--? print('cursor at final screen line of its 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 if State.cursor1.pos == 1 then
break break
end 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 if Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos-1, '%s') then
break break
end end
@ -605,15 +605,14 @@ end
function Text.match(s, pos, pat) function Text.match(s, pos, pat)
local start_offset = Text.offset(s, pos) local start_offset = Text.offset(s, pos)
assert(start_offset)
local end_offset = Text.offset(s, pos+1) 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) local curr = s:sub(start_offset, end_offset-1)
return curr:match(pat) return curr:match(pat)
end end
function Text.left(State) 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 if State.cursor1.pos > 1 then
State.cursor1.pos = State.cursor1.pos-1 State.cursor1.pos = State.cursor1.pos-1
else else
@ -646,7 +645,7 @@ function Text.right(State)
end end
function Text.right_without_scroll(State) 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 if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
State.cursor1.pos = State.cursor1.pos+1 State.cursor1.pos = State.cursor1.pos+1
else else
@ -671,7 +670,7 @@ function Text.pos_at_start_of_screen_line(State, loc1)
return spos,i return spos,i
end end
end end
assert(false) assert(false, ('invalid pos %d'):format(loc1.pos))
end end
function Text.pos_at_end_of_screen_line(State, loc1) 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 end
most_recent_final_pos = spos-1 most_recent_final_pos = spos-1
end end
assert(false) assert(false, ('invalid pos %d'):format(loc1.pos))
end end
function Text.cursor_at_final_screen_line(State) 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 end
-- hack: insert a text line at bottom of file if necessary -- hack: insert a text line at bottom of file if necessary
if State.cursor1.line > #State.lines then 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.lines, {mode='text', data=''})
table.insert(State.line_cache, {}) table.insert(State.line_cache, {})
end end
@ -742,8 +741,8 @@ function Text.snap_cursor_to_bottom_of_screen(State)
end end
y = y - h y = y - h
else else
assert(top2.line > 1) assert(top2.line > 1, 'tried to snap cursor to buttom of screen but failed')
assert(State.lines[top2.line-1].mode == 'drawing') 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 -- We currently can't draw partial drawings, so either skip it entirely
-- or not at all. -- or not at all.
local h = Drawing_padding_height + Drawing.pixels(State.lines[top2.line-1].h, State.width) 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) function Text.to_pos_on_line(State, line_index, mx, my)
local line = State.lines[line_index] local line = State.lines[line_index]
local line_cache = State.line_cache[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 -- duplicate some logic from Text.draw
local y = line_cache.starty local y = line_cache.starty
local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) 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 end
y = nexty y = nexty
end end
assert(false) assert(false, 'failed to map y pixel to line')
end end
function Text.screen_line_width(State, line_index, i) function Text.screen_line_width(State, line_index, i)
@ -864,7 +863,7 @@ function Text.nearest_cursor_pos(line, x, left)
leftpos = curr leftpos = curr
end end
end end
assert(false) assert(false, 'failed to map x pixel to pos')
end end
-- return the nearest index of line (in utf8 code points) which lies entirely -- 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 left = curr
end end
end end
assert(false) assert(false, 'failed to map x pixel to pos')
end end
function Text.x_after(s, pos) function Text.x_after(s, pos)
@ -926,7 +925,7 @@ function Text.to2(State, loc1)
break break
end end
end end
assert(result.screen_pos) assert(result.screen_pos, 'failed to convert schema-1 coordinate to schema-2')
return result return result
end end
@ -968,7 +967,7 @@ function Text.offset(s, pos1)
if result == nil then if result == nil then
print(pos1, #s, s) print(pos1, #s, s)
end end
assert(result) assert(result, "Text.offset returned nil; this is likely a failure to handle utf8")
return result return result
end end

View File

@ -36,11 +36,11 @@ end
-- Make copies of objects; the rest of the app may mutate them in place, but undo requires immutable histories. -- Make copies of objects; the rest of the app may mutate them in place, but undo requires immutable histories.
function snapshot(State, s,e) function snapshot(State, s,e)
-- Snapshot everything by default, but subset if requested. -- Snapshot everything by default, but subset if requested.
assert(s) assert(s, 'failed to snapshot operation for undo history')
if e == nil then if e == nil then
e = s e = s
end 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 < 1 then s = 1 end
if s > #State.lines then s = #State.lines end if s > #State.lines then s = #State.lines end
if e < 1 then e = 1 end if e < 1 then e = 1 end
@ -65,8 +65,7 @@ function snapshot(State, s,e)
elseif line.mode == 'drawing' then elseif line.mode == 'drawing' then
table.insert(event.lines, {mode='drawing', h=line.h, points=deepcopy(line.points), shapes=deepcopy(line.shapes), pending={}}) table.insert(event.lines, {mode='drawing', h=line.h, points=deepcopy(line.points), shapes=deepcopy(line.shapes), pending={}})
else else
print(line.mode) assert(false, ('unknown line mode %s'):format(line.mode))
assert(false)
end end
end end
return event return event
@ -80,22 +79,22 @@ function patch(lines, from, to)
--? lines[from.start_line] = to.lines[1] --? lines[from.start_line] = to.lines[1]
--? return --? return
--? end --? 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 for i=from.end_line,from.start_line,-1 do
table.remove(lines, i) table.remove(lines, i)
end 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 for i=1,#to.lines do
table.insert(lines, to.start_line+i-1, to.lines[i]) table.insert(lines, to.start_line+i-1, to.lines[i])
end end
end end
function patch_placeholders(line_cache, from, to) 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 for i=from.end_line,from.start_line,-1 do
table.remove(line_cache, i) table.remove(line_cache, i)
end 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 for i=1,#to.lines do
table.insert(line_cache, to.start_line+i-1, {}) table.insert(line_cache, to.start_line+i-1, {})
end end

View File

@ -12,7 +12,7 @@ function Text.draw(State, line_index, y, startpos)
-- wrap long lines -- wrap long lines
local final_screen_line_starting_pos = startpos -- track value to return local final_screen_line_starting_pos = startpos -- track value to return
Text.populate_screen_line_starting_pos(State, line_index) 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 for i=1,#line_cache.screen_line_starting_pos do
local pos = line_cache.screen_line_starting_pos[i] local pos = line_cache.screen_line_starting_pos[i]
if pos < startpos then if pos < startpos then
@ -135,7 +135,7 @@ function Text.text_input(State, t)
end end
function Text.insert_at_cursor(State, t) 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) 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) 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) Text.clear_screen_line_cache(State, State.cursor1.line)
@ -212,7 +212,7 @@ function Text.keychord_press(State, chord)
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 fragments to avoid memory leaks
end end
Text.clear_screen_line_cache(State, State.cursor1.line) 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) schedule_save(State)
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)}) record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
elseif chord == 'delete' then elseif chord == 'delete' then
@ -390,7 +390,7 @@ function Text.pagedown(State)
end end
function Text.up(State) 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) --? 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) 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 if screen_line_starting_pos == 1 then
@ -416,7 +416,7 @@ function Text.up(State)
end end
else else
-- move up one screen line in current line -- 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_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 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) local s = string.sub(State.lines[State.cursor1.line].data, new_screen_line_starting_byte_offset)
@ -433,9 +433,9 @@ function Text.up(State)
end end
function Text.down(State) 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) --? 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 if Text.cursor_at_final_screen_line(State) then
-- line is done, skip to next text line -- line is done, skip to next text line
--? print('cursor at final screen line of its line') --? print('cursor at final screen line of its line')
@ -509,7 +509,7 @@ function Text.word_left(State)
if State.cursor1.pos == 1 then if State.cursor1.pos == 1 then
break break
end 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 if Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos-1, '%s') then
break break
end end
@ -543,15 +543,14 @@ end
function Text.match(s, pos, pat) function Text.match(s, pos, pat)
local start_offset = Text.offset(s, pos) local start_offset = Text.offset(s, pos)
assert(start_offset)
local end_offset = Text.offset(s, pos+1) 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) local curr = s:sub(start_offset, end_offset-1)
return curr:match(pat) return curr:match(pat)
end end
function Text.left(State) 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 if State.cursor1.pos > 1 then
State.cursor1.pos = State.cursor1.pos-1 State.cursor1.pos = State.cursor1.pos-1
else else
@ -584,7 +583,7 @@ function Text.right(State)
end end
function Text.right_without_scroll(State) 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 if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
State.cursor1.pos = State.cursor1.pos+1 State.cursor1.pos = State.cursor1.pos+1
else else
@ -609,7 +608,7 @@ function Text.pos_at_start_of_screen_line(State, loc1)
return spos,i return spos,i
end end
end end
assert(false) assert(false, ('invalid pos %d'):format(loc1.pos))
end end
function Text.pos_at_end_of_screen_line(State, loc1) function Text.pos_at_end_of_screen_line(State, loc1)
@ -623,7 +622,7 @@ function Text.pos_at_end_of_screen_line(State, loc1)
end end
most_recent_final_pos = spos-1 most_recent_final_pos = spos-1
end end
assert(false) assert(false, ('invalid pos %d'):format(loc1.pos))
end end
function Text.cursor_at_final_screen_line(State) function Text.cursor_at_final_screen_line(State)
@ -648,7 +647,7 @@ function Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necess
end end
-- hack: insert a text line at bottom of file if necessary -- hack: insert a text line at bottom of file if necessary
if State.cursor1.line > #State.lines then 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.lines, {mode='text', data=''})
table.insert(State.line_cache, {}) table.insert(State.line_cache, {})
end end
@ -680,8 +679,8 @@ function Text.snap_cursor_to_bottom_of_screen(State)
end end
y = y - h y = y - h
else else
assert(top2.line > 1) assert(top2.line > 1, 'tried to snap cursor to buttom of screen but failed')
assert(State.lines[top2.line-1].mode == 'drawing') 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 -- We currently can't draw partial drawings, so either skip it entirely
-- or not at all. -- or not at all.
local h = Drawing_padding_height + Drawing.pixels(State.lines[top2.line-1].h, State.width) local h = Drawing_padding_height + Drawing.pixels(State.lines[top2.line-1].h, State.width)
@ -713,7 +712,7 @@ end
function Text.to_pos_on_line(State, line_index, mx, my) function Text.to_pos_on_line(State, line_index, mx, my)
local line = State.lines[line_index] local line = State.lines[line_index]
local line_cache = State.line_cache[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 -- duplicate some logic from Text.draw
local y = line_cache.starty local y = line_cache.starty
local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos)
@ -736,7 +735,7 @@ function Text.to_pos_on_line(State, line_index, mx, my)
end end
y = nexty y = nexty
end end
assert(false) assert(false, 'failed to map y pixel to line')
end end
function Text.screen_line_width(State, line_index, i) function Text.screen_line_width(State, line_index, i)
@ -802,7 +801,7 @@ function Text.nearest_cursor_pos(line, x, left)
leftpos = curr leftpos = curr
end end
end end
assert(false) assert(false, 'failed to map x pixel to pos')
end end
-- return the nearest index of line (in utf8 code points) which lies entirely -- return the nearest index of line (in utf8 code points) which lies entirely
@ -833,7 +832,7 @@ function Text.nearest_pos_less_than(line, x)
left = curr left = curr
end end
end end
assert(false) assert(false, 'failed to map x pixel to pos')
end end
function Text.x_after(s, pos) function Text.x_after(s, pos)
@ -864,7 +863,7 @@ function Text.to2(State, loc1)
break break
end end
end end
assert(result.screen_pos) assert(result.screen_pos, 'failed to convert schema-1 coordinate to schema-2')
return result return result
end end
@ -906,7 +905,7 @@ function Text.offset(s, pos1)
if result == nil then if result == nil then
print(pos1, #s, s) print(pos1, #s, s)
end end
assert(result) assert(result, "Text.offset returned nil; this is likely a failure to handle utf8")
return result return result
end end

View File

@ -36,11 +36,11 @@ end
-- Make copies of objects; the rest of the app may mutate them in place, but undo requires immutable histories. -- Make copies of objects; the rest of the app may mutate them in place, but undo requires immutable histories.
function snapshot(State, s,e) function snapshot(State, s,e)
-- Snapshot everything by default, but subset if requested. -- Snapshot everything by default, but subset if requested.
assert(s) assert(s, 'failed to snapshot operation for undo history')
if e == nil then if e == nil then
e = s e = s
end 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 < 1 then s = 1 end
if s > #State.lines then s = #State.lines end if s > #State.lines then s = #State.lines end
if e < 1 then e = 1 end if e < 1 then e = 1 end
@ -66,7 +66,7 @@ function snapshot(State, s,e)
table.insert(event.lines, {mode='drawing', h=line.h, points=deepcopy(line.points), shapes=deepcopy(line.shapes), pending={}}) table.insert(event.lines, {mode='drawing', h=line.h, points=deepcopy(line.points), shapes=deepcopy(line.shapes), pending={}})
else else
print(line.mode) print(line.mode)
assert(false) assert(false, ('unknown line mode %s'):format(line.mode))
end end
end end
return event return event
@ -80,22 +80,22 @@ function patch(lines, from, to)
--? lines[from.start_line] = to.lines[1] --? lines[from.start_line] = to.lines[1]
--? return --? return
--? end --? 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 for i=from.end_line,from.start_line,-1 do
table.remove(lines, i) table.remove(lines, i)
end 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 for i=1,#to.lines do
table.insert(lines, to.start_line+i-1, to.lines[i]) table.insert(lines, to.start_line+i-1, to.lines[i])
end end
end end
function patch_placeholders(line_cache, from, to) 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 for i=from.end_line,from.start_line,-1 do
table.remove(line_cache, i) table.remove(line_cache, i)
end 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 for i=1,#to.lines do
table.insert(line_cache, to.start_line+i-1, {}) table.insert(line_cache, to.start_line+i-1, {})
end end