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:
parent
5cce511550
commit
007b965b11
|
@ -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
|
||||||
|
|
28
drawing.lua
28
drawing.lua
|
@ -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]
|
||||||
|
|
15
edit.lua
15
edit.lua
|
@ -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
|
||||||
|
|
13
file.lua
13
file.lua
|
@ -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
|
||||||
|
|
3
geom.lua
3
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)
|
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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
10
select.lua
10
select.lua
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
45
text.lua
45
text.lua
|
@ -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
|
||||||
|
|
||||||
|
|
14
undo.lua
14
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.
|
-- 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
|
||||||
|
|
Loading…
Reference in New Issue