Compare commits

..

No commits in common. "2eb71d7a804bc862e2b5304cf2d574c5c882fff7" and "12f5adacf072298e2f09a98fa58b4b29f4ee9e39" have entirely different histories.

15 changed files with 425 additions and 379 deletions

View File

@ -73,11 +73,6 @@ found anything amiss: http://akkartik.name/contact
* No clipping yet for drawings. In particular, circles/squares/rectangles and
point labels can overflow a drawing.
* If you ever see a crash when clicking on the mouse, it might be because a
mouse press and release need to happen in separate frames. Try pressing and
releasing more slowly and let me know if that helps or not. This is klunky,
sorry.
* Touchpads can drag the mouse pointer using a light touch or a heavy click.
On Linux, drags using the light touch get interrupted when a key is pressed.
You'll have to press down to drag.

View File

@ -130,8 +130,6 @@ function App.run_tests()
end
end
table.sort(sorted_names)
--? App.initialize_for_test() -- debug: run a single test at a time like these 2 lines
--? test_click_below_all_lines()
for _,name in ipairs(sorted_names) do
App.initialize_for_test()
--? print('=== '..name)
@ -406,10 +404,9 @@ end
-- prepend file/line/test
function prepend_debug_info_to_test_failure(test_name, err)
local err_without_line_number = err:gsub('^[^:]*:[^:]*: ', '')
local stack_trace = debug.traceback('', --[[stack frame]]5) -- most likely to be useful, but set to 0 for a complete stack trace
local stack_trace = debug.traceback('', --[[stack frame]]5)
local file_and_line_number = stack_trace:gsub('stack traceback:\n', ''):gsub(': .*', '')
local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number
-- uncomment this line for a complete stack trace
--? local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number..'\t\t'..stack_trace:gsub('\n', '\n\t\t')
table.insert(Test_errors, full_error)
end

View File

@ -6,15 +6,16 @@ require 'drawing_tests'
-- into 256 parts.
function Drawing.draw(State, line_index, y)
local line = State.lines[line_index]
local line_cache = State.line_cache[line_index]
line_cache.starty = y
local pmx,pmy = App.mouse_x(), App.mouse_y()
local starty = Text.starty(State, line_index)
if pmx < State.right and pmy > starty and pmy < starty+Drawing.pixels(line.h, State.width) then
if pmx < State.right and pmy > line_cache.starty and pmy < line_cache.starty+Drawing.pixels(line.h, State.width) then
App.color(Icon_color)
love.graphics.rectangle('line', State.left,starty, State.width,Drawing.pixels(line.h, State.width))
love.graphics.rectangle('line', State.left,line_cache.starty, State.width,Drawing.pixels(line.h, State.width))
if icon[State.current_drawing_mode] then
icon[State.current_drawing_mode](State.right-22, starty+4)
icon[State.current_drawing_mode](State.right-22, line_cache.starty+4)
else
icon[State.previous_drawing_mode](State.right-22, starty+4)
icon[State.previous_drawing_mode](State.right-22, line_cache.starty+4)
end
if App.mouse_down(1) and love.keyboard.isDown('h') then
@ -29,7 +30,7 @@ function Drawing.draw(State, line_index, y)
end
local mx = Drawing.coord(pmx-State.left, State.width)
local my = Drawing.coord(pmy-starty, State.width)
local my = Drawing.coord(pmy-line_cache.starty, State.width)
for _,shape in ipairs(line.shapes) do
if geom.on_shape(mx,my, line, shape) then
@ -37,11 +38,11 @@ function Drawing.draw(State, line_index, y)
else
App.color(Stroke_color)
end
Drawing.draw_shape(line, shape, starty, State.left,State.right)
Drawing.draw_shape(line, shape, line_cache.starty, State.left,State.right)
end
local function px(x) return Drawing.pixels(x, State.width)+State.left end
local function py(y) return Drawing.pixels(y, State.width)+starty end
local function py(y) return Drawing.pixels(y, State.width)+line_cache.starty end
for i,p in ipairs(line.points) do
if p.deleted == nil then
if Drawing.near(p, mx,my, State.width) then
@ -70,7 +71,7 @@ function Drawing.draw(State, line_index, y)
end
end
App.color(Current_stroke_color)
Drawing.draw_pending_shape(line, starty, State.left,State.right)
Drawing.draw_pending_shape(line, line_cache.starty, State.left,State.right)
end
function Drawing.draw_shape(drawing, shape, top, left,right)
@ -208,24 +209,17 @@ function Drawing.draw_pending_shape(drawing, top, left,right)
end
end
function Drawing.in_current_drawing(State, x,y, left,right)
return Drawing.in_drawing(State, State.lines.current_drawing_index, x,y, left,right)
end
function Drawing.in_drawing(State, line_index, x,y, left,right)
assert(State.lines[line_index].mode == 'drawing')
local starty = Text.starty(State, line_index)
if starty == nil then return false end -- outside current page
local drawing = State.lines[line_index]
function Drawing.in_drawing(drawing, line_cache, x,y, left,right)
if line_cache.starty == nil then return false end -- outside current page
local width = right-left
return y >= starty and y < starty + Drawing.pixels(drawing.h, width) and x >= left and x < right
return y >= line_cache.starty and y < line_cache.starty + Drawing.pixels(drawing.h, width) and x >= left and x < right
end
function Drawing.mouse_press(State, drawing_index, x,y, mouse_button)
local drawing = State.lines[drawing_index]
local starty = Text.starty(State, drawing_index)
local line_cache = State.line_cache[drawing_index]
local cx = Drawing.coord(x-State.left, State.width)
local cy = Drawing.coord(y-starty, State.width)
local cy = Drawing.coord(y-line_cache.starty, State.width)
if State.current_drawing_mode == 'freehand' then
drawing.pending = {mode=State.current_drawing_mode, points={{x=cx, y=cy}}}
elseif State.current_drawing_mode == 'line' or State.current_drawing_mode == 'manhattan' then
@ -250,8 +244,8 @@ end
function Drawing.update(State)
if State.lines.current_drawing == nil then return end
local drawing = State.lines.current_drawing
local starty = Text.starty(State, State.lines.current_drawing_index)
if starty == nil then
local line_cache = State.line_cache[State.lines.current_drawing_index]
if line_cache.starty == nil then
-- some event cleared starty just this frame
-- draw in this frame will soon set starty
-- just skip this frame
@ -260,9 +254,9 @@ function Drawing.update(State)
assert(drawing.mode == 'drawing', 'Drawing.update: line is not a drawing')
local pmx, pmy = App.mouse_x(), App.mouse_y()
local mx = Drawing.coord(pmx-State.left, State.width)
local my = Drawing.coord(pmy-starty, State.width)
local my = Drawing.coord(pmy-line_cache.starty, State.width)
if App.mouse_down(1) then
if Drawing.in_current_drawing(State, pmx,pmy, State.left,State.right) then
if Drawing.in_drawing(drawing, line_cache, pmx,pmy, State.left,State.right) then
if drawing.pending.mode == 'freehand' then
table.insert(drawing.pending.points, {x=mx, y=my})
elseif drawing.pending.mode == 'move' then
@ -272,7 +266,7 @@ function Drawing.update(State)
end
end
elseif State.current_drawing_mode == 'move' then
if Drawing.in_current_drawing(State, pmx, pmy, State.left,State.right) then
if Drawing.in_drawing(drawing, line_cache, pmx, pmy, State.left,State.right) then
drawing.pending.target_point.x = mx
drawing.pending.target_point.y = my
Drawing.relax_constraints(drawing, drawing.pending.target_point_index)
@ -310,7 +304,7 @@ function Drawing.mouse_release(State, x,y, mouse_button)
end
elseif State.lines.current_drawing then
local drawing = State.lines.current_drawing
local starty = Text.starty(State, State.lines.current_drawing_index)
local line_cache = State.line_cache[State.lines.current_drawing_index]
if drawing.pending then
if drawing.pending.mode == nil then
-- nothing pending
@ -319,14 +313,14 @@ function Drawing.mouse_release(State, x,y, mouse_button)
Drawing.smoothen(drawing.pending)
table.insert(drawing.shapes, drawing.pending)
elseif drawing.pending.mode == 'line' then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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
drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
table.insert(drawing.shapes, drawing.pending)
end
elseif drawing.pending.mode == 'manhattan' then
local p1 = drawing.points[drawing.pending.p1]
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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 math.abs(mx-p1.x) > math.abs(my-p1.y) then
drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx, p1.y, State.width)
@ -334,11 +328,11 @@ function Drawing.mouse_release(State, x,y, mouse_button)
drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, p1.x, my, State.width)
end
local p2 = drawing.points[drawing.pending.p2]
App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), starty+Drawing.pixels(p2.y, State.width))
App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), line_cache.starty+Drawing.pixels(p2.y, State.width))
table.insert(drawing.shapes, drawing.pending)
end
elseif drawing.pending.mode == 'polygon' then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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
table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, mx,my, State.width))
table.insert(drawing.shapes, drawing.pending)
@ -346,7 +340,7 @@ function Drawing.mouse_release(State, x,y, mouse_button)
elseif drawing.pending.mode == 'rectangle' then
assert(#drawing.pending.vertices <= 2, 'Drawing.mouse_release: rectangle has too many pending vertices')
if #drawing.pending.vertices == 2 then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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
local first = drawing.points[drawing.pending.vertices[1]]
local second = drawing.points[drawing.pending.vertices[2]]
@ -361,7 +355,7 @@ function Drawing.mouse_release(State, x,y, mouse_button)
elseif drawing.pending.mode == 'square' then
assert(#drawing.pending.vertices <= 2, 'Drawing.mouse_release: square has too many pending vertices')
if #drawing.pending.vertices == 2 then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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
local first = drawing.points[drawing.pending.vertices[1]]
local second = drawing.points[drawing.pending.vertices[2]]
@ -372,14 +366,14 @@ function Drawing.mouse_release(State, x,y, mouse_button)
end
end
elseif drawing.pending.mode == 'circle' then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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
local center = drawing.points[drawing.pending.center]
drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my))
table.insert(drawing.shapes, drawing.pending)
end
elseif drawing.pending.mode == 'arc' then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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
local center = drawing.points[drawing.pending.center]
drawing.pending.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, drawing.pending.end_angle)
@ -483,15 +477,13 @@ function Drawing.keychord_press(State, chord)
end
drawing.pending.mode = 'square'
elseif App.mouse_down(1) and chord == 'p' and State.current_drawing_mode == 'polygon' then
local drawing_index,drawing = Drawing.current_drawing(State)
local starty = Text.starty(State, drawing_index)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)
local _,drawing,line_cache = Drawing.current_drawing(State)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
table.insert(drawing.pending.vertices, j)
elseif App.mouse_down(1) and chord == 'p' and (State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square') then
local drawing_index,drawing = Drawing.current_drawing(State)
local starty = Text.starty(State, drawing_index)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)
local _,drawing,line_cache = Drawing.current_drawing(State)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
while #drawing.pending.vertices >= 2 do
table.remove(drawing.pending.vertices)
@ -500,10 +492,9 @@ function Drawing.keychord_press(State, chord)
elseif chord == 'C-o' and not App.mouse_down(1) then
State.current_drawing_mode = 'circle'
elseif App.mouse_down(1) and chord == 'a' and State.current_drawing_mode == 'circle' then
local drawing_index,drawing = Drawing.current_drawing(State)
local starty = Text.starty(State, drawing_index)
local _,drawing,line_cache = Drawing.current_drawing(State)
drawing.pending.mode = 'arc'
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
local center = drawing.points[drawing.pending.center]
drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my))
drawing.pending.start_angle = geom.angle(center.x,center.y, mx,my)
@ -519,7 +510,7 @@ function Drawing.keychord_press(State, chord)
end
drawing.pending.mode = 'circle'
elseif chord == 'C-u' and not App.mouse_down(1) then
local drawing_index,drawing,_,i,p = Drawing.select_point_at_mouse(State)
local drawing_index,drawing,line_cache,i,p = Drawing.select_point_at_mouse(State)
if drawing then
if State.previous_drawing_mode == nil then
State.previous_drawing_mode = State.current_drawing_mode
@ -530,7 +521,7 @@ function Drawing.keychord_press(State, chord)
State.lines.current_drawing = drawing
end
elseif chord == 'C-n' and not App.mouse_down(1) then
local drawing_index,drawing,_,point_index,p = Drawing.select_point_at_mouse(State)
local drawing_index,drawing,line_cache,point_index,p = Drawing.select_point_at_mouse(State)
if drawing then
if State.previous_drawing_mode == nil then
-- don't clobber
@ -628,8 +619,9 @@ function Drawing.current_drawing(State)
local x, y = App.mouse_x(), App.mouse_y()
for drawing_index,drawing in ipairs(State.lines) do
if drawing.mode == 'drawing' then
if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
return drawing_index,drawing
local line_cache = State.line_cache[drawing_index]
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
return drawing_index,drawing,line_cache
end
end
end
@ -640,12 +632,12 @@ function Drawing.select_shape_at_mouse(State)
for drawing_index,drawing in ipairs(State.lines) do
if drawing.mode == 'drawing' then
local x, y = App.mouse_x(), App.mouse_y()
local starty = Text.starty(State, drawing_index)
if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
local line_cache = State.line_cache[drawing_index]
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
for i,shape in ipairs(drawing.shapes) do
if geom.on_shape(mx,my, drawing, shape) then
return drawing,starty,i,shape
return drawing,line_cache,i,shape
end
end
end
@ -657,12 +649,12 @@ function Drawing.select_point_at_mouse(State)
for drawing_index,drawing in ipairs(State.lines) do
if drawing.mode == 'drawing' then
local x, y = App.mouse_x(), App.mouse_y()
local starty = Text.starty(State, drawing_index)
if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
local line_cache = State.line_cache[drawing_index]
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
for i,point in ipairs(drawing.points) do
if Drawing.near(point, mx,my, State.width) then
return drawing_index,drawing,starty,i,point
return drawing_index,drawing,line_cache,i,point
end
end
end
@ -674,7 +666,8 @@ function Drawing.select_drawing_at_mouse(State)
for drawing_index,drawing in ipairs(State.lines) do
if drawing.mode == 'drawing' then
local x, y = App.mouse_x(), App.mouse_y()
if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
local line_cache = State.line_cache[drawing_index]
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
return drawing
end
end

View File

@ -3,7 +3,7 @@
-- of specific shapes. In particular, no tests of freehand drawings.
function test_creating_drawing_saves()
App.screen.init{width=800, height=600}
App.screen.init{width=120, height=60}
Editor_state = edit.initialize_test_state()
Editor_state.filename = 'foo'
Editor_state.lines = load_array{}
@ -32,7 +32,7 @@ function test_draw_line()
edit.draw(Editor_state)
check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- draw a line
@ -77,7 +77,7 @@ function test_draw_horizontal_line()
edit.draw(Editor_state)
check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- draw a line that is more horizontal than vertical
@ -105,7 +105,7 @@ function test_draw_circle()
edit.draw(Editor_state)
check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- draw a circle
@ -134,7 +134,7 @@ function test_cancel_stroke()
edit.draw(Editor_state)
check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- start drawing a line
@ -172,7 +172,7 @@ function test_draw_circle_mid_stroke()
edit.draw(Editor_state)
check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- draw a circle
@ -200,7 +200,7 @@ function test_draw_arc()
edit.draw(Editor_state)
check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- draw an arc
@ -231,7 +231,7 @@ function test_draw_polygon()
check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode')
check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- first point
@ -269,7 +269,7 @@ function test_draw_rectangle()
check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode')
check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- first point
@ -313,7 +313,7 @@ function test_draw_rectangle_intermediate()
check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode')
check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- first point
@ -349,7 +349,7 @@ function test_draw_square()
check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode')
check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- first point

View File

@ -31,6 +31,7 @@ function edit.initialize_state(top, left, right, font, font_height, line_height)
-- string data,
-- a drawing is a table with:
-- mode = 'drawing'
-- a (y) coord in pixels (updated while painting screen),
-- a (h)eight,
-- an array of points, and
-- an array of shapes
@ -51,6 +52,7 @@ function edit.initialize_state(top, left, right, font, font_height, line_height)
-- rendering wrapped text lines needs some additional short-lived data per line:
-- startpos, the index of data the line starts rendering from, can only be >1 for topmost line on screen
-- starty, the y coord in pixels the line starts rendering from
-- fragments: snippets of the line guaranteed to not straddle screen lines
-- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line
line_cache = {},
@ -64,10 +66,9 @@ function edit.initialize_state(top, left, right, font, font_height, line_height)
--
-- Make sure these coordinates are never aliased, so that changing one causes
-- action at a distance.
--
-- On lines that are drawings, pos will be nil.
screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen
cursor1 = {line=1, pos=1}, -- position of cursor; must be on a text line
cursor1 = {line=1, pos=1}, -- position of cursor
screen_bottom1 = {line=1, pos=1}, -- position of start of screen line at bottom of screen
selection1 = {},
-- some extra state to compute selection between mouse press and release
@ -165,11 +166,13 @@ function edit.draw(State)
State.cursor_x = nil
State.cursor_y = nil
local y = State.top
local screen_bottom1 = {line=nil, pos=nil}
--? print('== draw')
for line_index = State.screen_top1.line,#State.lines do
local line = State.lines[line_index]
--? print('draw:', y, line_index, line)
if y + State.line_height > App.screen.height then break end
screen_bottom1.line = line_index
if line.mode == 'text' then
--? print('text.draw', y, line_index)
local startpos = 1
@ -192,7 +195,7 @@ function edit.draw(State)
end,
})
end
y = Text.draw(State, line_index, y, startpos)
y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos)
--? print('=> y', y)
elseif line.mode == 'drawing' then
y = y+Drawing_padding_top
@ -202,6 +205,7 @@ function edit.draw(State)
assert(false, ('unknown line mode %s'):format(line.mode))
end
end
State.screen_bottom1 = screen_bottom1
if State.search_term then
Text.draw_search_bar(State)
end
@ -275,7 +279,8 @@ function edit.mouse_press(State, x,y, mouse_button)
return
end
elseif line.mode == 'drawing' then
if Drawing.in_drawing(State, line_index, x, y, State.left,State.right) then
local line_cache = State.line_cache[line_index]
if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then
State.lines.current_drawing_index = line_index
State.lines.current_drawing = line
Drawing.before = snapshot(State, line_index)
@ -289,7 +294,10 @@ function edit.mouse_press(State, x,y, mouse_button)
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = Text.final_text_loc_on_screen(State)
State.selection1 = {
line=State.screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1),
}
end
function edit.mouse_release(State, x,y, mouse_button)
@ -327,7 +335,7 @@ function edit.mouse_release(State, x,y, mouse_button)
end
-- still here? mouse release is below all screen lines
State.cursor1 = Text.final_text_loc_on_screen(State)
State.cursor1.line, State.cursor1.pos = State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1)
edit.clean_up_mouse_press(State)
--? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos))
end
@ -355,7 +363,7 @@ function edit.mouse_wheel_move(State, dx,dy)
Text.up(State)
end
elseif dy < 0 then
State.cursor1 = Text.screen_bottom1(State)
State.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
edit.put_cursor_on_next_text_line(State)
for i=1,math.floor(-dy) do
Text.down(State)
@ -393,6 +401,7 @@ function edit.keychord_press(State, chord, key)
Text.delete_selection(State, State.left, State.right)
end
if State.search_term then
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
if chord == 'escape' then
State.search_term = nil
State.cursor1 = State.search_backup.cursor
@ -497,6 +506,7 @@ function edit.keychord_press(State, chord, key)
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
-- dispatch to drawing or text
elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then
-- DON'T reset line_cache.starty here
local drawing_index, drawing = Drawing.current_drawing(State)
if drawing_index then
local before = snapshot(State, drawing_index)
@ -533,6 +543,7 @@ function edit.keychord_press(State, chord, key)
end
schedule_save(State)
else
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
Text.keychord_press(State, chord)
end
end
@ -587,7 +598,6 @@ end
function edit.run_after_mouse_click(State, x,y, mouse_button)
App.fake_mouse_press(x,y, mouse_button)
edit.mouse_press(State, x,y, mouse_button)
edit.draw(State)
App.fake_mouse_release(x,y, mouse_button)
edit.mouse_release(State, x,y, mouse_button)
App.screen.contents = {}

View File

@ -1,8 +1,8 @@
function draw_help_without_mouse_pressed(State, drawing_index)
local drawing = State.lines[drawing_index]
local starty = Text.starty(State, drawing_index)
local line_cache = State.line_cache[drawing_index]
App.color(Help_color)
local y = starty+10
local y = line_cache.starty+10
love.graphics.print("Things you can do:", State.left+30,y)
y = y + State.line_height
love.graphics.print("* Press the mouse button to start drawing a "..current_shape(State), State.left+30,y)
@ -48,14 +48,14 @@ function draw_help_without_mouse_pressed(State, drawing_index)
love.graphics.print("Press 'esc' now to hide this message", State.left+30,y)
y = y + State.line_height
App.color(Help_background_color)
love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty))
love.graphics.rectangle('fill', State.left,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty))
end
function draw_help_with_mouse_pressed(State, drawing_index)
local drawing = State.lines[drawing_index]
local starty = Text.starty(State, drawing_index)
local line_cache = State.line_cache[drawing_index]
App.color(Help_color)
local y = starty+10
local y = line_cache.starty+10
love.graphics.print("You're currently drawing a "..current_shape(State, drawing.pending), State.left+30,y)
y = y + State.line_height
love.graphics.print('Things you can do now:', State.left+30,y)
@ -129,7 +129,7 @@ function draw_help_with_mouse_pressed(State, drawing_index)
y = y + State.line_height
end
App.color(Help_background_color)
love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty))
love.graphics.rectangle('fill', State.left,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty))
end
function current_shape(State, shape)

View File

@ -62,8 +62,7 @@ function Text.search_next(State)
State.screen_top1.line = State.search_backup.screen_top.line
State.screen_top1.pos = State.search_backup.screen_top.pos
end
local screen_bottom1 = Text.screen_bottom1(State)
if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(screen_bottom1, State.cursor1) then
if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(State.screen_bottom1, State.cursor1) then
State.screen_top1.line = State.cursor1.line
local pos = Text.pos_at_start_of_screen_line(State, State.cursor1)
State.screen_top1.pos = pos
@ -116,8 +115,7 @@ function Text.search_previous(State)
State.screen_top1.line = State.search_backup.screen_top.line
State.screen_top1.pos = State.search_backup.screen_top.pos
end
local screen_bottom1 = Text.screen_bottom1(State)
if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(screen_bottom1, State.cursor1) then
if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(State.screen_bottom1, State.cursor1) then
State.screen_top1.line = State.cursor1.line
local pos = Text.pos_at_start_of_screen_line(State, State.cursor1)
State.screen_top1.pos = pos

View File

@ -69,7 +69,7 @@ end
function Text.mouse_pos(State)
local x,y = App.mouse_x(), App.mouse_y()
if y < State.top then
if y < State.line_cache[State.screen_top1.line].starty then
return State.screen_top1.line, State.screen_top1.pos
end
for line_index,line in ipairs(State.lines) do
@ -79,8 +79,7 @@ function Text.mouse_pos(State)
end
end
end
local screen_bottom1 = Text.screen_bottom1(State)
return screen_bottom1.line, Text.pos_at_end_of_screen_line(State, screen_bottom1)
return State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1)
end
function Text.cut_selection(State)

View File

@ -307,6 +307,7 @@ function source.mouse_press(x,y, mouse_button)
return
end
log_browser.mouse_press(Log_browser_state, x,y, mouse_button)
for _,line_cache in ipairs(Editor_state.line_cache) do line_cache.starty = nil end -- just in case we scroll
end
end

View File

@ -33,6 +33,7 @@ function edit.initialize_state(top, left, right, font, font_height, line_height)
-- string data,
-- a drawing is a table with:
-- mode = 'drawing'
-- a (y) coord in pixels (updated while painting screen),
-- a (h)eight,
-- an array of points, and
-- an array of shapes
@ -53,6 +54,7 @@ function edit.initialize_state(top, left, right, font, font_height, line_height)
-- rendering wrapped text lines needs some additional short-lived data per line:
-- startpos, the index of data the line starts rendering from, can only be >1 for topmost line on screen
-- starty, the y coord in pixels the line starts rendering from
-- fragments: snippets of the line guaranteed to not straddle screen lines
-- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line
line_cache = {},
@ -66,10 +68,9 @@ function edit.initialize_state(top, left, right, font, font_height, line_height)
--
-- Make sure these coordinates are never aliased, so that changing one causes
-- action at a distance.
--
-- On lines that are drawings, pos will be nil.
screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen
cursor1 = {line=1, pos=1}, -- position of cursor; must be on a text line
cursor1 = {line=1, pos=1}, -- position of cursor
screen_bottom1 = {line=1, pos=1}, -- position of start of screen line at bottom of screen
selection1 = {},
-- some extra state to compute selection between mouse press and release
@ -164,11 +165,13 @@ function edit.draw(State, hide_cursor, show_line_numbers)
State.cursor_x = nil
State.cursor_y = nil
local y = State.top
local screen_bottom1 = {line=nil, pos=nil}
--? print('== draw')
for line_index = State.screen_top1.line,#State.lines do
local line = State.lines[line_index]
--? print('draw:', y, line_index, line)
if y + State.line_height > App.screen.height then break end
screen_bottom1.line = line_index
if line.mode == 'text' then
--? print('text.draw', y, line_index)
local startpos = 1
@ -195,7 +198,7 @@ function edit.draw(State, hide_cursor, show_line_numbers)
end,
})
end
y = Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers)
y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers)
--? print('=> y', y)
elseif line.mode == 'drawing' then
y = y+Drawing_padding_top
@ -205,6 +208,7 @@ function edit.draw(State, hide_cursor, show_line_numbers)
assert(false, ('unknown line mode %s'):format(line.mode))
end
end
State.screen_bottom1 = screen_bottom1
if State.search_term then
Text.draw_search_bar(State)
end
@ -277,7 +281,8 @@ function edit.mouse_press(State, x,y, mouse_button)
return
end
elseif line.mode == 'drawing' then
if Drawing.in_drawing(State, line_index, x, y, State.left,State.right) then
local line_cache = State.line_cache[line_index]
if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then
State.lines.current_drawing_index = line_index
State.lines.current_drawing = line
Drawing.before = snapshot(State, line_index)
@ -291,7 +296,10 @@ function edit.mouse_press(State, x,y, mouse_button)
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = Text.final_text_loc_on_screen(State)
State.selection1 = {
line=State.screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1),
}
end
function edit.mouse_release(State, x,y, mouse_button)
@ -329,7 +337,7 @@ function edit.mouse_release(State, x,y, mouse_button)
end
-- still here? mouse release is below all screen lines
State.cursor1 = Text.final_text_loc_on_screen(State)
State.cursor1.line, State.cursor1.pos = State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1)
edit.clean_up_mouse_press(State)
--? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos))
end
@ -357,7 +365,7 @@ function edit.mouse_wheel_move(State, dx,dy)
Text.up(State)
end
elseif dy < 0 then
State.cursor1 = Text.screen_bottom1(State)
State.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
edit.put_cursor_on_next_text_line(State)
for i=1,math.floor(-dy) do
Text.down(State)
@ -391,10 +399,11 @@ function edit.keychord_press(State, chord, key)
-- printable character created using shift key => delete selection
-- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)
(not App.shift_down() or utf8.len(key) == 1) and
chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(key) then
chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(chord) then
Text.delete_selection(State, State.left, State.right)
end
if State.search_term then
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
if chord == 'escape' then
State.search_term = nil
State.cursor1 = State.search_backup.cursor
@ -499,6 +508,7 @@ function edit.keychord_press(State, chord, key)
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
-- dispatch to drawing or text
elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then
-- DON'T reset line_cache.starty here
local drawing_index, drawing = Drawing.current_drawing(State)
if drawing_index then
local before = snapshot(State, drawing_index)
@ -535,6 +545,7 @@ function edit.keychord_press(State, chord, key)
end
schedule_save(State)
else
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
Text.keychord_press(State, chord)
end
end
@ -589,7 +600,6 @@ end
function edit.run_after_mouse_click(State, x,y, mouse_button)
App.fake_mouse_press(x,y, mouse_button)
edit.mouse_press(State, x,y, mouse_button)
edit.draw(State)
App.fake_mouse_release(x,y, mouse_button)
edit.mouse_release(State, x,y, mouse_button)
App.screen.contents = {}

View File

@ -69,7 +69,7 @@ end
function Text.mouse_pos(State)
local x,y = App.mouse_x(), App.mouse_y()
if y < State.top then
if y < State.line_cache[State.screen_top1.line].starty then
return State.screen_top1.line, State.screen_top1.pos
end
for line_index,line in ipairs(State.lines) do
@ -79,8 +79,7 @@ function Text.mouse_pos(State)
end
end
end
local screen_bottom1 = Text.screen_bottom1(State)
return screen_bottom1.line, Text.pos_at_end_of_screen_line(State, screen_bottom1)
return State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1)
end
function Text.cut_selection(State)

View File

@ -2,12 +2,14 @@
Text = {}
-- draw a line starting from startpos to screen at y between State.left and State.right
-- return y for the next line
-- return y for the next line, and position of start of final screen line drawn
function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers)
local line = State.lines[line_index]
local line_cache = State.line_cache[line_index]
line_cache.starty = y
line_cache.startpos = startpos
-- wrap long lines
local final_screen_line_starting_pos = startpos -- track value to return
Text.populate_screen_line_starting_pos(State, line_index)
Text.populate_link_offsets(State, line_index)
if show_line_numbers then
@ -22,6 +24,7 @@ function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_number
-- render nothing
--? print('skipping', screen_line)
else
final_screen_line_starting_pos = pos
local screen_line = Text.screen_line(line, line_cache, i)
--? print('text.draw:', screen_line, 'at', line_index,pos, 'after', x,y)
local frag_len = utf8.len(screen_line)
@ -81,7 +84,7 @@ function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_number
end
end
end
return y
return y, final_screen_line_starting_pos
end
function Text.screen_line(line, line_cache, i)
@ -205,7 +208,7 @@ function Text.text_input(State, t)
end
end
local before = snapshot(State, State.cursor1.line)
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
Text.insert_at_cursor(State, t)
if State.cursor_y > App.screen.height - State.line_height then
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
@ -238,12 +241,12 @@ function Text.keychord_press(State, chord)
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
elseif chord == 'tab' then
local before = snapshot(State, State.cursor1.line)
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
Text.insert_at_cursor(State, '\t')
if State.cursor_y > App.screen.height - State.line_height then
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
--? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
end
schedule_save(State)
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
@ -424,79 +427,37 @@ function Text.insert_return(State)
end
function Text.pageup(State)
State.screen_top1 = Text.previous_screen_top1(State)
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
end
-- return the top y coordinate of a given line_index,
-- or nil if no part of it is on screen
function Text.starty(State, line_index)
--? print('pageup')
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
if line_index < State.screen_top1.line then return end
local loc2 = Text.to2(State, State.screen_top1)
local y = State.top
while true do
if State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_top
end
if loc2.line == line_index then return y end
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing.pixels(State.lines[loc2.line].h, State.width) + Drawing_padding_bottom
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end -- end of file
loc2 = next_loc2
end
end
function Text.previous_screen_top1(State)
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
local loc2 = Text.to2(State, State.screen_top1)
local top2 = Text.to2(State, State.screen_top1)
--? print(App.screen.height)
local y = App.screen.height - State.line_height
while y >= State.top do
if loc2.line == 1 and loc2.screen_line == 1 and loc2.screen_pos == 1 then break end
if State.lines[loc2.line].mode == 'text' then
--? print(y, top2.line, top2.screen_line, top2.screen_pos)
if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end
if State.lines[State.screen_top1.line].mode == 'text' then
y = y - State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y - Drawing_padding_height - Drawing.pixels(State.lines[loc2.line].h, State.width)
elseif State.lines[State.screen_top1.line].mode == 'drawing' then
y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)
end
loc2 = Text.previous_screen_line(State, loc2)
top2 = Text.previous_screen_line(State, top2)
end
return Text.to1(State, loc2)
State.screen_top1 = Text.to1(State, top2)
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
--? print(State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
--? print('pageup end')
end
function Text.pagedown(State)
State.screen_top1 = Text.screen_bottom1(State)
--? print('pagedown')
State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
--? print('setting top to', State.screen_top1.line, State.screen_top1.pos)
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
--? print('top now', State.screen_top1.line)
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
end
-- return the location of the start of the bottom-most line on screen
function Text.screen_bottom1(State)
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
local loc2 = Text.to2(State, State.screen_top1)
local y = State.top
while true do
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width)
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end
loc2 = next_loc2
end
return Text.to1(State, loc2)
--? print('pagedown end')
end
function Text.up(State)
@ -544,7 +505,7 @@ end
function Text.down(State)
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)
--? 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, 'cursor has no pos')
if Text.cursor_at_final_screen_line(State) then
-- line is done, skip to next text line
@ -561,9 +522,7 @@ function Text.down(State)
break
end
end
local screen_bottom1 = Text.screen_bottom1(State)
--? print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos)
if State.cursor1.line > screen_bottom1.line then
if State.cursor1.line > State.screen_bottom1.line then
--? print('screen top before:', State.screen_top1.line, State.screen_top1.pos)
--? print('scroll up preserving cursor')
Text.snap_cursor_to_bottom_of_screen(State)
@ -571,8 +530,7 @@ function Text.down(State)
end
else
-- move down one screen line in current line
local screen_bottom1 = Text.screen_bottom1(State)
local scroll_down = Text.le1(screen_bottom1, State.cursor1)
local scroll_down = Text.le1(State.screen_bottom1, State.cursor1)
--? print('cursor is NOT at final screen line of its line')
local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
@ -588,7 +546,7 @@ function Text.down(State)
--? print('screen top after:', State.screen_top1.line, State.screen_top1.pos)
end
end
--? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
--? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
end
function Text.start_of_line(State)
@ -725,7 +683,6 @@ function Text.pos_at_start_of_screen_line(State, loc1)
end
function Text.pos_at_end_of_screen_line(State, loc1)
assert(State.lines[loc1.line].mode == 'text')
Text.populate_screen_line_starting_pos(State, loc1.line)
local line_cache = State.line_cache[loc1.line]
local most_recent_final_pos = utf8.len(State.lines[loc1.line].data)+1
@ -739,25 +696,6 @@ function Text.pos_at_end_of_screen_line(State, loc1)
assert(false, ('invalid pos %d'):format(loc1.pos))
end
function Text.final_text_loc_on_screen(State)
local screen_bottom1 = Text.screen_bottom1(State)
if State.lines[screen_bottom1.line].mode == 'text' then
return {
line=screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, screen_bottom1),
}
end
local loc2 = Text.to2(State, screen_bottom1)
while true do
if State.lines[loc2.line].mode == 'text' then break end
assert(loc2.line > 1 or loc2.screen_line > 1 and loc2.screen_pos > 1) -- elsewhere we're making sure there's always at least one text line on screen
loc2 = Text.previous_screen_line(State, loc2)
end
local result = Text.to1(State, loc2)
result.pos = Text.pos_at_end_of_screen_line(State, result)
return result
end
function Text.cursor_at_final_screen_line(State)
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
local screen_lines = State.line_cache[State.cursor1.line].screen_line_starting_pos
@ -798,7 +736,7 @@ function Text.snap_cursor_to_bottom_of_screen(State)
--? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos)
-- slide to start of screen line
top2.screen_pos = 1 -- start of screen line
--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
--? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down')
local y = App.screen.height - State.line_height
-- duplicate some logic from love.draw
@ -828,28 +766,26 @@ function Text.snap_cursor_to_bottom_of_screen(State)
--? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos)
State.screen_top1 = Text.to1(State, top2)
--? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos)
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
end
function Text.in_line(State, line_index, x,y)
local line = State.lines[line_index]
local line_cache = State.line_cache[line_index]
local starty = Text.starty(State, line_index)
if starty == nil then return false end -- outside current page
if y < starty then return false end
if line_cache.starty == nil then return false end -- outside current page
if y < line_cache.starty then return false end
Text.populate_screen_line_starting_pos(State, line_index)
return y < starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1)
return y < line_cache.starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1)
end
-- convert mx,my in pixels to schema-1 coordinates
function Text.to_pos_on_line(State, line_index, mx, my)
local line = State.lines[line_index]
local line_cache = State.line_cache[line_index]
local starty = Text.starty(State, line_index)
assert(my >= starty, 'failed to map y pixel to line')
assert(my >= line_cache.starty, 'failed to map y pixel to line')
-- duplicate some logic from Text.draw
local y = starty
local y = line_cache.starty
local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos)
for screen_line_index = start_screen_line_index,#line_cache.screen_line_starting_pos do
local screen_line_starting_pos = line_cache.screen_line_starting_pos[screen_line_index]
@ -1035,10 +971,6 @@ function Text.le1(a, b)
return a.pos <= b.pos
end
function Text.eq2(a, b)
return a.line == b.line and a.screen_line == b.screen_line and a.screen_pos == b.screen_pos
end
function Text.offset(s, pos1)
if pos1 == 1 then return 1 end
local result = utf8.offset(s, pos1)
@ -1062,22 +994,6 @@ function Text.previous_screen_line(State, loc2)
end
end
function Text.next_screen_line(State, loc2)
if State.lines[loc2.line].mode == 'drawing' then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
end
Text.populate_screen_line_starting_pos(State, loc2.line)
if loc2.screen_line >= #State.line_cache[loc2.line].screen_line_starting_pos then
if loc2.line < #State.lines then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
else
return loc2
end
else
return {line=loc2.line, screen_line=loc2.screen_line+1, screen_pos=1}
end
end
-- resize helper
function Text.tweak_screen_top_and_cursor(State)
if State.screen_top1.pos == 1 then return end
@ -1101,12 +1017,16 @@ function Text.tweak_screen_top_and_cursor(State)
end
end
-- make sure cursor is on screen
local screen_bottom1 = Text.screen_bottom1(State)
if Text.lt1(State.cursor1, State.screen_top1) then
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
elseif State.cursor1.line >= screen_bottom1.line then
elseif State.cursor1.line >= State.screen_bottom1.line then
--? print('too low')
if Text.cursor_out_of_screen(State) then
State.cursor1 = Text.final_text_loc_on_screen(State)
--? print('tweak')
State.cursor1 = {
line=State.screen_bottom1.line,
pos=Text.to_pos_on_line(State, State.screen_bottom1.line, State.right-5, App.screen.height-5),
}
end
end
end
@ -1115,6 +1035,11 @@ end
function Text.cursor_out_of_screen(State)
edit.draw(State)
return State.cursor_y == nil
-- this approach is cheaper and almost works, except on the final screen
-- where file ends above bottom of screen
--? local botpos = Text.pos_at_start_of_screen_line(State, State.cursor1)
--? local botline1 = {line=State.cursor1.line, pos=botpos}
--? return Text.lt1(State.screen_bottom1, botline1)
end
function Text.redraw_all(State)

View File

@ -16,7 +16,7 @@ function test_initial_state()
end
function test_click_to_create_drawing()
App.screen.init{width=800, height=600}
App.screen.init{width=120, height=60}
Editor_state = edit.initialize_test_state()
Editor_state.lines = load_array{}
Text.redraw_all(Editor_state)
@ -75,6 +75,7 @@ function test_press_ctrl()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.run_after_keychord(Editor_state, 'C-m', 'm')
end
@ -231,7 +232,7 @@ function test_skip_multiple_spaces_to_next_word()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=4} -- at the start of second word
edit.draw(Editor_state)
edit.run_after_keychord(Editor_state, 'M-right', 'right')
edit.run_after_keychord(Editor_state, 'M-right', 'right')
check_eq(Editor_state.cursor1.pos, 9, 'check')
end
@ -242,7 +243,7 @@ function test_move_past_end_of_word_on_next_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=8}
edit.draw(Editor_state)
edit.run_after_keychord(Editor_state, 'M-right', 'right')
edit.run_after_keychord(Editor_state, 'M-right', 'right')
check_eq(Editor_state.cursor1.line, 2, 'line')
check_eq(Editor_state.cursor1.pos, 4, 'pos')
end
@ -254,8 +255,9 @@ function test_click_moves_cursor()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
@ -272,6 +274,7 @@ function test_click_to_left_of_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=3}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
-- click to the left of the line
edit.draw(Editor_state)
@ -291,6 +294,7 @@ function test_click_takes_margins_into_account()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
-- click on the other line
edit.draw(Editor_state)
@ -309,6 +313,7 @@ function test_click_on_empty_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
-- click on the empty line
edit.draw(Editor_state)
@ -327,6 +332,7 @@ function test_click_below_all_lines()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
-- click below first line
edit.draw(Editor_state)
@ -344,6 +350,7 @@ function test_draw_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'screen:1')
@ -360,6 +367,7 @@ function test_draw_wrapping_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'screen:1')
@ -376,6 +384,7 @@ function test_draw_word_wrapping_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc ', 'screen:1')
@ -393,6 +402,7 @@ function test_click_on_wrapping_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=20}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- click on the other line
edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -411,6 +421,7 @@ function test_click_on_wrapping_line_takes_margins_into_account()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=20}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- click on the other line
edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -428,6 +439,7 @@ function test_draw_text_wrapping_within_word()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abcd ', 'screen:1')
@ -445,6 +457,7 @@ function test_draw_wrapping_text_containing_non_ascii()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'mad', 'screen:1')
@ -463,6 +476,7 @@ function test_click_past_end_of_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'madam ', 'baseline/screen:1')
@ -485,6 +499,7 @@ function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=8}
Editor_state.screen_top1 = {line=1, pos=7}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, "I'm ad", 'baseline/screen:2')
@ -505,6 +520,7 @@ function test_click_past_end_of_wrapping_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'madam ', 'baseline/screen:1')
@ -528,6 +544,7 @@ function test_click_past_end_of_wrapping_line_containing_non_ascii()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'madam ', 'baseline/screen:1')
@ -552,6 +569,7 @@ function test_click_past_end_of_word_wrapping_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1')
@ -570,10 +588,11 @@ function test_select_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- select a letter
App.fake_key_press('lshift')
edit.run_after_keychord(Editor_state, 'S-right', 'right')
edit.run_after_keychord(Editor_state, 'S-right', 'right')
App.fake_key_release('lshift')
edit.key_release(Editor_state, 'lshift')
-- selection persists even after shift is released
@ -592,9 +611,10 @@ function test_cursor_movement_without_shift_resets_selection()
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- press an arrow key without shift
edit.run_after_keychord(Editor_state, 'right', 'right')
edit.run_after_keychord(Editor_state, 'right', 'right')
-- no change to data, selection is reset
check_nil(Editor_state.selection1.line, 'check')
check_eq(Editor_state.lines[1].data, 'abc', 'data')
@ -609,6 +629,7 @@ function test_edit_deletes_selection()
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- press a key
edit.run_after_text_input(Editor_state, 'x')
@ -625,6 +646,7 @@ function test_edit_with_shift_key_deletes_selection()
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- mimic precise keypresses for a capital letter
App.fake_key_press('lshift')
@ -646,6 +668,7 @@ function test_copy_does_not_reset_selection()
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- copy selection
edit.run_after_keychord(Editor_state, 'C-c', 'c')
@ -663,6 +686,7 @@ function test_cut()
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- press a key
edit.run_after_keychord(Editor_state, 'C-x', 'x')
@ -680,6 +704,7 @@ function test_paste_replaces_selection()
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.selection1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- set clipboard
App.clipboard = 'xyz'
@ -698,6 +723,7 @@ function test_deleting_selection_may_scroll()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=2}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1')
@ -721,6 +747,7 @@ function test_edit_wrapping_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=4}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
edit.run_after_text_input(Editor_state, 'g')
local y = Editor_state.top
@ -739,6 +766,7 @@ function test_insert_newline()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -767,6 +795,7 @@ function test_insert_newline_at_start_of_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- hitting the enter key splits the line
edit.run_after_keychord(Editor_state, 'return', 'return')
check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
@ -783,6 +812,7 @@ function test_insert_from_clipboard()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -811,8 +841,9 @@ function test_select_text_using_mouse()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
-- press and hold on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
-- drag and release somewhere else
@ -830,8 +861,9 @@ function test_select_text_using_mouse_starting_above_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
-- press mouse above first line of text
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')
@ -847,6 +879,7 @@ function test_select_text_using_mouse_starting_above_text_wrapping_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=5}
Editor_state.screen_top1 = {line=2, pos=3}
Editor_state.screen_bottom1 = {}
-- press mouse above first line of text
edit.draw(Editor_state)
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
@ -869,6 +902,7 @@ function test_select_text_using_mouse_starting_below_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'ab', 'baseline:screen:1')
@ -889,8 +923,9 @@ function test_select_text_using_mouse_and_shift()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
-- click on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -913,8 +948,9 @@ function test_select_text_repeatedly_using_mouse_and_shift()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
-- click on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -942,6 +978,7 @@ function test_select_all_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- select all
App.fake_key_press('lctrl')
@ -963,6 +1000,7 @@ function test_cut_without_selection()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
edit.draw(Editor_state)
-- try to cut without selecting text
@ -978,6 +1016,7 @@ function test_pagedown()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- initially the first two lines are displayed
edit.draw(Editor_state)
local y = Editor_state.top
@ -1007,6 +1046,7 @@ function test_pagedown_skips_drawings()
check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines')
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
local drawing_height = Drawing_padding_height + drawing_width/2 -- default
-- initially the screen displays the first line and the drawing
-- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px
@ -1030,6 +1070,7 @@ function test_pagedown_can_start_from_middle_of_long_wrapping_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc ', 'baseline/screen:1')
@ -1064,6 +1105,7 @@ function test_pagedown_never_moves_up()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=9}
Editor_state.screen_top1 = {line=1, pos=9}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- pagedown makes no change
edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
@ -1078,6 +1120,7 @@ function test_down_arrow_moves_cursor()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- initially the first three lines are displayed
edit.draw(Editor_state)
local y = Editor_state.top
@ -1110,6 +1153,7 @@ function test_down_arrow_skips_drawing()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1131,6 +1175,7 @@ function test_down_arrow_scrolls_down_by_one_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1158,6 +1203,7 @@ function test_down_arrow_scrolls_down_by_one_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1186,6 +1232,7 @@ function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1213,6 +1260,7 @@ function test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1246,6 +1294,7 @@ function test_up_arrow_moves_cursor()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1277,6 +1326,7 @@ function test_up_arrow_skips_drawing()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1298,6 +1348,7 @@ function test_up_arrow_scrolls_up_by_one_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1')
@ -1325,6 +1376,7 @@ function test_up_arrow_scrolls_up_by_one_line_skipping_drawing()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=3, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1')
@ -1346,6 +1398,7 @@ function test_up_arrow_scrolls_up_by_one_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=6}
Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:1')
@ -1373,6 +1426,7 @@ function test_up_arrow_scrolls_up_to_final_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'ghi', 'baseline/screen:1')
@ -1402,6 +1456,7 @@ function test_up_arrow_scrolls_up_to_empty_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1428,6 +1483,7 @@ function test_pageup()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
-- initially the last two lines are displayed
edit.draw(Editor_state)
local y = Editor_state.top
@ -1452,6 +1508,7 @@ function test_pageup_scrolls_up_by_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'ghi', 'baseline/screen:1')
@ -1480,6 +1537,7 @@ function test_pageup_scrolls_up_from_middle_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=5}
Editor_state.screen_top1 = {line=2, pos=5}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:2')
@ -1506,6 +1564,7 @@ function test_enter_on_bottom_line_scrolls_down()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1534,6 +1593,7 @@ function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=4, pos=2}
Editor_state.screen_top1 = {line=4, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:1')
@ -1556,6 +1616,7 @@ function test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bot
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- after hitting the inserting_text key the screen does not scroll down
edit.run_after_text_input(Editor_state, 'a')
@ -1574,6 +1635,7 @@ function test_typing_on_bottom_line_scrolls_down()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=4}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1603,6 +1665,7 @@ function test_left_arrow_scrolls_up_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
-- cursor is at top of screen
Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state)
@ -1631,6 +1694,7 @@ function test_right_arrow_scrolls_down_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- cursor is at bottom right of screen
Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state)
@ -1641,7 +1705,7 @@ function test_right_arrow_scrolls_down_in_wrapped_line()
y = y + Editor_state.line_height
App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
-- after hitting the right arrow the screen scrolls down by one line
edit.run_after_keychord(Editor_state, 'right', 'right')
edit.run_after_keychord(Editor_state, 'right', 'right')
check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')
@ -1660,6 +1724,7 @@ function test_home_scrolls_up_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
-- cursor is at top of screen
Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state)
@ -1688,6 +1753,7 @@ function test_end_scrolls_down_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- cursor is at bottom right of screen
Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state)
@ -1718,6 +1784,7 @@ function test_position_cursor_on_recently_edited_wrapping_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=25}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1')
@ -1751,6 +1818,7 @@ function test_backspace_can_scroll_up()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1')
@ -1778,6 +1846,7 @@ function test_backspace_can_scroll_up_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=5}
Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:1')
@ -1912,6 +1981,7 @@ function test_undo_insert_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=4}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- insert a character
edit.draw(Editor_state)
edit.run_after_text_input(Editor_state, 'g')
@ -1946,6 +2016,7 @@ function test_undo_delete_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=5}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- delete a character
edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')
@ -1984,6 +2055,7 @@ function test_undo_restores_selection()
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- delete selected text
edit.run_after_text_input(Editor_state, 'x')
@ -2004,6 +2076,7 @@ function test_search()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- search for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f')
@ -2030,6 +2103,7 @@ function test_search_upwards()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- search for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f')
@ -2047,6 +2121,7 @@ function test_search_wrap()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- search for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f')
@ -2064,6 +2139,7 @@ function test_search_wrap_upwards()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- search upwards for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f')

187
text.lua
View File

@ -2,13 +2,15 @@
Text = {}
-- draw a line starting from startpos to screen at y between State.left and State.right
-- return y for the next line
-- return y for the next line, and position of start of final screen line drawn
function Text.draw(State, line_index, y, startpos)
--? print('text.draw', line_index, y)
local line = State.lines[line_index]
local line_cache = State.line_cache[line_index]
line_cache.starty = y
line_cache.startpos = startpos
-- wrap long lines
local final_screen_line_starting_pos = startpos -- track value to return
Text.populate_screen_line_starting_pos(State, line_index)
assert(#line_cache.screen_line_starting_pos >= 1, 'line cache missing screen line info')
for i=1,#line_cache.screen_line_starting_pos do
@ -16,6 +18,7 @@ function Text.draw(State, line_index, y, startpos)
if pos < startpos then
-- render nothing
else
final_screen_line_starting_pos = pos
local screen_line = Text.screen_line(line, line_cache, i)
--? print('text.draw:', screen_line, 'at', line_index,pos, 'after', x,y)
local frag_len = utf8.len(screen_line)
@ -56,7 +59,7 @@ function Text.draw(State, line_index, y, startpos)
end
end
end
return y
return y, final_screen_line_starting_pos
end
function Text.screen_line(line, line_cache, i)
@ -131,7 +134,7 @@ function Text.text_input(State, t)
end
end
local before = snapshot(State, State.cursor1.line)
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
Text.insert_at_cursor(State, t)
if State.cursor_y > App.screen.height - State.line_height then
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
@ -164,12 +167,12 @@ function Text.keychord_press(State, chord)
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
elseif chord == 'tab' then
local before = snapshot(State, State.cursor1.line)
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
Text.insert_at_cursor(State, '\t')
if State.cursor_y > App.screen.height - State.line_height then
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
--? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
end
schedule_save(State)
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
@ -350,79 +353,49 @@ function Text.insert_return(State)
end
function Text.pageup(State)
State.screen_top1 = Text.previous_screen_top1(State)
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
end
-- return the top y coordinate of a given line_index,
-- or nil if no part of it is on screen
function Text.starty(State, line_index)
--? print('pageup')
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
if line_index < State.screen_top1.line then return end
local loc2 = Text.to2(State, State.screen_top1)
local y = State.top
while true do
if State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_top
end
if loc2.line == line_index then return y end
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing.pixels(State.lines[loc2.line].h, State.width) + Drawing_padding_bottom
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end -- end of file
loc2 = next_loc2
end
end
function Text.previous_screen_top1(State)
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
local loc2 = Text.to2(State, State.screen_top1)
local top2 = Text.to2(State, State.screen_top1)
--? print(App.screen.height)
local y = App.screen.height - State.line_height
while y >= State.top do
if loc2.line == 1 and loc2.screen_line == 1 and loc2.screen_pos == 1 then break end
if State.lines[loc2.line].mode == 'text' then
--? print(y, top2.line, top2.screen_line, top2.screen_pos)
if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end
if State.lines[State.screen_top1.line].mode == 'text' then
y = y - State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y - Drawing_padding_height - Drawing.pixels(State.lines[loc2.line].h, State.width)
elseif State.lines[State.screen_top1.line].mode == 'drawing' then
y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)
end
loc2 = Text.previous_screen_line(State, loc2)
top2 = Text.previous_screen_line(State, top2)
end
return Text.to1(State, loc2)
State.screen_top1 = Text.to1(State, top2)
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
--? print(State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
--? print('pageup end')
end
function Text.pagedown(State)
State.screen_top1 = Text.screen_bottom1(State)
--? print('pagedown')
-- If a line/paragraph gets to a page boundary, I often want to scroll
-- before I get to the bottom.
-- However, only do this if it makes forward progress.
local bot2 = Text.to2(State, State.screen_bottom1)
if bot2.screen_line > 1 then
bot2.screen_line = math.max(bot2.screen_line-10, 1)
end
local new_top1 = Text.to1(State, bot2)
if Text.lt1(State.screen_top1, new_top1) then
State.screen_top1 = new_top1
else
State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
end
--? print('setting top to', State.screen_top1.line, State.screen_top1.pos)
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
--? print('top now', State.screen_top1.line)
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
end
-- return the location of the start of the bottom-most line on screen
function Text.screen_bottom1(State)
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
local loc2 = Text.to2(State, State.screen_top1)
local y = State.top
while true do
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width)
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end
loc2 = next_loc2
end
return Text.to1(State, loc2)
--? print('pagedown end')
end
function Text.up(State)
@ -470,7 +443,7 @@ end
function Text.down(State)
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)
--? 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, 'cursor has no pos')
if Text.cursor_at_final_screen_line(State) then
-- line is done, skip to next text line
@ -487,9 +460,7 @@ function Text.down(State)
break
end
end
local screen_bottom1 = Text.screen_bottom1(State)
--? print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos)
if State.cursor1.line > screen_bottom1.line then
if State.cursor1.line > State.screen_bottom1.line then
--? print('screen top before:', State.screen_top1.line, State.screen_top1.pos)
--? print('scroll up preserving cursor')
Text.snap_cursor_to_bottom_of_screen(State)
@ -497,8 +468,7 @@ function Text.down(State)
end
else
-- move down one screen line in current line
local screen_bottom1 = Text.screen_bottom1(State)
local scroll_down = Text.le1(screen_bottom1, State.cursor1)
local scroll_down = Text.le1(State.screen_bottom1, State.cursor1)
--? print('cursor is NOT at final screen line of its line')
local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
@ -514,7 +484,7 @@ function Text.down(State)
--? print('screen top after:', State.screen_top1.line, State.screen_top1.pos)
end
end
--? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
--? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
end
function Text.start_of_line(State)
@ -651,7 +621,6 @@ function Text.pos_at_start_of_screen_line(State, loc1)
end
function Text.pos_at_end_of_screen_line(State, loc1)
assert(State.lines[loc1.line].mode == 'text')
Text.populate_screen_line_starting_pos(State, loc1.line)
local line_cache = State.line_cache[loc1.line]
local most_recent_final_pos = utf8.len(State.lines[loc1.line].data)+1
@ -665,25 +634,6 @@ function Text.pos_at_end_of_screen_line(State, loc1)
assert(false, ('invalid pos %d'):format(loc1.pos))
end
function Text.final_text_loc_on_screen(State)
local screen_bottom1 = Text.screen_bottom1(State)
if State.lines[screen_bottom1.line].mode == 'text' then
return {
line=screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, screen_bottom1),
}
end
local loc2 = Text.to2(State, screen_bottom1)
while true do
if State.lines[loc2.line].mode == 'text' then break end
assert(loc2.line > 1 or loc2.screen_line > 1 and loc2.screen_pos > 1) -- elsewhere we're making sure there's always at least one text line on screen
loc2 = Text.previous_screen_line(State, loc2)
end
local result = Text.to1(State, loc2)
result.pos = Text.pos_at_end_of_screen_line(State, result)
return result
end
function Text.cursor_at_final_screen_line(State)
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
local screen_lines = State.line_cache[State.cursor1.line].screen_line_starting_pos
@ -724,7 +674,7 @@ function Text.snap_cursor_to_bottom_of_screen(State)
--? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos)
-- slide to start of screen line
top2.screen_pos = 1 -- start of screen line
--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
--? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down')
local y = App.screen.height - State.line_height
-- duplicate some logic from love.draw
@ -754,28 +704,26 @@ function Text.snap_cursor_to_bottom_of_screen(State)
--? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos)
State.screen_top1 = Text.to1(State, top2)
--? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos)
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
end
function Text.in_line(State, line_index, x,y)
local line = State.lines[line_index]
local line_cache = State.line_cache[line_index]
local starty = Text.starty(State, line_index)
if starty == nil then return false end -- outside current page
if y < starty then return false end
if line_cache.starty == nil then return false end -- outside current page
if y < line_cache.starty then return false end
Text.populate_screen_line_starting_pos(State, line_index)
return y < starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1)
return y < line_cache.starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1)
end
-- convert mx,my in pixels to schema-1 coordinates
function Text.to_pos_on_line(State, line_index, mx, my)
local line = State.lines[line_index]
local line_cache = State.line_cache[line_index]
local starty = Text.starty(State, line_index)
assert(my >= starty, 'failed to map y pixel to line')
assert(my >= line_cache.starty, 'failed to map y pixel to line')
-- duplicate some logic from Text.draw
local y = starty
local y = line_cache.starty
local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos)
for screen_line_index = start_screen_line_index,#line_cache.screen_line_starting_pos do
local screen_line_starting_pos = line_cache.screen_line_starting_pos[screen_line_index]
@ -961,10 +909,6 @@ function Text.le1(a, b)
return a.pos <= b.pos
end
function Text.eq2(a, b)
return a.line == b.line and a.screen_line == b.screen_line and a.screen_pos == b.screen_pos
end
function Text.offset(s, pos1)
if pos1 == 1 then return 1 end
local result = utf8.offset(s, pos1)
@ -988,22 +932,6 @@ function Text.previous_screen_line(State, loc2)
end
end
function Text.next_screen_line(State, loc2)
if State.lines[loc2.line].mode == 'drawing' then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
end
Text.populate_screen_line_starting_pos(State, loc2.line)
if loc2.screen_line >= #State.line_cache[loc2.line].screen_line_starting_pos then
if loc2.line < #State.lines then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
else
return loc2
end
else
return {line=loc2.line, screen_line=loc2.screen_line+1, screen_pos=1}
end
end
-- resize helper
function Text.tweak_screen_top_and_cursor(State)
if State.screen_top1.pos == 1 then return end
@ -1027,12 +955,16 @@ function Text.tweak_screen_top_and_cursor(State)
end
end
-- make sure cursor is on screen
local screen_bottom1 = Text.screen_bottom1(State)
if Text.lt1(State.cursor1, State.screen_top1) then
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
elseif State.cursor1.line >= screen_bottom1.line then
elseif State.cursor1.line >= State.screen_bottom1.line then
--? print('too low')
if Text.cursor_out_of_screen(State) then
State.cursor1 = Text.final_text_loc_on_screen(State)
--? print('tweak')
State.cursor1 = {
line=State.screen_bottom1.line,
pos=Text.to_pos_on_line(State, State.screen_bottom1.line, State.right-5, App.screen.height-5),
}
end
end
end
@ -1041,6 +973,11 @@ end
function Text.cursor_out_of_screen(State)
edit.draw(State)
return State.cursor_y == nil
-- this approach is cheaper and almost works, except on the final screen
-- where file ends above bottom of screen
--? local botpos = Text.pos_at_start_of_screen_line(State, State.cursor1)
--? local botline1 = {line=State.cursor1.line, pos=botpos}
--? return Text.lt1(State.screen_bottom1, botline1)
end
function Text.redraw_all(State)

View File

@ -16,7 +16,7 @@ function test_initial_state()
end
function test_click_to_create_drawing()
App.screen.init{width=800, height=600}
App.screen.init{width=120, height=60}
Editor_state = edit.initialize_test_state()
Editor_state.lines = load_array{}
Text.redraw_all(Editor_state)
@ -75,6 +75,7 @@ function test_press_ctrl()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.run_after_keychord(Editor_state, 'C-m', 'm')
end
@ -254,8 +255,9 @@ function test_click_moves_cursor()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
@ -272,6 +274,7 @@ function test_click_to_left_of_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=3}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
-- click to the left of the line
edit.draw(Editor_state)
@ -291,6 +294,7 @@ function test_click_takes_margins_into_account()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
-- click on the other line
edit.draw(Editor_state)
@ -309,6 +313,7 @@ function test_click_on_empty_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
-- click on the empty line
edit.draw(Editor_state)
@ -327,6 +332,7 @@ function test_click_below_all_lines()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
-- click below first line
edit.draw(Editor_state)
@ -344,6 +350,7 @@ function test_draw_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'screen:1')
@ -360,6 +367,7 @@ function test_draw_wrapping_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'screen:1')
@ -376,6 +384,7 @@ function test_draw_word_wrapping_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc ', 'screen:1')
@ -393,6 +402,7 @@ function test_click_on_wrapping_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=20}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- click on the other line
edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -411,6 +421,7 @@ function test_click_on_wrapping_line_takes_margins_into_account()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=20}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- click on the other line
edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -428,6 +439,7 @@ function test_draw_text_wrapping_within_word()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abcd ', 'screen:1')
@ -445,6 +457,7 @@ function test_draw_wrapping_text_containing_non_ascii()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'mad', 'screen:1')
@ -463,6 +476,7 @@ function test_click_past_end_of_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'madam ', 'baseline/screen:1')
@ -485,6 +499,7 @@ function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=8}
Editor_state.screen_top1 = {line=1, pos=7}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, "I'm ad", 'baseline/screen:2')
@ -505,6 +520,7 @@ function test_click_past_end_of_wrapping_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'madam ', 'baseline/screen:1')
@ -528,6 +544,7 @@ function test_click_past_end_of_wrapping_line_containing_non_ascii()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'madam ', 'baseline/screen:1')
@ -552,6 +569,7 @@ function test_click_past_end_of_word_wrapping_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1')
@ -570,6 +588,7 @@ function test_select_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- select a letter
App.fake_key_press('lshift')
@ -592,6 +611,7 @@ function test_cursor_movement_without_shift_resets_selection()
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- press an arrow key without shift
edit.run_after_keychord(Editor_state, 'right', 'right')
@ -609,6 +629,7 @@ function test_edit_deletes_selection()
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- press a key
edit.run_after_text_input(Editor_state, 'x')
@ -625,6 +646,7 @@ function test_edit_with_shift_key_deletes_selection()
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- mimic precise keypresses for a capital letter
App.fake_key_press('lshift')
@ -646,6 +668,7 @@ function test_copy_does_not_reset_selection()
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- copy selection
edit.run_after_keychord(Editor_state, 'C-c', 'c')
@ -663,6 +686,7 @@ function test_cut()
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- press a key
edit.run_after_keychord(Editor_state, 'C-x', 'x')
@ -680,6 +704,7 @@ function test_paste_replaces_selection()
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.selection1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- set clipboard
App.clipboard = 'xyz'
@ -698,6 +723,7 @@ function test_deleting_selection_may_scroll()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=2}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1')
@ -721,6 +747,7 @@ function test_edit_wrapping_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=4}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
edit.run_after_text_input(Editor_state, 'g')
local y = Editor_state.top
@ -739,6 +766,7 @@ function test_insert_newline()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -767,6 +795,7 @@ function test_insert_newline_at_start_of_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- hitting the enter key splits the line
edit.run_after_keychord(Editor_state, 'return', 'return')
check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
@ -783,6 +812,7 @@ function test_insert_from_clipboard()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -811,8 +841,9 @@ function test_select_text_using_mouse()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
-- press and hold on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
-- drag and release somewhere else
@ -830,8 +861,9 @@ function test_select_text_using_mouse_starting_above_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
-- press mouse above first line of text
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')
@ -847,6 +879,7 @@ function test_select_text_using_mouse_starting_above_text_wrapping_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=5}
Editor_state.screen_top1 = {line=2, pos=3}
Editor_state.screen_bottom1 = {}
-- press mouse above first line of text
edit.draw(Editor_state)
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
@ -869,6 +902,7 @@ function test_select_text_using_mouse_starting_below_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'ab', 'baseline:screen:1')
@ -889,8 +923,9 @@ function test_select_text_using_mouse_and_shift()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
-- click on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -913,8 +948,9 @@ function test_select_text_repeatedly_using_mouse_and_shift()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
-- click on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -942,6 +978,7 @@ function test_select_all_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- select all
App.fake_key_press('lctrl')
@ -963,6 +1000,7 @@ function test_cut_without_selection()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
edit.draw(Editor_state)
-- try to cut without selecting text
@ -978,6 +1016,7 @@ function test_pagedown()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- initially the first two lines are displayed
edit.draw(Editor_state)
local y = Editor_state.top
@ -1007,6 +1046,7 @@ function test_pagedown_skips_drawings()
check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines')
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
local drawing_height = Drawing_padding_height + drawing_width/2 -- default
-- initially the screen displays the first line and the drawing
-- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px
@ -1022,6 +1062,36 @@ function test_pagedown_skips_drawings()
App.screen.check(y, 'def', 'screen:1')
end
function test_pagedown_often_shows_start_of_wrapping_line()
-- draw a few lines ending in part of a wrapping line
App.screen.init{width=50, height=60}
Editor_state = edit.initialize_test_state()
Editor_state.lines = load_array{'abc', 'def ghi jkl', 'mno'}
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
y = y + Editor_state.line_height
App.screen.check(y, 'def ', 'baseline/screen:2')
y = y + Editor_state.line_height
App.screen.check(y, 'ghi ', 'baseline/screen:3')
-- after pagedown we start drawing from the bottom _line_ (multiple screen lines)
edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
check_eq(Editor_state.screen_top1.line, 2, 'screen_top:line')
check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
y = Editor_state.top
App.screen.check(y, 'def ', 'screen:1')
y = y + Editor_state.line_height
App.screen.check(y, 'ghi ', 'screen:2')
y = y + Editor_state.line_height
App.screen.check(y, 'jkl', 'screen:3')
end
function test_pagedown_can_start_from_middle_of_long_wrapping_line()
-- draw a few lines starting from a very long wrapping line
App.screen.init{width=Editor_state.left+30, height=60}
@ -1030,6 +1100,7 @@ function test_pagedown_can_start_from_middle_of_long_wrapping_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc ', 'baseline/screen:1')
@ -1064,6 +1135,7 @@ function test_pagedown_never_moves_up()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=9}
Editor_state.screen_top1 = {line=1, pos=9}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- pagedown makes no change
edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
@ -1078,6 +1150,7 @@ function test_down_arrow_moves_cursor()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- initially the first three lines are displayed
edit.draw(Editor_state)
local y = Editor_state.top
@ -1110,6 +1183,7 @@ function test_down_arrow_skips_drawing()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1131,6 +1205,7 @@ function test_down_arrow_scrolls_down_by_one_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1158,6 +1233,7 @@ function test_down_arrow_scrolls_down_by_one_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1186,6 +1262,7 @@ function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1213,6 +1290,7 @@ function test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1246,6 +1324,7 @@ function test_up_arrow_moves_cursor()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1277,6 +1356,7 @@ function test_up_arrow_skips_drawing()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1298,6 +1378,7 @@ function test_up_arrow_scrolls_up_by_one_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1')
@ -1325,6 +1406,7 @@ function test_up_arrow_scrolls_up_by_one_line_skipping_drawing()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=3, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1')
@ -1346,6 +1428,7 @@ function test_up_arrow_scrolls_up_by_one_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=6}
Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:1')
@ -1373,6 +1456,7 @@ function test_up_arrow_scrolls_up_to_final_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'ghi', 'baseline/screen:1')
@ -1402,6 +1486,7 @@ function test_up_arrow_scrolls_up_to_empty_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1428,6 +1513,7 @@ function test_pageup()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
-- initially the last two lines are displayed
edit.draw(Editor_state)
local y = Editor_state.top
@ -1452,6 +1538,7 @@ function test_pageup_scrolls_up_by_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'ghi', 'baseline/screen:1')
@ -1480,6 +1567,7 @@ function test_pageup_scrolls_up_from_middle_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=5}
Editor_state.screen_top1 = {line=2, pos=5}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:2')
@ -1506,6 +1594,7 @@ function test_enter_on_bottom_line_scrolls_down()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1534,6 +1623,7 @@ function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=4, pos=2}
Editor_state.screen_top1 = {line=4, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:1')
@ -1556,6 +1646,7 @@ function test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bot
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- after hitting the inserting_text key the screen does not scroll down
edit.run_after_text_input(Editor_state, 'a')
@ -1574,6 +1665,7 @@ function test_typing_on_bottom_line_scrolls_down()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=4}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1603,6 +1695,7 @@ function test_left_arrow_scrolls_up_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
-- cursor is at top of screen
Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state)
@ -1631,6 +1724,7 @@ function test_right_arrow_scrolls_down_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- cursor is at bottom right of screen
Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state)
@ -1660,6 +1754,7 @@ function test_home_scrolls_up_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
-- cursor is at top of screen
Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state)
@ -1688,6 +1783,7 @@ function test_end_scrolls_down_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- cursor is at bottom right of screen
Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state)
@ -1718,6 +1814,7 @@ function test_position_cursor_on_recently_edited_wrapping_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=25}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1')
@ -1751,6 +1848,7 @@ function test_backspace_can_scroll_up()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1')
@ -1778,6 +1876,7 @@ function test_backspace_can_scroll_up_screen_line()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=5}
Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:1')
@ -1912,6 +2011,7 @@ function test_undo_insert_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=4}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- insert a character
edit.draw(Editor_state)
edit.run_after_text_input(Editor_state, 'g')
@ -1946,6 +2046,7 @@ function test_undo_delete_text()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=5}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- delete a character
edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')
@ -1984,6 +2085,7 @@ function test_undo_restores_selection()
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- delete selected text
edit.run_after_text_input(Editor_state, 'x')
@ -2004,6 +2106,7 @@ function test_search()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- search for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f')
@ -2030,6 +2133,7 @@ function test_search_upwards()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- search for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f')
@ -2047,6 +2151,7 @@ function test_search_wrap()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- search for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f')
@ -2064,6 +2169,7 @@ function test_search_wrap_upwards()
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
-- search upwards for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f')