~akkartik/capture.love

2eb71d7a804bc862e2b5304cf2d574c5c882fff7 — Kartik K. Agaram 2 days ago 12f5ada + 55f5c2d main
Merge lines.love
M README.md => README.md +5 -0
@@ 73,6 73,11 @@ 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.

M app.lua => app.lua +4 -1
@@ 130,6 130,8 @@ 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)


@@ 404,9 406,10 @@ 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)
  local stack_trace = debug.traceback('', --[[stack frame]]5)  -- most likely to be useful, but set to 0 for a complete stack trace
  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

M drawing.lua => drawing.lua +57 -50
@@ 6,16 6,15 @@ 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()
  if pmx < State.right and pmy > line_cache.starty and pmy < line_cache.starty+Drawing.pixels(line.h, State.width) then
  local starty = Text.starty(State, line_index)
  if pmx < State.right and pmy > starty and pmy < starty+Drawing.pixels(line.h, State.width) then
    App.color(Icon_color)
    love.graphics.rectangle('line', State.left,line_cache.starty, State.width,Drawing.pixels(line.h, State.width))
    love.graphics.rectangle('line', State.left,starty, State.width,Drawing.pixels(line.h, State.width))
    if icon[State.current_drawing_mode] then
      icon[State.current_drawing_mode](State.right-22, line_cache.starty+4)
      icon[State.current_drawing_mode](State.right-22, starty+4)
    else
      icon[State.previous_drawing_mode](State.right-22, line_cache.starty+4)
      icon[State.previous_drawing_mode](State.right-22, starty+4)
    end

    if App.mouse_down(1) and love.keyboard.isDown('h') then


@@ 30,7 29,7 @@ function Drawing.draw(State, line_index, y)
  end

  local mx = Drawing.coord(pmx-State.left, State.width)
  local my = Drawing.coord(pmy-line_cache.starty, State.width)
  local my = Drawing.coord(pmy-starty, State.width)

  for _,shape in ipairs(line.shapes) do
    if geom.on_shape(mx,my, line, shape) then


@@ 38,11 37,11 @@ function Drawing.draw(State, line_index, y)
    else
      App.color(Stroke_color)
    end
    Drawing.draw_shape(line, shape, line_cache.starty, State.left,State.right)
    Drawing.draw_shape(line, shape, 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)+line_cache.starty end
  local function py(y) return Drawing.pixels(y, State.width)+starty end
  for i,p in ipairs(line.points) do
    if p.deleted == nil then
      if Drawing.near(p, mx,my, State.width) then


@@ 71,7 70,7 @@ function Drawing.draw(State, line_index, y)
    end
  end
  App.color(Current_stroke_color)
  Drawing.draw_pending_shape(line, line_cache.starty, State.left,State.right)
  Drawing.draw_pending_shape(line, starty, State.left,State.right)
end

function Drawing.draw_shape(drawing, shape, top, left,right)


@@ 209,17 208,24 @@ function Drawing.draw_pending_shape(drawing, top, left,right)
  end
end

function Drawing.in_drawing(drawing, line_cache, x,y, left,right)
  if line_cache.starty == nil then return false end  -- outside current page
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]
  local width = right-left
  return y >= line_cache.starty and y < line_cache.starty + Drawing.pixels(drawing.h, width) and x >= left and x < right
  return y >= starty and y < 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 line_cache = State.line_cache[drawing_index]
  local starty = Text.starty(State, drawing_index)
  local cx = Drawing.coord(x-State.left, State.width)
  local cy = Drawing.coord(y-line_cache.starty, State.width)
  local cy = Drawing.coord(y-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


@@ 244,8 250,8 @@ end
function Drawing.update(State)
  if State.lines.current_drawing == nil then return end
  local drawing = State.lines.current_drawing
  local line_cache = State.line_cache[State.lines.current_drawing_index]
  if line_cache.starty == nil then
  local starty = Text.starty(State, State.lines.current_drawing_index)
  if starty == nil then
    -- some event cleared starty just this frame
    -- draw in this frame will soon set starty
    -- just skip this frame


@@ 254,9 260,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-line_cache.starty, State.width)
  local my = Drawing.coord(pmy-starty, State.width)
  if App.mouse_down(1) then
    if Drawing.in_drawing(drawing, line_cache, pmx,pmy, State.left,State.right) then
    if Drawing.in_current_drawing(State, 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


@@ 266,7 272,7 @@ function Drawing.update(State)
      end
    end
  elseif State.current_drawing_mode == 'move' then
    if Drawing.in_drawing(drawing, line_cache, pmx, pmy, State.left,State.right) then
    if Drawing.in_current_drawing(State, 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)


@@ 304,7 310,7 @@ function Drawing.mouse_release(State, x,y, mouse_button)
    end
  elseif State.lines.current_drawing then
    local drawing = State.lines.current_drawing
    local line_cache = State.line_cache[State.lines.current_drawing_index]
    local starty = Text.starty(State, State.lines.current_drawing_index)
    if drawing.pending then
      if drawing.pending.mode == nil then
        -- nothing pending


@@ 313,14 319,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-line_cache.starty, State.width)
        local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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-line_cache.starty, State.width)
        local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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)


@@ 328,11 334,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), line_cache.starty+Drawing.pixels(p2.y, State.width))
          App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), 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-line_cache.starty, State.width)
        local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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)


@@ 340,7 346,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-line_cache.starty, State.width)
          local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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]]


@@ 355,7 361,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-line_cache.starty, State.width)
          local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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]]


@@ 366,14 372,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-line_cache.starty, State.width)
        local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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-line_cache.starty, State.width)
        local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-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)


@@ 477,13 483,15 @@ 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,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 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 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,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 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 j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
    while #drawing.pending.vertices >= 2 do
      table.remove(drawing.pending.vertices)


@@ 492,9 500,10 @@ 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,line_cache = Drawing.current_drawing(State)
    local drawing_index,drawing = Drawing.current_drawing(State)
    local starty = Text.starty(State, drawing_index)
    drawing.pending.mode = 'arc'
    local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
    local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-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)


@@ 510,7 519,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,line_cache,i,p = Drawing.select_point_at_mouse(State)
    local drawing_index,drawing,_,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


@@ 521,7 530,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,line_cache,point_index,p = Drawing.select_point_at_mouse(State)
    local drawing_index,drawing,_,point_index,p = Drawing.select_point_at_mouse(State)
    if drawing then
      if State.previous_drawing_mode == nil then
        -- don't clobber


@@ 619,9 628,8 @@ 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
      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
      if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
        return drawing_index,drawing
      end
    end
  end


@@ 632,12 640,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 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)
      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)
        for i,shape in ipairs(drawing.shapes) do
          if geom.on_shape(mx,my, drawing, shape) then
            return drawing,line_cache,i,shape
            return drawing,starty,i,shape
          end
        end
      end


@@ 649,12 657,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 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)
      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)
        for i,point in ipairs(drawing.points) do
          if Drawing.near(point, mx,my, State.width) then
            return drawing_index,drawing,line_cache,i,point
            return drawing_index,drawing,starty,i,point
          end
        end
      end


@@ 666,8 674,7 @@ 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()
      local line_cache = State.line_cache[drawing_index]
      if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
      if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
        return drawing
      end
    end

M drawing_tests.lua => drawing_tests.lua +11 -11
@@ 3,7 3,7 @@
-- of specific shapes. In particular, no tests of freehand drawings.

function test_creating_drawing_saves()
  App.screen.init{width=120, height=60}
  App.screen.init{width=800, height=600}
  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(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
  check_eq(Text.starty(Editor_state, 1), 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(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
  check_eq(Text.starty(Editor_state, 1), 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(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
  check_eq(Text.starty(Editor_state, 1), 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(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
  check_eq(Text.starty(Editor_state, 1), 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(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
  check_eq(Text.starty(Editor_state, 1), 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(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
  check_eq(Text.starty(Editor_state, 1), 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(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
  check_eq(Text.starty(Editor_state, 1), 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(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
  check_eq(Text.starty(Editor_state, 1), 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(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
  check_eq(Text.starty(Editor_state, 1), 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(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y')
  check_eq(Text.starty(Editor_state, 1), 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

M edit.lua => edit.lua +9 -19
@@ 31,7 31,6 @@ 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


@@ 52,7 51,6 @@ 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,9 64,10 @@ 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
    screen_bottom1 = {line=1, pos=1},  -- position of start of screen line at bottom of screen
    cursor1 = {line=1, pos=1},  -- position of cursor; must be on a text line

    selection1 = {},
    -- some extra state to compute selection between mouse press and release


@@ 166,13 165,11 @@ 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


@@ 195,7 192,7 @@ function edit.draw(State)
                     end,
        })
      end
      y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos)
      y = Text.draw(State, line_index, y, startpos)
--?       print('=> y', y)
    elseif line.mode == 'drawing' then
      y = y+Drawing_padding_top


@@ 205,7 202,6 @@ 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


@@ 279,8 275,7 @@ function edit.mouse_press(State, x,y, mouse_button)
        return
      end
    elseif line.mode == 'drawing' then
      local line_cache = State.line_cache[line_index]
      if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then
      if Drawing.in_drawing(State, line_index, 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)


@@ 294,10 289,7 @@ 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 = {
      line=State.screen_bottom1.line,
      pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1),
  }
  State.selection1 = Text.final_text_loc_on_screen(State)
end

function edit.mouse_release(State, x,y, mouse_button)


@@ 335,7 327,7 @@ function edit.mouse_release(State, x,y, mouse_button)
    end

    -- still here? mouse release is below all screen lines
    State.cursor1.line, State.cursor1.pos = State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1)
    State.cursor1 = Text.final_text_loc_on_screen(State)
    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


@@ 363,7 355,7 @@ function edit.mouse_wheel_move(State, dx,dy)
      Text.up(State)
    end
  elseif dy < 0 then
    State.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
    State.cursor1 = Text.screen_bottom1(State)
    edit.put_cursor_on_next_text_line(State)
    for i=1,math.floor(-dy) do
      Text.down(State)


@@ 401,7 393,6 @@ 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


@@ 506,7 497,6 @@ 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)


@@ 543,7 533,6 @@ 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


@@ 598,6 587,7 @@ 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 = {}

M help.lua => help.lua +6 -6
@@ 1,8 1,8 @@
function draw_help_without_mouse_pressed(State, drawing_index)
  local drawing = State.lines[drawing_index]
  local line_cache = State.line_cache[drawing_index]
  local starty = Text.starty(State, drawing_index)
  App.color(Help_color)
  local y = line_cache.starty+10
  local y = 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,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty))
  love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty))
end

function draw_help_with_mouse_pressed(State, drawing_index)
  local drawing = State.lines[drawing_index]
  local line_cache = State.line_cache[drawing_index]
  local starty = Text.starty(State, drawing_index)
  App.color(Help_color)
  local y = line_cache.starty+10
  local y = 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,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty))
  love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty))
end

function current_shape(State, shape)

M search.lua => search.lua +4 -2
@@ 62,7 62,8 @@ 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
  if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(State.screen_bottom1, State.cursor1) then
  local screen_bottom1 = Text.screen_bottom1(State)
  if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(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


@@ 115,7 116,8 @@ 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
  if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(State.screen_bottom1, State.cursor1) then
  local screen_bottom1 = Text.screen_bottom1(State)
  if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(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

M select.lua => select.lua +3 -2
@@ 69,7 69,7 @@ end

function Text.mouse_pos(State)
  local x,y = App.mouse_x(), App.mouse_y()
  if y < State.line_cache[State.screen_top1.line].starty then
  if y < State.top then
    return State.screen_top1.line, State.screen_top1.pos
  end
  for line_index,line in ipairs(State.lines) do


@@ 79,7 79,8 @@ function Text.mouse_pos(State)
      end
    end
  end
  return State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1)
  local screen_bottom1 = Text.screen_bottom1(State)
  return screen_bottom1.line, Text.pos_at_end_of_screen_line(State, screen_bottom1)
end

function Text.cut_selection(State)

M source.lua => source.lua +0 -1
@@ 307,7 307,6 @@ 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


M source_edit.lua => source_edit.lua +10 -20
@@ 33,7 33,6 @@ 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


@@ 54,7 53,6 @@ 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 = {},


@@ 68,9 66,10 @@ 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
    screen_bottom1 = {line=1, pos=1},  -- position of start of screen line at bottom of screen
    cursor1 = {line=1, pos=1},  -- position of cursor; must be on a text line

    selection1 = {},
    -- some extra state to compute selection between mouse press and release


@@ 165,13 164,11 @@ 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


@@ 198,7 195,7 @@ function edit.draw(State, hide_cursor, show_line_numbers)
                     end,
        })
      end
      y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers)
      y = 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


@@ 208,7 205,6 @@ 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


@@ 281,8 277,7 @@ function edit.mouse_press(State, x,y, mouse_button)
        return
      end
    elseif line.mode == 'drawing' then
      local line_cache = State.line_cache[line_index]
      if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then
      if Drawing.in_drawing(State, line_index, 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)


@@ 296,10 291,7 @@ 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 = {
      line=State.screen_bottom1.line,
      pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1),
  }
  State.selection1 = Text.final_text_loc_on_screen(State)
end

function edit.mouse_release(State, x,y, mouse_button)


@@ 337,7 329,7 @@ function edit.mouse_release(State, x,y, mouse_button)
    end

    -- still here? mouse release is below all screen lines
    State.cursor1.line, State.cursor1.pos = State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1)
    State.cursor1 = Text.final_text_loc_on_screen(State)
    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


@@ 365,7 357,7 @@ function edit.mouse_wheel_move(State, dx,dy)
      Text.up(State)
    end
  elseif dy < 0 then
    State.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
    State.cursor1 = Text.screen_bottom1(State)
    edit.put_cursor_on_next_text_line(State)
    for i=1,math.floor(-dy) do
      Text.down(State)


@@ 399,11 391,10 @@ 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(chord) 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(key) 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


@@ 508,7 499,6 @@ 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)


@@ 545,7 535,6 @@ 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


@@ 600,6 589,7 @@ 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 = {}

M source_select.lua => source_select.lua +3 -2
@@ 69,7 69,7 @@ end

function Text.mouse_pos(State)
  local x,y = App.mouse_x(), App.mouse_y()
  if y < State.line_cache[State.screen_top1.line].starty then
  if y < State.top then
    return State.screen_top1.line, State.screen_top1.pos
  end
  for line_index,line in ipairs(State.lines) do


@@ 79,7 79,8 @@ function Text.mouse_pos(State)
      end
    end
  end
  return State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1)
  local screen_bottom1 = Text.screen_bottom1(State)
  return screen_bottom1.line, Text.pos_at_end_of_screen_line(State, screen_bottom1)
end

function Text.cut_selection(State)

M source_text.lua => source_text.lua +125 -50
@@ 2,14 2,12 @@
Text = {}

-- draw a line starting from startpos to screen at y between State.left and State.right
-- return y for the next line, and position of start of final screen line drawn
-- return y for the next line
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


@@ 24,7 22,6 @@ 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)


@@ 84,7 81,7 @@ function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_number
      end
    end
  end
  return y, final_screen_line_starting_pos
  return y
end

function Text.screen_line(line, line_cache, i)


@@ 208,7 205,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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?   print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.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)


@@ 241,12 238,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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?     print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?       print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
    end
    schedule_save(State)
    record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})


@@ 427,37 424,79 @@ function Text.insert_return(State)
end

function Text.pageup(State)
--?   print('pageup')
  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)
  -- 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
  local top2 = Text.to2(State, State.screen_top1)
--?   print(App.screen.height)
  -- does not modify State (except to populate line_cache)
  local loc2 = Text.to2(State, State.screen_top1)
  local y = App.screen.height - State.line_height
  while y >= State.top do
--?     print(y, top2.line, top2.screen_line, top2.screen_pos)
    if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end
    if State.lines[State.screen_top1.line].mode == 'text' then
    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
      y = y - State.line_height
    elseif State.lines[State.screen_top1.line].mode == 'drawing' then
      y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)
    elseif State.lines[loc2.line].mode == 'drawing' then
      y = y - Drawing_padding_height - Drawing.pixels(State.lines[loc2.line].h, State.width)
    end
    top2 = Text.previous_screen_line(State, top2)
    loc2 = Text.previous_screen_line(State, loc2)
  end
  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')
  return Text.to1(State, loc2)
end

function Text.pagedown(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.screen_top1 = Text.screen_bottom1(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)
--?   print('top now', State.screen_top1.line)
  Text.redraw_all(State)  -- if we're scrolling, reclaim all fragments to avoid memory leaks
--?   print('pagedown end')
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)
end

function Text.up(State)


@@ 505,7 544,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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?   print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.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


@@ 522,7 561,9 @@ function Text.down(State)
        break
      end
    end
    if State.cursor1.line > State.screen_bottom1.line then
    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
--?       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)


@@ 530,7 571,8 @@ function Text.down(State)
    end
  else
    -- move down one screen line in current line
    local scroll_down = Text.le1(State.screen_bottom1, State.cursor1)
    local screen_bottom1 = Text.screen_bottom1(State)
    local scroll_down = Text.le1(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)


@@ 546,7 588,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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?   print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
end

function Text.start_of_line(State)


@@ 683,6 725,7 @@ 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


@@ 696,6 739,25 @@ 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


@@ 736,7 798,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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?   print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.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


@@ 766,26 828,28 @@ 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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?   print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.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]
  if line_cache.starty == nil then return false end  -- outside current page
  if y < line_cache.starty then return false end
  local starty = Text.starty(State, line_index)
  if starty == nil then return false end  -- outside current page
  if y < starty then return false end
  Text.populate_screen_line_starting_pos(State, line_index)
  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)
  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)
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]
  assert(my >= line_cache.starty, 'failed to map y pixel to line')
  local starty = Text.starty(State, line_index)
  assert(my >= starty, 'failed to map y pixel to line')
  -- duplicate some logic from Text.draw
  local y = line_cache.starty
  local y = 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]


@@ 971,6 1035,10 @@ 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)


@@ 994,6 1062,22 @@ 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


@@ 1017,16 1101,12 @@ 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 >= State.screen_bottom1.line then
--?     print('too low')
  elseif State.cursor1.line >= screen_bottom1.line then
    if Text.cursor_out_of_screen(State) then
--?       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),
      }
      State.cursor1 = Text.final_text_loc_on_screen(State)
    end
  end
end


@@ 1035,11 1115,6 @@ 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)

M source_text_tests.lua => source_text_tests.lua +11 -87
@@ 16,7 16,7 @@ function test_initial_state()
end

function test_click_to_create_drawing()
  App.screen.init{width=120, height=60}
  App.screen.init{width=800, height=600}
  Editor_state = edit.initialize_test_state()
  Editor_state.lines = load_array{}
  Text.redraw_all(Editor_state)


@@ 75,7 75,6 @@ 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



@@ 232,7 231,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



@@ 243,7 242,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


@@ 255,9 254,8 @@ 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.starty for each line Editor_state.line_cache
  edit.draw(Editor_state)  -- populate line_cache.startpos for each line
  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')


@@ 274,7 272,6 @@ 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)


@@ 294,7 291,6 @@ 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)


@@ 313,7 309,6 @@ 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)


@@ 332,7 327,6 @@ 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)


@@ 350,7 344,6 @@ 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')


@@ 367,7 360,6 @@ 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')


@@ 384,7 376,6 @@ 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')


@@ 402,7 393,6 @@ 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)


@@ 421,7 411,6 @@ 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)


@@ 439,7 428,6 @@ 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')


@@ 457,7 445,6 @@ 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')


@@ 476,7 463,6 @@ 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')


@@ 499,7 485,6 @@ 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')


@@ 520,7 505,6 @@ 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')


@@ 544,7 528,6 @@ 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')


@@ 569,7 552,6 @@ 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')


@@ 588,11 570,10 @@ 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


@@ 611,10 592,9 @@ 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')


@@ 629,7 609,6 @@ 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')


@@ 646,7 625,6 @@ 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')


@@ 668,7 646,6 @@ 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')


@@ 686,7 663,6 @@ 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')


@@ 704,7 680,6 @@ 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'


@@ 723,7 698,6 @@ 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')


@@ 747,7 721,6 @@ 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


@@ 766,7 739,6 @@ 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')


@@ 795,7 767,6 @@ 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')


@@ 812,7 783,6 @@ 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')


@@ 841,9 811,8 @@ 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.starty for each line Editor_state.line_cache
  edit.draw(Editor_state)  -- populate line_cache.startpos for each line
  -- 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


@@ 861,9 830,8 @@ 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.starty for each line Editor_state.line_cache
  edit.draw(Editor_state)  -- populate line_cache.startpos for each line
  -- 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')


@@ 879,7 847,6 @@ 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)


@@ 902,7 869,6 @@ 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')


@@ 923,9 889,8 @@ 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.starty for each line Editor_state.line_cache
  edit.draw(Editor_state)  -- populate line_cache.startpos for each line
  -- 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)


@@ 948,9 913,8 @@ 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.starty for each line Editor_state.line_cache
  edit.draw(Editor_state)  -- populate line_cache.startpos for each line
  -- 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)


@@ 978,7 942,6 @@ 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')


@@ 1000,7 963,6 @@ 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


@@ 1016,7 978,6 @@ 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


@@ 1046,7 1007,6 @@ 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


@@ 1070,7 1030,6 @@ 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')


@@ 1105,7 1064,6 @@ 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')


@@ 1120,7 1078,6 @@ 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


@@ 1153,7 1110,6 @@ 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')


@@ 1175,7 1131,6 @@ 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')


@@ 1203,7 1158,6 @@ 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')


@@ 1232,7 1186,6 @@ 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')


@@ 1260,7 1213,6 @@ 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')


@@ 1294,7 1246,6 @@ 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')


@@ 1326,7 1277,6 @@ 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')


@@ 1348,7 1298,6 @@ 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')


@@ 1376,7 1325,6 @@ 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')


@@ 1398,7 1346,6 @@ 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')


@@ 1426,7 1373,6 @@ 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')


@@ 1456,7 1402,6 @@ 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')


@@ 1483,7 1428,6 @@ 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


@@ 1508,7 1452,6 @@ 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')


@@ 1537,7 1480,6 @@ 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')


@@ 1564,7 1506,6 @@ 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')


@@ 1593,7 1534,6 @@ 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')


@@ 1616,7 1556,6 @@ 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')


@@ 1635,7 1574,6 @@ 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')


@@ 1665,7 1603,6 @@ 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)


@@ 1694,7 1631,6 @@ 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)


@@ 1705,7 1641,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')


@@ 1724,7 1660,6 @@ 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)


@@ 1753,7 1688,6 @@ 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)


@@ 1784,7 1718,6 @@ 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')


@@ 1818,7 1751,6 @@ 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')


@@ 1846,7 1778,6 @@ 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')


@@ 1981,7 1912,6 @@ 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')


@@ 2016,7 1946,6 @@ 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')


@@ 2055,7 1984,6 @@ 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')


@@ 2076,7 2004,6 @@ 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')


@@ 2103,7 2030,6 @@ 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')


@@ 2121,7 2047,6 @@ 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')


@@ 2139,7 2064,6 @@ 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')

M text.lua => text.lua +125 -62
@@ 2,15 2,13 @@
Text = {}

-- draw a line starting from startpos to screen at y between State.left and State.right
-- return y for the next line, and position of start of final screen line drawn
-- return y for the next line
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


@@ 18,7 16,6 @@ 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)


@@ 59,7 56,7 @@ function Text.draw(State, line_index, y, startpos)
      end
    end
  end
  return y, final_screen_line_starting_pos
  return y
end

function Text.screen_line(line, line_cache, i)


@@ 134,7 131,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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?   print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.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)


@@ 167,12 164,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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?     print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?       print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
    end
    schedule_save(State)
    record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})


@@ 353,49 350,79 @@ function Text.insert_return(State)
end

function Text.pageup(State)
--?   print('pageup')
  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)
  -- duplicate some logic from love.draw
  local top2 = Text.to2(State, State.screen_top1)
--?   print(App.screen.height)
  -- 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 y = App.screen.height - State.line_height
  while y >= State.top do
--?     print(y, top2.line, top2.screen_line, top2.screen_pos)
    if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end
    if State.lines[State.screen_top1.line].mode == 'text' then
    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
      y = y - State.line_height
    elseif State.lines[State.screen_top1.line].mode == 'drawing' then
      y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)
    elseif State.lines[loc2.line].mode == 'drawing' then
      y = y - Drawing_padding_height - Drawing.pixels(State.lines[loc2.line].h, State.width)
    end
    top2 = Text.previous_screen_line(State, top2)
    loc2 = Text.previous_screen_line(State, loc2)
  end
  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')
  return Text.to1(State, loc2)
end

function Text.pagedown(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.screen_top1 = Text.screen_bottom1(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)
--?   print('top now', State.screen_top1.line)
  Text.redraw_all(State)  -- if we're scrolling, reclaim all fragments to avoid memory leaks
--?   print('pagedown end')
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)
end

function Text.up(State)


@@ 443,7 470,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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?   print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.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


@@ 460,7 487,9 @@ function Text.down(State)
        break
      end
    end
    if State.cursor1.line > State.screen_bottom1.line then
    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
--?       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)


@@ 468,7 497,8 @@ function Text.down(State)
    end
  else
    -- move down one screen line in current line
    local scroll_down = Text.le1(State.screen_bottom1, State.cursor1)
    local screen_bottom1 = Text.screen_bottom1(State)
    local scroll_down = Text.le1(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)


@@ 484,7 514,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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?   print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
end

function Text.start_of_line(State)


@@ 621,6 651,7 @@ 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


@@ 634,6 665,25 @@ 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


@@ 674,7 724,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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?   print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.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


@@ 704,26 754,28 @@ 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, State.screen_bottom1.line, State.screen_bottom1.pos)
--?   print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.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]
  if line_cache.starty == nil then return false end  -- outside current page
  if y < line_cache.starty then return false end
  local starty = Text.starty(State, line_index)
  if starty == nil then return false end  -- outside current page
  if y < starty then return false end
  Text.populate_screen_line_starting_pos(State, line_index)
  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)
  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)
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]
  assert(my >= line_cache.starty, 'failed to map y pixel to line')
  local starty = Text.starty(State, line_index)
  assert(my >= starty, 'failed to map y pixel to line')
  -- duplicate some logic from Text.draw
  local y = line_cache.starty
  local y = 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]


@@ 909,6 961,10 @@ 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)


@@ 932,6 988,22 @@ 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


@@ 955,16 1027,12 @@ 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 >= State.screen_bottom1.line then
--?     print('too low')
  elseif State.cursor1.line >= screen_bottom1.line then
    if Text.cursor_out_of_screen(State) then
--?       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),
      }
      State.cursor1 = Text.final_text_loc_on_screen(State)
    end
  end
end


@@ 973,11 1041,6 @@ 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)

M text_tests.lua => text_tests.lua +6 -112
@@ 16,7 16,7 @@ function test_initial_state()
end

function test_click_to_create_drawing()
  App.screen.init{width=120, height=60}
  App.screen.init{width=800, height=600}
  Editor_state = edit.initialize_test_state()
  Editor_state.lines = load_array{}
  Text.redraw_all(Editor_state)


@@ 75,7 75,6 @@ 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



@@ 255,9 254,8 @@ 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.starty for each line Editor_state.line_cache
  edit.draw(Editor_state)  -- populate line_cache.startpos for each line
  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')


@@ 274,7 272,6 @@ 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)


@@ 294,7 291,6 @@ 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)


@@ 313,7 309,6 @@ 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)


@@ 332,7 327,6 @@ 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)


@@ 350,7 344,6 @@ 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')


@@ 367,7 360,6 @@ 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')


@@ 384,7 376,6 @@ 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')


@@ 402,7 393,6 @@ 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)


@@ 421,7 411,6 @@ 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)


@@ 439,7 428,6 @@ 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')


@@ 457,7 445,6 @@ 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')


@@ 476,7 463,6 @@ 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')


@@ 499,7 485,6 @@ 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')


@@ 520,7 505,6 @@ 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')


@@ 544,7 528,6 @@ 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')


@@ 569,7 552,6 @@ 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')


@@ 588,7 570,6 @@ 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')


@@ 611,7 592,6 @@ 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')


@@ 629,7 609,6 @@ 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')


@@ 646,7 625,6 @@ 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')


@@ 668,7 646,6 @@ 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')


@@ 686,7 663,6 @@ 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')


@@ 704,7 680,6 @@ 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'


@@ 723,7 698,6 @@ 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')


@@ 747,7 721,6 @@ 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


@@ 766,7 739,6 @@ 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')


@@ 795,7 767,6 @@ 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')


@@ 812,7 783,6 @@ 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')


@@ 841,9 811,8 @@ 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.starty for each line Editor_state.line_cache
  edit.draw(Editor_state)  -- populate line_cache.startpos for each line
  -- 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


@@ 861,9 830,8 @@ 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.starty for each line Editor_state.line_cache
  edit.draw(Editor_state)  -- populate line_cache.startpos for each line
  -- 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')


@@ 879,7 847,6 @@ 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)


@@ 902,7 869,6 @@ 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')


@@ 923,9 889,8 @@ 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.starty for each line Editor_state.line_cache
  edit.draw(Editor_state)  -- populate line_cache.startpos for each line
  -- 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)


@@ 948,9 913,8 @@ 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.starty for each line Editor_state.line_cache
  edit.draw(Editor_state)  -- populate line_cache.startpos for each line
  -- 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)


@@ 978,7 942,6 @@ 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')


@@ 1000,7 963,6 @@ 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


@@ 1016,7 978,6 @@ 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


@@ 1046,7 1007,6 @@ 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


@@ 1062,36 1022,6 @@ 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}


@@ 1100,7 1030,6 @@ 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')


@@ 1135,7 1064,6 @@ 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')


@@ 1150,7 1078,6 @@ 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


@@ 1183,7 1110,6 @@ 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')


@@ 1205,7 1131,6 @@ 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')


@@ 1233,7 1158,6 @@ 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')


@@ 1262,7 1186,6 @@ 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')


@@ 1290,7 1213,6 @@ 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')


@@ 1324,7 1246,6 @@ 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')


@@ 1356,7 1277,6 @@ 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')


@@ 1378,7 1298,6 @@ 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')


@@ 1406,7 1325,6 @@ 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')


@@ 1428,7 1346,6 @@ 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')


@@ 1456,7 1373,6 @@ 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')


@@ 1486,7 1402,6 @@ 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')


@@ 1513,7 1428,6 @@ 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


@@ 1538,7 1452,6 @@ 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')


@@ 1567,7 1480,6 @@ 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')


@@ 1594,7 1506,6 @@ 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')


@@ 1623,7 1534,6 @@ 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')


@@ 1646,7 1556,6 @@ 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')


@@ 1665,7 1574,6 @@ 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')


@@ 1695,7 1603,6 @@ 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)


@@ 1724,7 1631,6 @@ 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)


@@ 1754,7 1660,6 @@ 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)


@@ 1783,7 1688,6 @@ 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)


@@ 1814,7 1718,6 @@ 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')


@@ 1848,7 1751,6 @@ 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')


@@ 1876,7 1778,6 @@ 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')


@@ 2011,7 1912,6 @@ 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')


@@ 2046,7 1946,6 @@ 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')


@@ 2085,7 1984,6 @@ 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')


@@ 2106,7 2004,6 @@ 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')


@@ 2133,7 2030,6 @@ 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')


@@ 2151,7 2047,6 @@ 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')


@@ 2169,7 2064,6 @@ 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')